html2canvas/src/NodeParser.js

163 lines
6.5 KiB
JavaScript
Raw Normal View History

2017-07-29 05:07:42 +03:00
/* @flow */
'use strict';
2017-10-18 15:34:17 +03:00
import type ResourceLoader, {ImageElement} from './ResourceLoader';
2017-07-29 05:07:42 +03:00
import type Logger from './Logger';
import StackingContext from './StackingContext';
import NodeContainer from './NodeContainer';
import TextContainer from './TextContainer';
import {inlineInputElement, inlineTextAreaElement, inlineSelectElement} from './Input';
import {inlineListItemElement} from './ListItem';
2017-07-29 05:07:42 +03:00
export const NodeParser = (
node: HTMLElement,
2017-10-18 15:34:17 +03:00
resourceLoader: ResourceLoader,
2017-07-29 05:07:42 +03:00
logger: Logger
): StackingContext => {
if (__DEV__) {
logger.log(`Starting node parsing`);
}
let index = 0;
2017-10-18 15:34:17 +03:00
const container = new NodeContainer(node, null, resourceLoader, index++);
2017-08-06 12:37:10 +03:00
const stack = new StackingContext(container, null, true);
2017-10-18 15:34:17 +03:00
parseNodeTree(node, container, stack, resourceLoader, index);
2017-07-29 05:07:42 +03:00
if (__DEV__) {
logger.log(`Finished parsing node tree`);
}
return stack;
};
const IGNORED_NODE_NAMES = ['SCRIPT', 'HEAD', 'TITLE', 'OBJECT', 'BR', 'OPTION'];
const parseNodeTree = (
node: HTMLElement,
parent: NodeContainer,
stack: StackingContext,
2017-10-18 15:34:17 +03:00
resourceLoader: ResourceLoader,
index: number
2017-07-29 05:07:42 +03:00
): void => {
if (__DEV__ && index > 50000) {
throw new Error(`Recursion error while parsing node tree`);
}
for (let childNode = node.firstChild, nextNode; childNode; childNode = nextNode) {
nextNode = childNode.nextSibling;
const defaultView = childNode.ownerDocument.defaultView;
if (
childNode instanceof defaultView.Text ||
childNode instanceof Text ||
(defaultView.parent && childNode instanceof defaultView.parent.Text)
) {
2017-07-29 05:07:42 +03:00
if (childNode.data.trim().length > 0) {
parent.childNodes.push(TextContainer.fromTextNode(childNode, parent));
2017-07-29 05:07:42 +03:00
}
} else if (
childNode instanceof defaultView.HTMLElement ||
childNode instanceof HTMLElement ||
(defaultView.parent && childNode instanceof defaultView.parent.HTMLElement)
) {
2017-07-29 05:07:42 +03:00
if (IGNORED_NODE_NAMES.indexOf(childNode.nodeName) === -1) {
2017-10-18 15:34:17 +03:00
const container = new NodeContainer(childNode, parent, resourceLoader, index++);
2017-07-29 05:07:42 +03:00
if (container.isVisible()) {
if (childNode.tagName === 'INPUT') {
// $FlowFixMe
inlineInputElement(childNode, container);
} else if (childNode.tagName === 'TEXTAREA') {
// $FlowFixMe
inlineTextAreaElement(childNode, container);
} else if (childNode.tagName === 'SELECT') {
// $FlowFixMe
inlineSelectElement(childNode, container);
} else if (childNode.tagName === 'LI') {
// $FlowFixMe
inlineListItemElement(childNode, container, resourceLoader);
}
const SHOULD_TRAVERSE_CHILDREN = childNode.tagName !== 'TEXTAREA';
2017-07-29 05:07:42 +03:00
const treatAsRealStackingContext = createsRealStackingContext(
container,
childNode
2017-07-29 05:07:42 +03:00
);
if (treatAsRealStackingContext || createsStackingContext(container)) {
// for treatAsRealStackingContext:false, any positioned descendants and descendants
// which actually create a new stacking context should be considered part of the parent stacking context
const parentStack =
treatAsRealStackingContext || container.isPositioned()
? stack.getRealParentStackingContext()
: stack;
const childStack = new StackingContext(
container,
parentStack,
treatAsRealStackingContext
);
parentStack.contexts.push(childStack);
if (SHOULD_TRAVERSE_CHILDREN) {
2017-10-18 15:34:17 +03:00
parseNodeTree(childNode, container, childStack, resourceLoader, index);
}
2017-07-29 05:07:42 +03:00
} else {
stack.children.push(container);
if (SHOULD_TRAVERSE_CHILDREN) {
2017-10-18 15:34:17 +03:00
parseNodeTree(childNode, container, stack, resourceLoader, index);
}
2017-07-29 05:07:42 +03:00
}
}
}
2017-08-13 12:48:37 +03:00
} else if (
childNode instanceof defaultView.SVGSVGElement ||
childNode instanceof SVGSVGElement ||
(defaultView.parent && childNode instanceof defaultView.parent.SVGSVGElement)
2017-08-13 12:48:37 +03:00
) {
2017-10-18 15:34:17 +03:00
const container = new NodeContainer(childNode, parent, resourceLoader, index++);
2017-08-13 12:48:37 +03:00
const treatAsRealStackingContext = createsRealStackingContext(container, childNode);
if (treatAsRealStackingContext || createsStackingContext(container)) {
// for treatAsRealStackingContext:false, any positioned descendants and descendants
// which actually create a new stacking context should be considered part of the parent stacking context
const parentStack =
treatAsRealStackingContext || container.isPositioned()
? stack.getRealParentStackingContext()
: stack;
const childStack = new StackingContext(
container,
parentStack,
treatAsRealStackingContext
);
parentStack.contexts.push(childStack);
} else {
stack.children.push(container);
}
2017-07-29 05:07:42 +03:00
}
}
2017-07-29 05:07:42 +03:00
};
2017-08-13 12:48:37 +03:00
const createsRealStackingContext = (
container: NodeContainer,
node: HTMLElement | SVGSVGElement
): boolean => {
2017-07-29 05:07:42 +03:00
return (
container.isRootElement() ||
container.isPositionedWithZIndex() ||
container.style.opacity < 1 ||
container.isTransformed() ||
isBodyWithTransparentRoot(container, node)
);
};
const createsStackingContext = (container: NodeContainer): boolean => {
return container.isPositioned() || container.isFloating();
};
2017-08-13 12:48:37 +03:00
const isBodyWithTransparentRoot = (
container: NodeContainer,
node: HTMLElement | SVGSVGElement
): boolean => {
2017-07-29 05:07:42 +03:00
return (
node.nodeName === 'BODY' &&
container.parent instanceof NodeContainer &&
container.parent.style.background.backgroundColor.isTransparent()
);
};