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

Lazy extension update #777

Merged
merged 5 commits into from
Apr 6, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ts/core/DOMAdaptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export interface DOMAdaptor<N, T, D> {
getElements(nodes: (string | N | N[])[], document: D): N[];

/**
* Determine if a container node contains a given node is somewhere in its DOM tree
* Determine if a container node contains a given node somewhere in its DOM tree
*
* @param {N} container The container to search
* @param {N|T} node The node to look for
Expand Down
9 changes: 0 additions & 9 deletions ts/core/MathDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,15 +436,6 @@ export interface MathDocument<N, T, D> {
*/
state(state: number, restore?: boolean): MathDocument<N, T, D>;

/**
* Rerender the MathItems on the page
*
* @param {number=} start The state to start rerendering at
* @param {number=} end The state to end rerendering at
* @return {MathDocument} The math document instance
*/
rerender(start?: number, end?: number): MathDocument<N, T, D>;

/**
* Clear the processed values so that the document can be reprocessed
*
Expand Down
216 changes: 200 additions & 16 deletions ts/ui/lazy/LazyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@
*/

import {MathDocumentConstructor, ContainerList} from '../../core/MathDocument.js';
import {MathItem, STATE} from '../../core/MathItem.js';
import {MathItem, STATE, newState} from '../../core/MathItem.js';
import {HTMLMathItem} from '../../handlers/html/HTMLMathItem.js';
import {HTMLDocument} from '../../handlers/html/HTMLDocument.js';
import {HTMLHandler} from '../../handlers/html/HTMLHandler.js';
import {handleRetriesFor} from '../../util/Retries.js';
import {OptionList} from '../../util/Options.js';

/**
* Add the needed function to the window object.
*/
declare const window: {
requestIdleCallback: (callback: () => void) => void;
addEventListener: ((type: string, handler: (event: Event) => void) => void);
matchMedia: (type: string) => {
addListener: (handler: (event: Event) => void) => void;
};
};

/**
Expand Down Expand Up @@ -97,6 +102,8 @@ export class LazyList<N, T, D> {

/*==========================================================================*/

newState('LAZYALWAYS', STATE.FINDMATH + 3);

/**
* The attribute to use for the ID on the marker node
*/
Expand Down Expand Up @@ -244,16 +251,6 @@ export function LazyMathItemMixin<N, T, D, B extends Constructor<HTMLMathItem<N,
}
}

/**
* @override
*/
public state(state: number = undefined, restore: boolean = false) {
//
// don't set the state if we are lazy processing
//
return (restore === null ? this._state : super.state(state, restore));
}

};

}
Expand All @@ -279,6 +276,21 @@ export interface LazyMathDocument<N, T, D> extends HTMLDocument<N, T, D> {
*/
lazyList: LazyList<N, T, D>;

/**
* The containers whose contents should always be typeset
*/
lazyAlwaysContainers: N[];

/**
* A function that will typeset all the remaining expressions (e.g., for printing)
*/
lazyTypesetAll(): Promise<void>;

/**
* Mark the math items that are to be always typeset
*/
lazyAlways(): void;

}

/**
Expand All @@ -299,6 +311,19 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(

return class BaseClass extends BaseDocument {

/**
* @override
*/
public static OPTIONS: OptionList = {
...BaseDocument.OPTIONS,
lazyMargin: '200px',
lazyAlwaysTypeset: null,
renderActions: {
...BaseDocument.OPTIONS.renderActions,
lazyAlways: [STATE.LAZYALWAYS, 'lazyAlways', '', false]
}
};

/**
* The Intersection Observer used to track the appearance of the expression markers
*/
Expand All @@ -309,6 +334,16 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
*/
public lazyList: LazyList<N, T, D>;

/**
* The containers whose contents should always be typeset
*/
public lazyAlwaysContainers: N[] = null;

/**
* Index of last container where math was found in lazyAlwaysContainers
*/
public lazyAlwaysIndex: number = 0;

/**
* A promise to make sure our compiling/typesetting is sequential
*/
Expand All @@ -334,21 +369,155 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
* Augment the MathItem class used for this MathDocument,
* then create the intersection observer and lazy list,
* and bind the lazyProcessSet function to this instance
* so it can be used as a callback more easily.
* so it can be used as a callback more easily. Add the
* event listeners to typeset everything before printing.
*
* @override
* @constructor
*/
constructor(...args: any[]) {
super(...args);
//
// Use the LazyMathItem for math items
//
this.options.MathItem =
LazyMathItemMixin<N, T, D, Constructor<HTMLMathItem<N, T, D>>>(this.options.MathItem);
this.lazyObserver = new IntersectionObserver(this.lazyObserve.bind(this));
//
// Allocate a process bit for lazyAlways
//
const ProcessBits = (this.constructor as typeof HTMLDocument).ProcessBits;
!ProcessBits.has('lazyAlways') && ProcessBits.allocate('lazyAlways');
//
// Set up the lazy observer and other needed data
//
this.lazyObserver = new IntersectionObserver(this.lazyObserve.bind(this), {rootMargin: this.options.lazyMargin});
this.lazyList = new LazyList<N, T, D>();
const callback = this.lazyHandleSet.bind(this);
this.lazyProcessSet = (typeof window !== 'undefined' && window.requestIdleCallback ?
() => window.requestIdleCallback(callback) :
() => setTimeout(callback, 10));
this.lazyProcessSet = (window && window.requestIdleCallback ?
() => window.requestIdleCallback(callback) :
() => setTimeout(callback, 10));
//
// Install print listeners to typeset the rest of the document before printing
//
if (window) {
let done = false;
const handler = () => {
!done && this.lazyTypesetAll();
done = true;
};
window.matchMedia('print').addListener(handler); // for Safari
dpvc marked this conversation as resolved.
Show resolved Hide resolved
window.addEventListener('beforeprint', handler); // for everyone else
}
}

/**
* Check all math items for those that should always be typeset
*/
public lazyAlways() {
if (!this.lazyAlwaysContainers || this.processed.isSet('lazyAlways')) return;
for (const item of this.math) {
const math = item as LazyMathItem<N, T, D>;
if (math.lazyTypeset && this.lazyIsAlways(math)) {
math.lazyCompile = math.lazyTypeset = false;
}
}
this.processed.set('lazyAlways');
}

/**
* Check if the MathItem is in one of the containers to always typeset.
* (start looking using the last container where math was found,
* in case the next math is in the same container).
*
* @param {LazyMathItem<N,T,D>} math The MathItem to test
* @return {boolean} True if one of the document's containers holds the MathItem
*/
protected lazyIsAlways(math: LazyMathItem<N, T, D>): boolean {
if (math.state() < STATE.LAZYALWAYS) {
math.state(STATE.LAZYALWAYS);
const node = math.start.node;
const adaptor = this.adaptor;
const start = this.lazyAlwaysIndex;
const end = this.lazyAlwaysContainers.length;
do {
const container = this.lazyAlwaysContainers[this.lazyAlwaysIndex];
if (adaptor.contains(container, node)) return true;
if (++this.lazyAlwaysIndex >= end) {
this.lazyAlwaysIndex = 0;
}
} while (this.lazyAlwaysIndex !== start);
}
return false;
}

/**
* @override
*/
public state(state: number, restore: boolean = false) {
super.state(state, restore);
if (state < STATE.LAZYALWAYS) {
this.processed.clear('lazyAlways');
}
return this;
}

/**
* Function to typeset all remaining expressions (for printing, etc.)
*
* @return {Promise} Promise that is resolved after the typesetting completes.
*/
public async lazyTypesetAll(): Promise<void> {
//
// The state we need to go back to (COMPILED or TYPESET).
//
let state = STATE.LAST;
//
// Loop through all the math...
//
for (const item of this.math) {
const math = item as LazyMathItem<N, T, D>;
//
// If it is not lazy compile or typeset, skip it.
//
if (!math.lazyCompile && !math.lazyTypeset) continue;
//
// Mark the state that we need to start at.
//
if (math.lazyCompile) {
math.state(STATE.COMPILED - 1);
state = STATE.COMPILED;
} else {
math.state(STATE.TYPESET - 1);
if (STATE.TYPESET < state) state = STATE.TYPESET;
}
//
// Mark it as not lazy and remove it from the observer.
//
math.lazyCompile = math.lazyTypeset = false;
math.lazyMarker && this.lazyObserver.unobserve(math.lazyMarker as any as Element);
}
//
// If something needs updating
//
if (state === STATE.LAST) return Promise.resolve();
//
// Reset the document state to the starting state that we need.
//
this.state(state - 1, null);
//
// Save the SVG font cache and set it to "none" temporarily
// (needed by Firefox, which doesn't seem to process the
// xlinks otherwise).
//
const fontCache = this.outputJax.options.fontCache;
if (fontCache) this.outputJax.options.fontCache = 'none';
//
// Typeset the math and put back the font cache when done.
//
this.reset();
return handleRetriesFor(() => this.render()).then(() => {
if (fontCache) this.outputJax.options.fontCache = fontCache;
});
}

/**
Expand Down Expand Up @@ -495,6 +664,21 @@ B extends MathDocumentConstructor<HTMLDocument<N, T, D>>>(
return items;
}

/**
* @override
*/
public render() {
//
// Get the containers whose content should always be typeset
//
const always = this.options.lazyAlwaysTypeset;
this.lazyAlwaysContainers = !always ? null :
zorkow marked this conversation as resolved.
Show resolved Hide resolved
this.adaptor.getElements(Array.isArray(always) ? always : [always], this.document);
this.lazyAlwaysIndex = 0;
super.render();
return this;
}

};

}
Expand Down