-
Notifications
You must be signed in to change notification settings - Fork 191
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
Bounds
must be non-empty
#856
Changes from 2 commits
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 |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { Bounds, ConcreteBounds } from '../bounds'; | ||
import { Bounds } from '../bounds'; | ||
import { moveNodesBefore, DOMOperations } from '../dom/helper'; | ||
import { Option, unwrap } from '@glimmer/util'; | ||
import { Option, unwrap, assert } from '@glimmer/util'; | ||
import { Simple } from '@glimmer/interfaces'; | ||
|
||
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; | ||
export type SVG_NAMESPACE = typeof SVG_NAMESPACE; | ||
|
@@ -30,41 +31,51 @@ export function applySVGInnerHTMLFix( | |
let div = document.createElement('div'); | ||
|
||
return class DOMChangesWithSVGInnerHTMLFix extends DOMClass { | ||
insertHTMLBefore(parent: HTMLElement, nextSibling: Node, html: string): Bounds { | ||
insertHTMLBefore( | ||
parent: Simple.Element, | ||
nextSibling: Option<Simple.Node>, | ||
html: string | ||
): Bounds { | ||
if (html === '') { | ||
return super.insertHTMLBefore(parent, nextSibling, html); | ||
} | ||
|
||
if (parent.namespaceURI !== svgNamespace) { | ||
return super.insertHTMLBefore(parent, nextSibling, html); | ||
} | ||
|
||
return fixSVG(parent, div, html, nextSibling); | ||
// TODO: why are these casts okay??? | ||
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. 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 mental model is that |
||
return fixSVG(parent as Element, div, html, nextSibling as Option<Node>); | ||
} | ||
}; | ||
} | ||
|
||
function fixSVG(parent: Element, div: HTMLElement, html: string, reference: Node): Bounds { | ||
function fixSVG(parent: Element, div: HTMLElement, html: string, reference: Option<Node>): Bounds { | ||
assert(html !== '', 'html cannot be empty'); | ||
|
||
let source: Node; | ||
|
||
// This is important, because decendants of the <foreignObject> integration | ||
// point are parsed in the HTML namespace | ||
if (parent.tagName.toUpperCase() === 'FOREIGNOBJECT') { | ||
// IE, Edge: also do not correctly support using `innerHTML` on SVG | ||
// namespaced elements. So here a wrapper is used. | ||
let wrappedHtml = '<svg><foreignObject>' + (html || '<!---->') + '</foreignObject></svg>'; | ||
let wrappedHtml = '<svg><foreignObject>' + html + '</foreignObject></svg>'; | ||
|
||
div.innerHTML = wrappedHtml; | ||
|
||
source = div.firstChild!.firstChild!; | ||
} else { | ||
// IE, Edge: also do not correctly support using `innerHTML` on SVG | ||
// namespaced elements. So here a wrapper is used. | ||
let wrappedHtml = '<svg>' + (html || '<!---->') + '</svg>'; | ||
let wrappedHtml = '<svg>' + html + '</svg>'; | ||
|
||
div.innerHTML = wrappedHtml; | ||
|
||
source = div.firstChild!; | ||
} | ||
|
||
let [first, last] = moveNodesBefore(source, parent, reference); | ||
return new ConcreteBounds(parent, first, last); | ||
return moveNodesBefore(source, parent, reference); | ||
} | ||
|
||
function shouldApplyFix(document: Document, svgNamespace: SVG_NAMESPACE) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ import { applySVGInnerHTMLFix } from '../compat/svg-inner-html-fix'; | |
import { applyTextNodeMergingFix } from '../compat/text-node-merging-fix'; | ||
import { Simple } from '@glimmer/interfaces'; | ||
|
||
import { Option } from '@glimmer/util'; | ||
import { Option, expect } from '@glimmer/util'; | ||
|
||
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; | ||
|
||
|
@@ -77,17 +77,22 @@ export function isWhitespace(string: string) { | |
export function moveNodesBefore( | ||
source: Simple.Node, | ||
target: Simple.Element, | ||
nextSibling: Simple.Node | ||
) { | ||
let first = source.firstChild; | ||
let last: Simple.Node | null = null; | ||
let current = first; | ||
nextSibling: Option<Simple.Node> | ||
): Bounds { | ||
let first = expect(source.firstChild, 'source is empty'); | ||
let last: Simple.Node = first; | ||
let current: Option<Simple.Node> = first; | ||
|
||
while (current) { | ||
let next: Option<Simple.Node> = current.nextSibling; | ||
|
||
target.insertBefore(current, nextSibling); | ||
|
||
last = current; | ||
current = current.nextSibling; | ||
target.insertBefore(last, nextSibling); | ||
current = next; | ||
} | ||
return [first, last]; | ||
|
||
return new ConcreteBounds(target, first, last); | ||
} | ||
|
||
export class DOMOperations { | ||
|
@@ -134,10 +139,44 @@ export class DOMOperations { | |
|
||
insertHTMLBefore( | ||
_parent: Simple.Element, | ||
nextSibling: Option<Simple.Node>, | ||
_nextSibling: Option<Simple.Node>, | ||
html: string | ||
): Bounds { | ||
return insertHTMLBefore(this.uselessElement, _parent, nextSibling, html); | ||
if (html === '') { | ||
let comment = this.createComment(''); | ||
_parent.insertBefore(comment, _nextSibling); | ||
return new ConcreteBounds(_parent, comment, comment); | ||
} | ||
|
||
// TODO why are these casts okay??? | ||
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 parent = _parent as Element; | ||
let nextSibling = _nextSibling as Option<Node>; | ||
|
||
let prev = nextSibling ? nextSibling.previousSibling : parent.lastChild; | ||
let last: Simple.Node; | ||
|
||
if (nextSibling === null) { | ||
parent.insertAdjacentHTML('beforeend', html); | ||
last = expect(parent.lastChild, 'bug in insertAdjacentHTML?'); | ||
} else if (nextSibling instanceof HTMLElement) { | ||
nextSibling.insertAdjacentHTML('beforebegin', html); | ||
last = expect(nextSibling.previousSibling, 'bug in insertAdjacentHTML?'); | ||
} else { | ||
// Non-element nodes do not support insertAdjacentHTML, so add an | ||
// element and call it on that element. Then remove the element. | ||
// | ||
// This also protects Edge, IE and Firefox w/o the inspector open | ||
// from merging adjacent text nodes. See ./compat/text-node-merging-fix.ts | ||
let { uselessElement } = this; | ||
|
||
parent.insertBefore(uselessElement, nextSibling); | ||
uselessElement.insertAdjacentHTML('beforebegin', html); | ||
last = expect(uselessElement.previousSibling, 'bug in insertAdjacentHTML?'); | ||
parent.removeChild(uselessElement); | ||
} | ||
|
||
let first = expect(prev ? prev.nextSibling : parent.firstChild, 'bug in insertAdjacentHTML?'); | ||
return new ConcreteBounds(parent, first, last); | ||
} | ||
|
||
createTextNode(text: string): Simple.Text { | ||
|
@@ -205,43 +244,6 @@ export class DOMChanges extends DOMOperations { | |
} | ||
} | ||
|
||
export function insertHTMLBefore( | ||
this: void, | ||
useless: HTMLElement, | ||
_parent: Simple.Element, | ||
_nextSibling: Option<Simple.Node>, | ||
_html: string | ||
): Bounds { | ||
let parent = _parent as Element; | ||
let nextSibling = _nextSibling as Option<Node>; | ||
|
||
let prev = nextSibling ? nextSibling.previousSibling : parent.lastChild; | ||
let last: Simple.Node | null; | ||
|
||
let html = _html || '<!---->'; | ||
|
||
if (nextSibling === null) { | ||
parent.insertAdjacentHTML('beforeend', html); | ||
last = parent.lastChild; | ||
} else if (nextSibling instanceof HTMLElement) { | ||
nextSibling.insertAdjacentHTML('beforebegin', html); | ||
last = nextSibling.previousSibling; | ||
} else { | ||
// Non-element nodes do not support insertAdjacentHTML, so add an | ||
// element and call it on that element. Then remove the element. | ||
// | ||
// This also protects Edge, IE and Firefox w/o the inspector open | ||
// from merging adjacent text nodes. See ./compat/text-node-merging-fix.ts | ||
parent.insertBefore(useless, nextSibling); | ||
useless.insertAdjacentHTML('beforebegin', html); | ||
last = useless.previousSibling; | ||
parent.removeChild(useless); | ||
} | ||
|
||
let first = prev ? prev.nextSibling : parent.firstChild; | ||
return new ConcreteBounds(parent, first, last); | ||
} | ||
|
||
let helper = DOMChanges; | ||
|
||
helper = applyTextNodeMergingFix(doc, helper) as typeof DOMChanges; | ||
|
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.
This is a bug (similar in nature to #852) in FastBoot. Since you don't normally run updates there I don't know how much this matters (i.e. should I split this into a BUGFIX PR and release that separately).