diff --git a/src/component/base/DraftEditor.react.js b/src/component/base/DraftEditor.react.js index aa272305f2..df44bdd740 100644 --- a/src/component/base/DraftEditor.react.js +++ b/src/component/base/DraftEditor.react.js @@ -327,7 +327,11 @@ class DraftEditor extends React.Component { * ie9-beta-minor-change-list.aspx */ if (isIE) { - document.execCommand('AutoUrlDetect', false, false); + ReactDOM.findDOMNode(this.refs.editor).ownerDocument.execCommand( + 'AutoUrlDetect', + false, + false, + ); } } @@ -371,15 +375,13 @@ class DraftEditor extends React.Component { const scrollParent = Style.getScrollParent(editorNode); const {x, y} = scrollPosition || getScrollPosition(scrollParent); - invariant( - editorNode instanceof HTMLElement, - 'editorNode is not an HTMLElement', - ); + invariant(editorNode.nodeType === 1, 'editorNode is not an Element'); editorNode.focus(); // Restore scroll position - if (scrollParent === window) { - window.scrollTo(x, y); + const {defaultView} = editorNode.ownerDocument; + if (scrollParent === defaultView) { + defaultView.scrollTo(x, y); } else { Scroll.setTop(scrollParent, y); } @@ -397,10 +399,7 @@ class DraftEditor extends React.Component { blur = (): void => { const editorNode = ReactDOM.findDOMNode(this.editor); - invariant( - editorNode instanceof HTMLElement, - 'editorNode is not an HTMLElement', - ); + invariant(editorNode.nodeType === 1, 'editorNode is not an Element'); editorNode.blur(); }; diff --git a/src/component/contents/DraftEditorBlock.react.js b/src/component/contents/DraftEditorBlock.react.js index 2c62b8df54..6e9e50273f 100644 --- a/src/component/contents/DraftEditorBlock.react.js +++ b/src/component/contents/DraftEditorBlock.react.js @@ -102,26 +102,28 @@ class DraftEditorBlock extends React.Component { } const blockNode = ReactDOM.findDOMNode(this); + if (!blockNode || !blockNode.ownerDocument) { + return; + } + const scrollParent = Style.getScrollParent(blockNode); const scrollPosition = getScrollPosition(scrollParent); + const win = blockNode.ownerDocument.defaultView; let scrollDelta; - if (scrollParent === window) { + if (scrollParent === win) { const nodePosition = getElementPosition(blockNode); const nodeBottom = nodePosition.y + nodePosition.height; const viewportHeight = getViewportDimensions().height; scrollDelta = nodeBottom - viewportHeight; if (scrollDelta > 0) { - window.scrollTo( + win.scrollTo( scrollPosition.x, scrollPosition.y + scrollDelta + SCROLL_BUFFER, ); } } else { - invariant( - blockNode instanceof HTMLElement, - 'blockNode is not an HTMLElement', - ); + invariant(blockNode.nodeType === 1, 'blockNode is not an Element'); const blockBottom = blockNode.offsetHeight + blockNode.offsetTop; const scrollBottom = scrollParent.offsetHeight + scrollPosition.y; scrollDelta = blockBottom - scrollBottom; diff --git a/src/component/contents/DraftEditorTextNode.react.js b/src/component/contents/DraftEditorTextNode.react.js index 50e3bfb5d4..e47972d22c 100644 --- a/src/component/contents/DraftEditorTextNode.react.js +++ b/src/component/contents/DraftEditorTextNode.react.js @@ -81,7 +81,7 @@ class DraftEditorTextNode extends React.Component { shouldComponentUpdate(nextProps: Props): boolean { const node = ReactDOM.findDOMNode(this); const shouldBeNewline = nextProps.children === ''; - invariant(node instanceof Element, 'node is not an Element'); + invariant(node.nodeType === 1, 'node is not an Element'); if (shouldBeNewline) { return !isNewline(node); } diff --git a/src/component/contents/exploration/DraftEditorBlockNode.react.js b/src/component/contents/exploration/DraftEditorBlockNode.react.js index 40c6273a8b..d45377d0c6 100644 --- a/src/component/contents/exploration/DraftEditorBlockNode.react.js +++ b/src/component/contents/exploration/DraftEditorBlockNode.react.js @@ -237,26 +237,28 @@ class DraftEditorBlockNode extends React.Component { } const blockNode = ReactDOM.findDOMNode(this); + if (!blockNode || !blockNode.ownerDocument) { + return; + } + const scrollParent = Style.getScrollParent(blockNode); const scrollPosition = getScrollPosition(scrollParent); + const win = blockNode.ownerDocument.defaultView; let scrollDelta; - if (scrollParent === window) { + if (scrollParent === win) { const nodePosition = getElementPosition(blockNode); const nodeBottom = nodePosition.y + nodePosition.height; const viewportHeight = getViewportDimensions().height; scrollDelta = nodeBottom - viewportHeight; if (scrollDelta > 0) { - window.scrollTo( + win.scrollTo( scrollPosition.x, scrollPosition.y + scrollDelta + SCROLL_BUFFER, ); } } else { - invariant( - blockNode instanceof HTMLElement, - 'blockNode is not an HTMLElement', - ); + invariant(blockNode.nodeType === 1, 'blockNode is not an Element'); const blockBottom = blockNode.offsetHeight + blockNode.offsetTop; const scrollBottom = scrollParent.offsetHeight + scrollPosition.y; scrollDelta = blockBottom - scrollBottom; diff --git a/src/component/handlers/edit/editOnBlur.js b/src/component/handlers/edit/editOnBlur.js index d96d751886..42d761bd51 100644 --- a/src/component/handlers/edit/editOnBlur.js +++ b/src/component/handlers/edit/editOnBlur.js @@ -20,7 +20,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): 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. @@ -29,8 +29,9 @@ 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). - if (getActiveElement() === document.body) { - const selection = global.getSelection(); + const {ownerDocument} = e.currentTarget; + if (getActiveElement(ownerDocument) === ownerDocument.body) { + const selection = ownerDocument.defaultView.getSelection(); const editorNode = editor.editor; if ( selection.rangeCount === 1 && diff --git a/src/component/handlers/edit/editOnCut.js b/src/component/handlers/edit/editOnCut.js index 575b9578a2..4eb3b7b3ef 100644 --- a/src/component/handlers/edit/editOnCut.js +++ b/src/component/handlers/edit/editOnCut.js @@ -31,11 +31,13 @@ const getScrollPosition = require('getScrollPosition'); * In addition, we can keep a copy of the removed fragment, including all * styles and entities, for use as an internal paste. */ -function editOnCut(editor: DraftEditor, e: SyntheticClipboardEvent<>): void { +function editOnCut( + editor: DraftEditor, + e: SyntheticClipboardEvent, +): void { const editorState = editor._latestEditorState; const selection = editorState.getSelection(); - const element = e.target; - let scrollPosition; + const element = e.currentTarget; // No selection, so there's nothing to cut. if (selection.isCollapsed()) { @@ -45,9 +47,7 @@ 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)); - } + const scrollPosition = getScrollPosition(Style.getScrollParent(element)); const fragment = getFragmentFromSelection(editorState); editor.setClipboard(fragment); diff --git a/src/component/handlers/edit/editOnInput.js b/src/component/handlers/edit/editOnInput.js index 8d231287b6..3dbc8fa6a5 100644 --- a/src/component/handlers/edit/editOnInput.js +++ b/src/component/handlers/edit/editOnInput.js @@ -46,7 +46,8 @@ function editOnInput(editor: DraftEditor): void { editor._pendingStateFromBeforeInput = undefined; } - var domSelection = global.getSelection(); + const editorNode = nullthrows(editor.editor); + const domSelection = editorNode.ownerDocument.defaultView.getSelection(); var {anchorNode, isCollapsed} = domSelection; const isNotTextNode = anchorNode.nodeType !== Node.TEXT_NODE; diff --git a/src/component/handlers/edit/editOnSelect.js b/src/component/handlers/edit/editOnSelect.js index 88aa3d70d1..a089381c99 100644 --- a/src/component/handlers/edit/editOnSelect.js +++ b/src/component/handlers/edit/editOnSelect.js @@ -16,10 +16,9 @@ import type DraftEditor from 'DraftEditor.react'; var EditorState = require('EditorState'); -var ReactDOM = require('ReactDOM'); var getDraftEditorSelection = require('getDraftEditorSelection'); -const invariant = require('invariant'); +const nullthrows = require('nullthrows'); function editOnSelect(editor: DraftEditor): void { if ( @@ -30,16 +29,8 @@ function editOnSelect(editor: DraftEditor): void { } var editorState = editor.props.editorState; - const editorNode = ReactDOM.findDOMNode(editor.editorContainer); - invariant(editorNode, 'Missing editorNode'); - invariant( - editorNode.firstChild instanceof HTMLElement, - 'editorNode.firstChild is not an HTMLElement', - ); - var documentSelection = getDraftEditorSelection( - editorState, - editorNode.firstChild, - ); + const editorNode = nullthrows(editor.editor); + var documentSelection = getDraftEditorSelection(editorState, editorNode); var updatedSelectionState = documentSelection.selectionState; if (updatedSelectionState !== editorState.getSelection()) { diff --git a/src/component/selection/expandRangeToStartOfLine.js b/src/component/selection/expandRangeToStartOfLine.js index e889b05925..bdbcd1d251 100644 --- a/src/component/selection/expandRangeToStartOfLine.js +++ b/src/component/selection/expandRangeToStartOfLine.js @@ -30,7 +30,7 @@ function getLineHeightPx(element: Element): number { div.style.position = 'absolute'; div.textContent = 'M'; - let documentBody = document.body; + let documentBody = element.ownerDocument.body; invariant(documentBody, 'Missing document.body'); // forced layout here diff --git a/src/component/selection/findAncestorOffsetKey.js b/src/component/selection/findAncestorOffsetKey.js index 45530b3372..de1c97efc8 100644 --- a/src/component/selection/findAncestorOffsetKey.js +++ b/src/component/selection/findAncestorOffsetKey.js @@ -20,7 +20,7 @@ var getSelectionOffsetKeyForNode = require('getSelectionOffsetKeyForNode'); */ function findAncestorOffsetKey(node: Node): ?string { let searchNode = node; - while (searchNode && searchNode !== document.documentElement) { + while (searchNode && searchNode.nodeName !== 'HTML') { var key = getSelectionOffsetKeyForNode(searchNode); if (key != null) { return key; diff --git a/src/component/selection/getDraftEditorSelection.js b/src/component/selection/getDraftEditorSelection.js index a9f7c16ede..cb2203fd12 100644 --- a/src/component/selection/getDraftEditorSelection.js +++ b/src/component/selection/getDraftEditorSelection.js @@ -26,7 +26,7 @@ function getDraftEditorSelection( editorState: EditorState, root: HTMLElement, ): DOMDerivedSelection { - var selection = global.getSelection(); + var selection = root.ownerDocument.defaultView.getSelection(); // No active selection. if (selection.rangeCount === 0) { diff --git a/src/component/selection/getDraftEditorSelectionWithNodes.js b/src/component/selection/getDraftEditorSelectionWithNodes.js index 7f28e6d66b..f5f1709814 100644 --- a/src/component/selection/getDraftEditorSelectionWithNodes.js +++ b/src/component/selection/getDraftEditorSelectionWithNodes.js @@ -169,7 +169,10 @@ function getPointForNonTextNode( if (editorRoot === node) { node = node.firstChild; invariant( - node instanceof Element && node.getAttribute('data-contents') === 'true', + node && + node.nodeType === 1 && + typeof node.getAttribute === 'function' && + node.getAttribute('data-contents') === 'true', 'Invalid DraftEditorContents structure.', ); if (childOffset > 0) { diff --git a/src/component/selection/getSelectionOffsetKeyForNode.js b/src/component/selection/getSelectionOffsetKeyForNode.js index bc149bedd8..eabae39ed1 100644 --- a/src/component/selection/getSelectionOffsetKeyForNode.js +++ b/src/component/selection/getSelectionOffsetKeyForNode.js @@ -18,13 +18,14 @@ * found on the DOM tree of given node. */ function getSelectionOffsetKeyForNode(node: Node): ?string { - if (node instanceof Element) { - var offsetKey = node.getAttribute('data-offset-key'); + if (node.nodeType === 1) { + var element: Element = (node: any); + var offsetKey = element.getAttribute('data-offset-key'); if (offsetKey) { return offsetKey; } - for (var ii = 0; ii < node.childNodes.length; ii++) { - var childOffsetKey = getSelectionOffsetKeyForNode(node.childNodes[ii]); + for (var ii = 0; ii < element.childNodes.length; ii++) { + var childOffsetKey = getSelectionOffsetKeyForNode(element.childNodes[ii]); if (childOffsetKey) { return childOffsetKey; } diff --git a/src/component/selection/setDraftEditorSelection.js b/src/component/selection/setDraftEditorSelection.js index 98a79aee2e..8484ecbd62 100644 --- a/src/component/selection/setDraftEditorSelection.js +++ b/src/component/selection/setDraftEditorSelection.js @@ -35,10 +35,11 @@ function getAnonymizedDOM( } invariant( - anonymized instanceof Element, + anonymized.nodeType === Node.ELEMENT_NODE, 'Node must be an Element if it is not a text node.', ); - return anonymized.outerHTML; + var element: Element = (anonymized: any); + return element.outerHTML; } function anonymizeTextWithin( @@ -76,15 +77,14 @@ function getAnonymizedEditorDOM( // grabbing the DOM content of the Draft editor let currentNode = node; while (currentNode) { - if ( - currentNode instanceof Element && - currentNode.hasAttribute('contenteditable') - ) { - // found the Draft editor container - return getAnonymizedDOM(currentNode, getNodeLabels); - } else { - currentNode = currentNode.parentNode; + if (currentNode.nodeType === 1) { + const currentElement: Element = (currentNode: any); + if (currentElement.hasAttribute('contenteditable')) { + // found the Draft editor container + return getAnonymizedDOM(currentNode, getNodeLabels); + } } + currentNode = currentNode.parentNode; } return 'Could not find contentEditable parent of node'; } @@ -111,14 +111,16 @@ function setDraftEditorSelection( nodeStart: number, nodeEnd: number, ): void { + var ownerDocument = node.ownerDocument; + // It's possible that the editor has been removed from the DOM but // our selection code doesn't know it yet. Forcing selection in // this case may lead to errors, so just bail now. - if (!containsNode(document.documentElement, node)) { + if (!containsNode(ownerDocument.documentElement, node)) { return; } - var selection = global.getSelection(); + var selection = ownerDocument.defaultView.getSelection(); var anchorKey = selectionState.getAnchorKey(); var anchorOffset = selectionState.getAnchorOffset(); var focusKey = selectionState.getFocusKey(); @@ -234,7 +236,7 @@ function addFocusToSelection( offset: number, selectionState: SelectionState, ): void { - const activeElement = getActiveElement(); + const activeElement = getActiveElement(node.ownerDocument); if (selection.extend && containsNode(activeElement, node)) { // If `extend` is called while another element has focus, an error is // thrown. We therefore disable `extend` if the active element is somewhere @@ -315,7 +317,7 @@ function addPointToSelection( offset: number, selectionState: SelectionState, ): void { - var range = document.createRange(); + var range = node.ownerDocument.createRange(); // logging to catch bug that is being reported in t16250795 if (offset > getNodeLength(node)) { // in this case we know that the call to 'range.setStart' is about to throw diff --git a/src/model/encoding/convertFromHTMLToContentBlocks.js b/src/model/encoding/convertFromHTMLToContentBlocks.js index 0b32f327a3..cf07fa9e6d 100644 --- a/src/model/encoding/convertFromHTMLToContentBlocks.js +++ b/src/model/encoding/convertFromHTMLToContentBlocks.js @@ -189,8 +189,8 @@ const processInlineTag = ( const styleToCheck = inlineTags[tag]; if (styleToCheck) { currentStyle = currentStyle.add(styleToCheck).toOrderedSet(); - } else if (node instanceof HTMLElement) { - const htmlElement = node; + } else if (node.nodeType === 1) { + const htmlElement: HTMLElement = (node: any); currentStyle = currentStyle .withMutations(style => { const fontWeight = htmlElement.style.fontWeight; @@ -274,11 +274,9 @@ const containsSemanticBlockMarkup = ( }; const hasValidLinkText = (link: Node): boolean => { - invariant( - link instanceof HTMLAnchorElement, - 'Link must be an HTMLAnchorElement.', - ); - const protocol = link.protocol; + invariant(link.nodeName === 'A', 'Link must be an HTMLAnchorElement.'); + const element: HTMLAnchorElement = (link: any); + const {protocol} = element; return ( protocol === 'http:' || protocol === 'https:' || protocol === 'mailto:' ); @@ -378,7 +376,7 @@ const genFragment = ( text = text.replace(REGEX_LF, SPACE); } - // save the last block so we can use it later + // Save the last block so we can use it later lastBlock = nodeName; return { @@ -392,7 +390,7 @@ const genFragment = ( }; } - // save the last block so we can use it later + // Save the last block so we can use it later lastBlock = nodeName; // BR tags @@ -407,30 +405,30 @@ const genFragment = ( } // IMG tags - if ( - nodeName === 'img' && - node instanceof HTMLImageElement && - node.attributes.getNamedItem('src') && - node.attributes.getNamedItem('src').value - ) { - const image: HTMLImageElement = node; - const entityConfig = {}; - - imgAttr.forEach(attr => { - const imageAttribute = image.getAttribute(attr); - if (imageAttribute) { - entityConfig[attr] = imageAttribute; - } - }); - // Forcing this node to have children because otherwise no entity will be - // created for this node. - // The child text node cannot just have a space or return as content - - // we strip those out. - // See https://github.com/facebook/draft-js/issues/231 for some context. - node.textContent = '\ud83d\udcf7'; - - // TODO: update this when we remove DraftEntity entirely - inEntity = DraftEntity.__create('IMAGE', 'MUTABLE', entityConfig || {}); + if (nodeName === 'img') { + const image: HTMLImageElement = (node: any); + if ( + image.attributes.getNamedItem('src') && + image.attributes.getNamedItem('src').value + ) { + const entityConfig = {}; + + imgAttr.forEach(attr => { + const imageAttribute = image.getAttribute(attr); + if (imageAttribute) { + entityConfig[attr] = imageAttribute; + } + }); + // Forcing this node to have children because otherwise no entity will be + // created for this node. + // The child text node cannot just have a space or return as content - + // we strip those out. + // See https://github.com/facebook/draft-js/issues/231 for some context. + node.textContent = '\ud83d\udcf7'; + + // TODO: update this when we remove DraftEntity entirely + inEntity = DraftEntity.__create('IMAGE', 'MUTABLE', entityConfig || {}); + } } // Inline tags @@ -473,26 +471,23 @@ const genFragment = ( let entityId: ?string = null; while (child) { - if ( - child instanceof HTMLAnchorElement && - child.href && - hasValidLinkText(child) - ) { - const anchor: HTMLAnchorElement = child; - const entityConfig = {}; - - anchorAttr.forEach(attr => { - const anchorAttribute = anchor.getAttribute(attr); - if (anchorAttribute) { - entityConfig[attr] = anchorAttribute; - } - }); + entityId = null; + if (nodeName === 'a') { + const anchor: HTMLAnchorElement = (child: any); + if (anchor.href && hasValidLinkText(anchor)) { + const entityConfig = {}; + + anchorAttr.forEach(attr => { + const anchorAttribute = anchor.getAttribute(attr); + if (anchorAttribute) { + entityConfig[attr] = anchorAttribute; + } + }); - entityConfig.url = new URI(anchor.href).toString(); - // TODO: update this when we remove DraftEntity completely - entityId = DraftEntity.__create('LINK', 'MUTABLE', entityConfig || {}); - } else { - entityId = undefined; + entityConfig.url = new URI(anchor.href).toString(); + // TODO: update this when we remove DraftEntity completely + entityId = DraftEntity.__create('LINK', 'MUTABLE', entityConfig || {}); + } } const {