Skip to content

Commit 676fc52

Browse files
authored
Make active element tracking work inside closed shadow roots
1 parent adc4282 commit 676fc52

File tree

9 files changed

+32
-22
lines changed

9 files changed

+32
-22
lines changed

src/display/operations.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { clipPos } from "../line/pos.js"
22
import { findMaxLine } from "../line/spans.js"
33
import { displayWidth, measureChar, scrollGap } from "../measurement/position_measurement.js"
44
import { signal } from "../util/event.js"
5-
import { activeElt, doc } from "../util/dom.js"
5+
import { activeElt, root } from "../util/dom.js"
66
import { finishOperation, pushOperation } from "../util/operation_group.js"
77

88
import { ensureFocus } from "./focus.js"
@@ -116,7 +116,7 @@ function endOperation_W2(op) {
116116
cm.display.maxLineChanged = false
117117
}
118118

119-
let takeFocus = op.focus && op.focus == activeElt(doc(cm))
119+
let takeFocus = op.focus && op.focus == activeElt(root(cm))
120120
if (op.preparedSelection)
121121
cm.display.input.showSelection(op.preparedSelection, takeFocus)
122122
if (op.updatedDisplay || op.startHeight != cm.doc.height)

src/display/update_display.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { heightAtLine, visualLineEndNo, visualLineNo } from "../line/spans.js"
33
import { getLine, lineNumberFor } from "../line/utils_line.js"
44
import { displayHeight, displayWidth, getDimensions, paddingVert, scrollGap } from "../measurement/position_measurement.js"
55
import { mac, webkit } from "../util/browser.js"
6-
import { activeElt, removeChildren, contains, win, doc } from "../util/dom.js"
6+
import { activeElt, removeChildren, contains, win, root, rootNode } from "../util/dom.js"
77
import { hasHandler, signal } from "../util/event.js"
88
import { signalLater } from "../util/operation_group.js"
99
import { indexOf } from "../util/misc.js"
@@ -57,7 +57,7 @@ export function maybeClipScrollbars(cm) {
5757

5858
function selectionSnapshot(cm) {
5959
if (cm.hasFocus()) return null
60-
let active = activeElt(doc(cm))
60+
let active = activeElt(root(cm))
6161
if (!active || !contains(cm.display.lineDiv, active)) return null
6262
let result = {activeElt: active}
6363
if (window.getSelection) {
@@ -73,7 +73,7 @@ function selectionSnapshot(cm) {
7373
}
7474

7575
function restoreSelection(snapshot) {
76-
if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(snapshot.activeElt.ownerDocument)) return
76+
if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(rootNode(snapshot.activeElt))) return
7777
snapshot.activeElt.focus()
7878
if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&
7979
snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {

src/edit/fromTextArea.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { CodeMirror } from "./CodeMirror.js"
2-
import { activeElt } from "../util/dom.js"
2+
import { activeElt, rootNode } from "../util/dom.js"
33
import { off, on } from "../util/event.js"
44
import { copyObj } from "../util/misc.js"
55

@@ -13,7 +13,7 @@ export function fromTextArea(textarea, options) {
1313
// Set autofocus to true if this textarea is focused, or if it has
1414
// autofocus and no other element is focused.
1515
if (options.autofocus == null) {
16-
let hasFocus = activeElt(textarea.ownerDocument)
16+
let hasFocus = activeElt(rootNode(textarea))
1717
options.autofocus = hasFocus == textarea ||
1818
textarea.getAttribute("autofocus") != null && hasFocus == document.body
1919
}

src/edit/key_events.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { restartBlink } from "../display/selection.js"
33
import { isModifierKey, keyName, lookupKey } from "../input/keymap.js"
44
import { eventInWidget } from "../measurement/widgets.js"
55
import { ie, ie_version, mac, presto, gecko } from "../util/browser.js"
6-
import { activeElt, addClass, rmClass, doc } from "../util/dom.js"
6+
import { activeElt, addClass, rmClass, root } from "../util/dom.js"
77
import { e_preventDefault, off, on, signalDOMEvent } from "../util/event.js"
88
import { hasCopyEvent } from "../util/feature_detection.js"
99
import { Delayed, Pass } from "../util/misc.js"
@@ -107,7 +107,7 @@ let lastStoppedKey = null
107107
export function onKeyDown(e) {
108108
let cm = this
109109
if (e.target && e.target != cm.display.input.getField()) return
110-
cm.curOp.focus = activeElt(doc(cm))
110+
cm.curOp.focus = activeElt(root(cm))
111111
if (signalDOMEvent(cm, e)) return
112112
// IE does strange things with escape.
113113
if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false

src/edit/methods.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { deleteNearSelection } from "./deleteNearSelection.js"
22
import { commands } from "./commands.js"
33
import { attachDoc } from "../model/document_data.js"
4-
import { activeElt, addClass, rmClass, doc, win } from "../util/dom.js"
4+
import { activeElt, addClass, rmClass, root, win } from "../util/dom.js"
55
import { eventMixin, signal } from "../util/event.js"
66
import { getLineStyles, getContextBefore, takeToken } from "../line/highlight.js"
77
import { indentLine } from "../input/indent.js"
@@ -358,7 +358,7 @@ export default function(CodeMirror) {
358358

359359
signal(this, "overwriteToggle", this, this.state.overwrite)
360360
},
361-
hasFocus: function() { return this.display.input.getField() == activeElt(doc(this)) },
361+
hasFocus: function() { return this.display.input.getField() == activeElt(root(this)) },
362362
isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },
363363

364364
scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }),

src/edit/mouse_events.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { normalizeSelection, Range, Selection } from "../model/selection.js"
99
import { extendRange, extendSelection, replaceOneSelection, setSelection } from "../model/selection_updates.js"
1010
import { captureRightClick, chromeOS, ie, ie_version, mac, webkit, safari } from "../util/browser.js"
1111
import { getOrder, getBidiPartAt } from "../util/bidi.js"
12-
import { activeElt, doc as getDoc, win } from "../util/dom.js"
12+
import { activeElt, root, win } from "../util/dom.js"
1313
import { e_button, e_defaultPrevented, e_preventDefault, e_target, hasHandler, off, on, signal, signalDOMEvent } from "../util/event.js"
1414
import { dragAndDrop } from "../util/feature_detection.js"
1515
import { bind, countColumn, findColumn, sel_mouse } from "../util/misc.js"
@@ -128,7 +128,7 @@ function configureMouse(cm, repeat, event) {
128128

129129
function leftButtonDown(cm, pos, repeat, event) {
130130
if (ie) setTimeout(bind(ensureFocus, cm), 0)
131-
else cm.curOp.focus = activeElt(getDoc(cm))
131+
else cm.curOp.focus = activeElt(root(cm))
132132

133133
let behavior = configureMouse(cm, repeat, event)
134134

@@ -292,7 +292,7 @@ function leftButtonSelect(cm, event, start, behavior) {
292292
let cur = posFromMouse(cm, e, true, behavior.unit == "rectangle")
293293
if (!cur) return
294294
if (cmp(cur, lastPos) != 0) {
295-
cm.curOp.focus = activeElt(getDoc(cm))
295+
cm.curOp.focus = activeElt(root(cm))
296296
extendTo(cur)
297297
let visible = visibleLines(display, doc)
298298
if (cur.line >= visible.to || cur.line < visible.from)

src/input/ContentEditableInput.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { simpleSelection } from "../model/selection.js"
1010
import { setSelection } from "../model/selection_updates.js"
1111
import { getBidiPartAt, getOrder } from "../util/bidi.js"
1212
import { android, chrome, gecko, ie_version } from "../util/browser.js"
13-
import { activeElt, contains, range, removeChildrenAndAdd, selectInput } from "../util/dom.js"
13+
import { activeElt, contains, range, removeChildrenAndAdd, selectInput, rootNode } from "../util/dom.js"
1414
import { on, signalDOMEvent } from "../util/event.js"
1515
import { Delayed, lst, sel_dontScroll } from "../util/misc.js"
1616

@@ -97,7 +97,7 @@ export default class ContentEditableInput {
9797
disableBrowserMagic(te)
9898
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild)
9999
te.value = lastCopied.text.join("\n")
100-
let hadFocus = activeElt(div.ownerDocument)
100+
let hadFocus = activeElt(rootNode(div))
101101
selectInput(te)
102102
setTimeout(() => {
103103
cm.display.lineSpace.removeChild(kludge)
@@ -120,7 +120,7 @@ export default class ContentEditableInput {
120120

121121
prepareSelection() {
122122
let result = prepareSelection(this.cm, false)
123-
result.focus = activeElt(this.div.ownerDocument) == this.div
123+
result.focus = activeElt(rootNode(this.div)) == this.div
124124
return result
125125
}
126126

@@ -214,7 +214,7 @@ export default class ContentEditableInput {
214214

215215
focus() {
216216
if (this.cm.options.readOnly != "nocursor") {
217-
if (!this.selectionInEditor() || activeElt(this.div.ownerDocument) != this.div)
217+
if (!this.selectionInEditor() || activeElt(rootNode(this.div)) != this.div)
218218
this.showSelection(this.prepareSelection(), true)
219219
this.div.focus()
220220
}

src/input/TextareaInput.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { eventInWidget } from "../measurement/widgets.js"
66
import { simpleSelection } from "../model/selection.js"
77
import { selectAll, setSelection } from "../model/selection_updates.js"
88
import { captureRightClick, ie, ie_version, ios, mac, mobile, presto, webkit } from "../util/browser.js"
9-
import { activeElt, removeChildrenAndAdd, selectInput } from "../util/dom.js"
9+
import { activeElt, removeChildrenAndAdd, selectInput, rootNode } from "../util/dom.js"
1010
import { e_preventDefault, e_stop, off, on, signalDOMEvent } from "../util/event.js"
1111
import { hasSelection } from "../util/feature_detection.js"
1212
import { Delayed, sel_dontScroll } from "../util/misc.js"
@@ -182,7 +182,7 @@ export default class TextareaInput {
182182
supportsTouch() { return false }
183183

184184
focus() {
185-
if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt(this.textarea.ownerDocument) != this.textarea)) {
185+
if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt(rootNode(this.textarea)) != this.textarea)) {
186186
try { this.textarea.focus() }
187187
catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
188188
}

src/util/dom.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,14 @@ export function contains(parent, child) {
6464
} while (child = child.parentNode)
6565
}
6666

67-
export function activeElt(doc) {
67+
export function activeElt(rootNode) {
6868
// IE and Edge may throw an "Unspecified Error" when accessing document.activeElement.
6969
// IE < 10 will throw when accessed while the page is loading or in an iframe.
7070
// IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.
71+
let doc = rootNode.ownerDocument || rootNode
7172
let activeElement
7273
try {
73-
activeElement = doc.activeElement
74+
activeElement = rootNode.activeElement
7475
} catch(e) {
7576
activeElement = doc.body || null
7677
}
@@ -98,4 +99,13 @@ else if (ie) // Suppress mysterious IE10 errors
9899

99100
export function doc(cm) { return cm.display.wrapper.ownerDocument }
100101

102+
export function root(cm) {
103+
return rootNode(cm.display.wrapper)
104+
}
105+
106+
export function rootNode(element) {
107+
// Detect modern browsers (2017+).
108+
return element.getRootNode ? element.getRootNode() : element.ownerDocument
109+
}
110+
101111
export function win(cm) { return doc(cm).defaultView }

0 commit comments

Comments
 (0)