-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Minimally support iframes (nested browsing contexts) in selection event handling #12037
Changes from 2 commits
6940e33
3f3c1d7
b08a270
51be426
3714067
66b8766
75bc65e
5a61b66
f752792
a8fcf05
0003681
fef8519
799ee39
6e3d4b7
d1ad016
3c32963
3f7b5c5
05d8969
a776570
26e9d82
4db5c44
ccf0329
2edf1f8
db02b65
75b9992
0f1de45
95b2aef
d997ba8
e63391c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,25 @@ import * as ReactDOMSelection from './ReactDOMSelection'; | |
import {ELEMENT_NODE} from '../shared/HTMLNodeType'; | ||
|
||
function isInDocument(node) { | ||
return containsNode(document.documentElement, node); | ||
return ( | ||
node && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to check if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The value that gets passed in comes from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gaearon We can update export default function getActiveElement(doc: ?Document): Element {
doc = doc || document;
try {
return doc.activeElement || doc.body;
} catch (e) {
return doc.body;
}
} But strictly speaking, flow will still complain that export default function getActiveElement(doc: ?Document): Element {
doc = doc || document;
const body = doc.body || doc.createElement('body');
try {
return doc.activeElement || body;
} catch (e) {
return body;
}
} Do you have a preferred approach? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to lean towards the first, but I keep reasoning out of it in favor of the second. Body could be null, and it really it should never happen. But I've been surprised too much before :). With the second example, do you need a try/catch? Also: do you anticipate any problems with code downstream working with a document body that isn't attached? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nhunzaker No try/catch needed for the second example, and the ReactInputSelection plugin won’t have any issues; the code is already setup to handle detached DOM elements for a case where the active element becomes detached between when it is first read and cached and after React finishes committing an update. The second option has grown on me; I suggested it thinking it was silly, but now feel like it’s pretty reasonable. If we go with that one, should we add a comment explaining that document.body can be null? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Let's go with it 👍 |
||
node.ownerDocument && | ||
containsNode(node.ownerDocument.documentElement, node) | ||
); | ||
} | ||
|
||
function getActiveElementDeep() { | ||
let win = window; | ||
let element = getActiveElement(); | ||
while (element instanceof win.HTMLIFrameElement) { | ||
try { | ||
win = element.contentDocument.defaultView; | ||
} catch (e) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What exactly are we catching here? Is it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you just try to access the contentDocument of an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a comment to this line describing such a scenario? |
||
return element; | ||
} | ||
element = getActiveElement(win.document); | ||
} | ||
return element; | ||
} | ||
|
||
/** | ||
|
@@ -33,7 +51,7 @@ export function hasSelectionCapabilities(elem) { | |
} | ||
|
||
export function getSelectionInformation() { | ||
const focusedElem = getActiveElement(); | ||
const focusedElem = getActiveElementDeep(); | ||
return { | ||
focusedElem: focusedElem, | ||
selectionRange: hasSelectionCapabilities(focusedElem) | ||
|
@@ -48,7 +66,7 @@ export function getSelectionInformation() { | |
* nodes and place them back in, resulting in focus being lost. | ||
*/ | ||
export function restoreSelection(priorSelectionInformation) { | ||
const curFocusedElem = getActiveElement(); | ||
const curFocusedElem = getActiveElementDeep(); | ||
const priorFocusedElem = priorSelectionInformation.focusedElem; | ||
const priorSelectionRange = priorSelectionInformation.selectionRange; | ||
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,32 +64,55 @@ function getSelection(node) { | |
start: node.selectionStart, | ||
end: node.selectionEnd, | ||
}; | ||
} else if (window.getSelection) { | ||
const selection = window.getSelection(); | ||
return { | ||
anchorNode: selection.anchorNode, | ||
anchorOffset: selection.anchorOffset, | ||
focusNode: selection.focusNode, | ||
focusOffset: selection.focusOffset, | ||
}; | ||
} else { | ||
let win = window; | ||
if (node.ownerDocument && node.ownerDocument.defaultView) { | ||
win = node.ownerDocument.defaultView; | ||
} | ||
if (win.getSelection) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise, when is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise, in IE8 |
||
const selection = win.getSelection(); | ||
return { | ||
anchorNode: selection.anchorNode, | ||
anchorOffset: selection.anchorOffset, | ||
focusNode: selection.focusNode, | ||
focusOffset: selection.focusOffset, | ||
}; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Get document associated with the event target. | ||
* | ||
* @param {object} nativeEventTarget | ||
* @return {Document} | ||
*/ | ||
function getEventTargetDocument(eventTarget) { | ||
return eventTarget.window === eventTarget | ||
? eventTarget.document | ||
: eventTarget.nodeType === DOCUMENT_NODE | ||
? eventTarget | ||
: eventTarget.ownerDocument; | ||
} | ||
|
||
/** | ||
* Poll selection to see whether it's changed. | ||
* | ||
* @param {object} nativeEvent | ||
* @param {object} nativeEventTarget | ||
* @return {?SyntheticEvent} | ||
*/ | ||
function constructSelectEvent(nativeEvent, nativeEventTarget) { | ||
// Ensure we have the right element, and that the user is not dragging a | ||
// selection (this matches native `select` event behavior). In HTML5, select | ||
// fires only on input and textarea thus if there's no focused element we | ||
// won't dispatch. | ||
const doc = getEventTargetDocument(nativeEventTarget); | ||
|
||
if ( | ||
mouseDown || | ||
activeElement == null || | ||
activeElement !== getActiveElement() | ||
activeElement !== getActiveElement(doc) | ||
) { | ||
return null; | ||
} | ||
|
@@ -140,12 +163,7 @@ const SelectEventPlugin = { | |
nativeEvent, | ||
nativeEventTarget, | ||
) { | ||
const doc = | ||
nativeEventTarget.window === nativeEventTarget | ||
? nativeEventTarget.document | ||
: nativeEventTarget.nodeType === DOCUMENT_NODE | ||
? nativeEventTarget | ||
: nativeEventTarget.ownerDocument; | ||
const doc = getEventTargetDocument(nativeEventTarget); | ||
// Track whether all listeners exists for this plugin. If none exist, we do | ||
// not extract events. See #3639. | ||
if (!doc || !isListeningToAllDependencies('onSelect', doc)) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaultView
is defined as a getter, and since we don't expect it to change withinsetOffsets
maybe we should store the value in a local variable so we avoid triggering the getter twice.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made that change (for this line and two other instances) in 51be426