Commit 5e35c498 authored by David Sveningsson's avatar David Sveningsson
Browse files

feat(dom): disable cache until node is fully constructed

parent e33c2130
Pipeline #240773686 passed with stages
in 10 minutes and 46 seconds
......@@ -173,37 +173,50 @@ describe("DOMNode", () => {
});
describe("cache", () => {
it("should not cache until enabled", () => {
expect.assertions(1);
const node = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
node.cacheSet("foo", 1);
expect(node.cacheGet("foo")).toBeUndefined();
});
it("cacheGet() should return undefined when no value is cached", () => {
expect.assertions(1);
const a = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
expect(a.cacheGet("foo")).toBeUndefined();
const node = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
node.cacheEnable();
expect(node.cacheGet("foo")).toBeUndefined();
});
it("cacheGet() should get value set with cacheSet()", () => {
expect.assertions(1);
const a = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
a.cacheSet("foo", 1);
expect(a.cacheGet("foo")).toEqual(1);
const node = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
node.cacheEnable();
node.cacheSet("foo", 1);
expect(node.cacheGet("foo")).toEqual(1);
});
it("cacheSet() should return value", () => {
expect.assertions(1);
const a = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
expect(a.cacheSet("foo", 1)).toEqual(1);
const node = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
node.cacheEnable();
expect(node.cacheSet("foo", 1)).toEqual(1);
});
it("cacheSet() should overwrite previous value", () => {
expect.assertions(1);
const a = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
a.cacheSet("foo", 1);
a.cacheSet("foo", 2);
expect(a.cacheGet("foo")).toEqual(2);
const node = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
node.cacheEnable();
node.cacheSet("foo", 1);
node.cacheSet("foo", 2);
expect(node.cacheGet("foo")).toEqual(2);
});
it("should cache values per instance", () => {
expect.assertions(4);
const a = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
const b = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
const a = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
const b = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
a.cacheEnable();
b.cacheEnable();
expect(a.cacheGet("foo")).toBeUndefined();
expect(b.cacheGet("foo")).toBeUndefined();
a.cacheSet("foo", 1);
......@@ -214,30 +227,33 @@ describe("DOMNode", () => {
it("cacheRemove() should remove value from cache", () => {
expect.assertions(4);
const a = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
a.cacheSet("foo", 1);
expect(a.cacheExists("foo")).toBeTruthy();
expect(a.cacheGet("foo")).toEqual(1);
a.cacheRemove("foo");
expect(a.cacheExists("foo")).toBeFalsy();
expect(a.cacheGet("foo")).toBeUndefined();
const node = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
node.cacheEnable();
node.cacheSet("foo", 1);
expect(node.cacheExists("foo")).toBeTruthy();
expect(node.cacheGet("foo")).toEqual(1);
node.cacheRemove("foo");
expect(node.cacheExists("foo")).toBeFalsy();
expect(node.cacheGet("foo")).toBeUndefined();
});
it("cacheRemove() should return true if value existed", () => {
expect.assertions(3);
const a = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
a.cacheSet("foo", 1);
expect(a.cacheRemove("foo")).toBeTruthy();
expect(a.cacheRemove("foo")).toBeFalsy();
expect(a.cacheRemove("bar")).toBeFalsy();
const node = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
node.cacheEnable();
node.cacheSet("foo", 1);
expect(node.cacheRemove("foo")).toBeTruthy();
expect(node.cacheRemove("foo")).toBeFalsy();
expect(node.cacheRemove("bar")).toBeFalsy();
});
it("cacheExists() should return true if value is cached", () => {
expect.assertions(2);
const a = new DOMNode(NodeType.ELEMENT_NODE, "a", location);
a.cacheSet("foo", 1);
expect(a.cacheExists("foo")).toBeTruthy();
expect(a.cacheExists("bar")).toBeFalsy();
const node = new DOMNode(NodeType.ELEMENT_NODE, "div", location);
node.cacheEnable();
node.cacheSet("foo", 1);
expect(node.cacheExists("foo")).toBeTruthy();
expect(node.cacheExists("bar")).toBeFalsy();
});
});
});
......@@ -28,7 +28,7 @@ export class DOMNode {
public readonly location: Location;
public readonly unique: DOMInternalID;
private readonly cache: Map<string | number | symbol, any>;
private cache: null | Map<string | number | symbol, any>;
/**
* Set of disabled rules for this node.
......@@ -52,18 +52,34 @@ export class DOMNode {
this.disabledRules = new Set();
this.childNodes = [];
this.unique = counter++;
this.cache = null;
}
/**
* Enable cache for this node.
*
* Should not be called before the node and all children are fully constructed.
*/
public cacheEnable(): void {
this.cache = new Map();
}
/**
* Fetch cached value from this DOM node.
*
* Cache is not enabled until `cacheEnable()` is called by [[Parser]] (when
* the element is fully constructed).
*
* @returns value or `undefined` if the value doesn't exist.
*/
public cacheGet<K extends keyof DOMNodeCache>(key: K): DOMNodeCache[K] | undefined;
public cacheGet(key: string | number | symbol): any | undefined;
public cacheGet(key: string | number | symbol): any | undefined {
return this.cache.get(key);
if (this.cache) {
return this.cache.get(key);
} else {
return undefined;
}
}
/**
......@@ -74,17 +90,25 @@ export class DOMNode {
public cacheSet<K extends keyof DOMNodeCache>(key: K, value: DOMNodeCache[K]): DOMNodeCache[K];
public cacheSet<T>(key: string | number | symbol, value: T): T;
public cacheSet<T>(key: string | number | symbol, value: T): T {
this.cache.set(key, value);
if (this.cache) {
this.cache.set(key, value);
}
return value;
}
/**
* Remove a value by key from cache.
*
* @returns `true` if the entry existed and has been removed.
*/
public cacheRemove<K extends keyof DOMNodeCache>(key: K): boolean;
public cacheRemove(key: string | number | symbol): boolean;
public cacheRemove(key: string | number | symbol): boolean {
return this.cache.delete(key);
if (this.cache) {
return this.cache.delete(key);
} else {
return false;
}
}
/**
......@@ -93,7 +117,7 @@ export class DOMNode {
public cacheExists<K extends keyof DOMNodeCache>(key: K): boolean;
public cacheExists(key: string | number | symbol): boolean;
public cacheExists(key: string | number | symbol): boolean {
return this.cache.has(key);
return Boolean(this.cache && this.cache.has(key));
}
/**
......@@ -104,6 +128,7 @@ export class DOMNode {
if (cached) {
return cached;
}
const text = this.childNodes.map((node) => node.textContent).join("");
this.cacheSet(TEXT_CONTENT, text);
return text;
......
......@@ -127,7 +127,7 @@ export class Parser {
it = this.next(tokenStream);
}
/* resolve and dynamic meta element properties */
/* resolve any dynamic meta element properties */
this.dom.resolveMeta(this.metaTable);
/* trigger any rules waiting for DOM ready */
......@@ -272,6 +272,9 @@ export class Parser {
}
private processElement(node: HtmlElement, source: Source): void {
/* enable cache on node now that it is fully constructed */
node.cacheEnable();
if (source.hooks && source.hooks.processElement) {
const processElement = source.hooks.processElement;
const metaTable = this.metaTable;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment