* @fileoverview DOM related utilities.
*/
export class HTMLElementWithSymbolIndex extends HTMLElement {
[key: symbol|string]: any
}
export class ElementWithSymbolIndex extends HTMLElement {
[key: symbol|string]: any
}
export class NodeWithSymbolIndex extends Node {
[key: symbol|string]: any
}
export class TextWithSymbolIndex extends Text {
[key: symbol|string]: any
}
export interface Rect {
height?: number;
width?: number;
x?: number;
y?: number;
}
const IGNORE_NODE_NAMES = new Set([
'SCRIPT', 'NOSCRIPT', 'STYLE', 'EMBED', 'OBJECT',
'TEXTAREA', 'IFRAME', 'INPUT', 'IMG', 'CHROME_ANNOTATION',
'HEAD', 'APPLET', 'AREA', 'AUDIO', 'BUTTON',
'CANVAS', 'FRAME', 'FRAMESET', 'KEYGEN', 'LABEL',
'MAP', 'OPTGROUP', 'OPTION', 'PROGRESS', 'SELECT',
'VIDEO', 'A', 'APP', 'FORM', 'SVG',
]);
export function getMetaContentByHttpEquiv(httpEquiv: string): string {
const metas = document.getElementsByTagName('meta');
for (const meta of metas) {
if (meta.httpEquiv && meta.httpEquiv.toLowerCase() === httpEquiv) {
return meta.content;
}
}
return '';
}
export function noFormatDetectionTypes(): Set<string> {
const metas = document.getElementsByTagName('meta');
const types = new Set<string>();
for (const meta of metas) {
if (meta.getAttribute('name') !== 'format-detection') {
continue;
}
const content = meta.getAttribute('content');
if (!content) {
continue;
}
const matches = content.toLowerCase().matchAll(/([a-z]+)\s*=\s*([a-z]+)/gi);
if (!matches) {
continue;
}
for (const match of matches) {
if (match && match[2] === 'no' && match[1]) {
types.add(match[1]);
}
}
}
return types;
}
export function hasNoIntentDetection(): boolean {
const metas = document.getElementsByTagName('meta');
for (const meta of metas) {
if (meta.getAttribute('name') === 'chrome' &&
meta.getAttribute('content') === 'nointentdetection') {
return true;
}
}
return false;
}
export function annotationCanBeCrossElement(annotationType: string) {
return ['address', 'email'].includes(annotationType.toLowerCase());
}
export function rectFromElement(element: Element): Rect {
const domRect = element.getClientRects()[0];
if (!domRect) {
return {};
}
return {
x: domRect.x,
y: domRect.y,
width: domRect.width,
height: domRect.height,
};
}
export function isValidNode(node: Node): boolean {
if (node instanceof Element && node.getAttribute('contenteditable')) {
return false;
}
return !IGNORE_NODE_NAMES.has(node.nodeName);
}
export function previousLeaf(node: Node|null, breakAtInvalid = false): Node|null {
while (node) {
while (node && !node.previousSibling) {
node = node.parentNode;
}
node = node && node.previousSibling;
if (node && isValidNode(node)) {
while (node && isValidNode(node) && node.hasChildNodes()) {
node = node.lastChild;
}
if (node && isValidNode(node)) {
return node;
}
}
if (breakAtInvalid) {
return null;
}
}
return null;
}
export function nextLeaf(node: Node|null, breakAtInvalid = false): Node|null {
while (node) {
while (node && !node.nextSibling) {
node = node.parentNode;
}
node = node && node.nextSibling;
if (node && isValidNode(node)) {
while (node && isValidNode(node) && node.hasChildNodes()) {
node = node.firstChild;
}
if (node && isValidNode(node)) {
return node;
}
}
if (breakAtInvalid) {
return null;
}
}
return null;
}