Skip to content
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

[shadow dom] make codemirror work inside closed shadow root #7075

Merged
merged 1 commit into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/display/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { clipPos } from "../line/pos.js"
import { findMaxLine } from "../line/spans.js"
import { displayWidth, measureChar, scrollGap } from "../measurement/position_measurement.js"
import { signal } from "../util/event.js"
import { activeElt, doc } from "../util/dom.js"
import { activeElt, root } from "../util/dom.js"
import { finishOperation, pushOperation } from "../util/operation_group.js"

import { ensureFocus } from "./focus.js"
Expand Down Expand Up @@ -116,7 +116,7 @@ function endOperation_W2(op) {
cm.display.maxLineChanged = false
}

let takeFocus = op.focus && op.focus == activeElt(doc(cm))
let takeFocus = op.focus && op.focus == activeElt(root(cm))
if (op.preparedSelection)
cm.display.input.showSelection(op.preparedSelection, takeFocus)
if (op.updatedDisplay || op.startHeight != cm.doc.height)
Expand Down
6 changes: 3 additions & 3 deletions src/display/update_display.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { heightAtLine, visualLineEndNo, visualLineNo } from "../line/spans.js"
import { getLine, lineNumberFor } from "../line/utils_line.js"
import { displayHeight, displayWidth, getDimensions, paddingVert, scrollGap } from "../measurement/position_measurement.js"
import { mac, webkit } from "../util/browser.js"
import { activeElt, removeChildren, contains, win, doc } from "../util/dom.js"
import { activeElt, removeChildren, contains, win, root, rootNode } from "../util/dom.js"
import { hasHandler, signal } from "../util/event.js"
import { signalLater } from "../util/operation_group.js"
import { indexOf } from "../util/misc.js"
Expand Down Expand Up @@ -57,7 +57,7 @@ export function maybeClipScrollbars(cm) {

function selectionSnapshot(cm) {
if (cm.hasFocus()) return null
let active = activeElt(doc(cm))
let active = activeElt(root(cm))
if (!active || !contains(cm.display.lineDiv, active)) return null
let result = {activeElt: active}
if (window.getSelection) {
Expand All @@ -73,7 +73,7 @@ function selectionSnapshot(cm) {
}

function restoreSelection(snapshot) {
if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(snapshot.activeElt.ownerDocument)) return
if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(rootNode(snapshot.activeElt))) return
snapshot.activeElt.focus()
if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&
snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
Expand Down
4 changes: 2 additions & 2 deletions src/edit/fromTextArea.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CodeMirror } from "./CodeMirror.js"
import { activeElt } from "../util/dom.js"
import { activeElt, rootNode } from "../util/dom.js"
import { off, on } from "../util/event.js"
import { copyObj } from "../util/misc.js"

Expand All @@ -13,7 +13,7 @@ export function fromTextArea(textarea, options) {
// Set autofocus to true if this textarea is focused, or if it has
// autofocus and no other element is focused.
if (options.autofocus == null) {
let hasFocus = activeElt(textarea.ownerDocument)
let hasFocus = activeElt(rootNode(textarea))
options.autofocus = hasFocus == textarea ||
textarea.getAttribute("autofocus") != null && hasFocus == document.body
}
Expand Down
4 changes: 2 additions & 2 deletions src/edit/key_events.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { restartBlink } from "../display/selection.js"
import { isModifierKey, keyName, lookupKey } from "../input/keymap.js"
import { eventInWidget } from "../measurement/widgets.js"
import { ie, ie_version, mac, presto, gecko } from "../util/browser.js"
import { activeElt, addClass, rmClass, doc } from "../util/dom.js"
import { activeElt, addClass, rmClass, root } from "../util/dom.js"
import { e_preventDefault, off, on, signalDOMEvent } from "../util/event.js"
import { hasCopyEvent } from "../util/feature_detection.js"
import { Delayed, Pass } from "../util/misc.js"
Expand Down Expand Up @@ -107,7 +107,7 @@ let lastStoppedKey = null
export function onKeyDown(e) {
let cm = this
if (e.target && e.target != cm.display.input.getField()) return
cm.curOp.focus = activeElt(doc(cm))
cm.curOp.focus = activeElt(root(cm))
if (signalDOMEvent(cm, e)) return
// IE does strange things with escape.
if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false
Expand Down
4 changes: 2 additions & 2 deletions src/edit/methods.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { deleteNearSelection } from "./deleteNearSelection.js"
import { commands } from "./commands.js"
import { attachDoc } from "../model/document_data.js"
import { activeElt, addClass, rmClass, doc, win } from "../util/dom.js"
import { activeElt, addClass, rmClass, root, win } from "../util/dom.js"
import { eventMixin, signal } from "../util/event.js"
import { getLineStyles, getContextBefore, takeToken } from "../line/highlight.js"
import { indentLine } from "../input/indent.js"
Expand Down Expand Up @@ -358,7 +358,7 @@ export default function(CodeMirror) {

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

scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }),
Expand Down
6 changes: 3 additions & 3 deletions src/edit/mouse_events.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { normalizeSelection, Range, Selection } from "../model/selection.js"
import { extendRange, extendSelection, replaceOneSelection, setSelection } from "../model/selection_updates.js"
import { captureRightClick, chromeOS, ie, ie_version, mac, webkit, safari } from "../util/browser.js"
import { getOrder, getBidiPartAt } from "../util/bidi.js"
import { activeElt, doc as getDoc, win } from "../util/dom.js"
import { activeElt, root, win } from "../util/dom.js"
import { e_button, e_defaultPrevented, e_preventDefault, e_target, hasHandler, off, on, signal, signalDOMEvent } from "../util/event.js"
import { dragAndDrop } from "../util/feature_detection.js"
import { bind, countColumn, findColumn, sel_mouse } from "../util/misc.js"
Expand Down Expand Up @@ -128,7 +128,7 @@ function configureMouse(cm, repeat, event) {

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

let behavior = configureMouse(cm, repeat, event)

Expand Down Expand Up @@ -292,7 +292,7 @@ function leftButtonSelect(cm, event, start, behavior) {
let cur = posFromMouse(cm, e, true, behavior.unit == "rectangle")
if (!cur) return
if (cmp(cur, lastPos) != 0) {
cm.curOp.focus = activeElt(getDoc(cm))
cm.curOp.focus = activeElt(root(cm))
extendTo(cur)
let visible = visibleLines(display, doc)
if (cur.line >= visible.to || cur.line < visible.from)
Expand Down
8 changes: 4 additions & 4 deletions src/input/ContentEditableInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { simpleSelection } from "../model/selection.js"
import { setSelection } from "../model/selection_updates.js"
import { getBidiPartAt, getOrder } from "../util/bidi.js"
import { android, chrome, gecko, ie_version } from "../util/browser.js"
import { activeElt, contains, range, removeChildrenAndAdd, selectInput } from "../util/dom.js"
import { activeElt, contains, range, removeChildrenAndAdd, selectInput, rootNode } from "../util/dom.js"
import { on, signalDOMEvent } from "../util/event.js"
import { Delayed, lst, sel_dontScroll } from "../util/misc.js"

Expand Down Expand Up @@ -97,7 +97,7 @@ export default class ContentEditableInput {
disableBrowserMagic(te)
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild)
te.value = lastCopied.text.join("\n")
let hadFocus = activeElt(div.ownerDocument)
let hadFocus = activeElt(rootNode(div))
selectInput(te)
setTimeout(() => {
cm.display.lineSpace.removeChild(kludge)
Expand All @@ -120,7 +120,7 @@ export default class ContentEditableInput {

prepareSelection() {
let result = prepareSelection(this.cm, false)
result.focus = activeElt(this.div.ownerDocument) == this.div
result.focus = activeElt(rootNode(this.div)) == this.div
return result
}

Expand Down Expand Up @@ -214,7 +214,7 @@ export default class ContentEditableInput {

focus() {
if (this.cm.options.readOnly != "nocursor") {
if (!this.selectionInEditor() || activeElt(this.div.ownerDocument) != this.div)
if (!this.selectionInEditor() || activeElt(rootNode(this.div)) != this.div)
this.showSelection(this.prepareSelection(), true)
this.div.focus()
}
Expand Down
4 changes: 2 additions & 2 deletions src/input/TextareaInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { eventInWidget } from "../measurement/widgets.js"
import { simpleSelection } from "../model/selection.js"
import { selectAll, setSelection } from "../model/selection_updates.js"
import { captureRightClick, ie, ie_version, ios, mac, mobile, presto, webkit } from "../util/browser.js"
import { activeElt, removeChildrenAndAdd, selectInput } from "../util/dom.js"
import { activeElt, removeChildrenAndAdd, selectInput, rootNode } from "../util/dom.js"
import { e_preventDefault, e_stop, off, on, signalDOMEvent } from "../util/event.js"
import { hasSelection } from "../util/feature_detection.js"
import { Delayed, sel_dontScroll } from "../util/misc.js"
Expand Down Expand Up @@ -182,7 +182,7 @@ export default class TextareaInput {
supportsTouch() { return false }

focus() {
if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt(this.textarea.ownerDocument) != this.textarea)) {
if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt(rootNode(this.textarea)) != this.textarea)) {
try { this.textarea.focus() }
catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
}
Expand Down
14 changes: 12 additions & 2 deletions src/util/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ export function contains(parent, child) {
} while (child = child.parentNode)
}

export function activeElt(doc) {
export function activeElt(rootNode) {
// IE and Edge may throw an "Unspecified Error" when accessing document.activeElement.
// IE < 10 will throw when accessed while the page is loading or in an iframe.
// IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.
let doc = rootNode.ownerDocument || rootNode
let activeElement
try {
activeElement = doc.activeElement
activeElement = rootNode.activeElement
} catch(e) {
activeElement = doc.body || null
}
Expand Down Expand Up @@ -98,4 +99,13 @@ else if (ie) // Suppress mysterious IE10 errors

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

export function root(cm) {
return rootNode(cm.display.wrapper)
}

export function rootNode(element) {
// Detect modern browsers (2017+).
return element.getRootNode ? element.getRootNode() : element.ownerDocument
}

export function win(cm) { return doc(cm).defaultView }