Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | 94x 94x 94x 94x 6196x 6196x 94x 1756x 1756x 94x 243x 94x 1x 94x 94x 2068x 5345x 94x 94x 188x 372x 94x 94x 94x 94x 7952x 7952x 1715x 1715x 94x 2422x 1549x 1549x 2422x 2408x 1278x 1278x 2408x 711x 1697x 94x 2827x 7902x 7902x | /** * @module Utils * * @categoryDescription DOM: Preventing layout trashing * * {@link waitForMeasureTime} allows you to schedule tasks that read or * "measure", the DOM, for example getting computed styles, taking the * `offsetWidth` or the `scrollTop` of an element, etc... anything that _would_ * force a layout if it runs after the layout has been invalidated by a * "mutation". * * See https://gist.github.com/paulirish/5d52fb081b3570c81e3 for a list of * operations that should be run on a valid layout to avoid forced layouts. * * {@link waitForMutateTime} allows you to schedule tasks that invalidate the * DOM layout by making changes to the style, inserting or removing elements, * etc. * * These ensure that: * - All mutation tasks that would invalidate the style run together before the * next repaint. * - All measurement tasks that need a valid style will run as soon as possible * after the next repaint. * - If a mutation task is scheduled by another mutation task, it will run in * the same batch. * - If a measurement task is scheduled by either a mutation or another * measurement task, it will run in the same batch. */ import * as MH from "@lisn/globals/minification-helpers"; import { logError } from "@lisn/utils/log"; import { scheduleHighPriorityTask } from "@lisn/utils/tasks"; /** * Returns a Promise that is resolved before the next repaint. * * @category DOM: Preventing layout trashing */ export const waitForMutateTime = () => MH.newPromise<void>((resolve) => { scheduleDOMTask(scheduledDOMMutations, resolve); }); /** * Returns a Promise that is resolved as soon as possible after the next * repaint. * * @category DOM: Preventing layout trashing */ export const waitForMeasureTime = () => MH.newPromise<void>((resolve) => { scheduleDOMTask(scheduledDOMMeasurements, resolve); }); /** * Returns a Promise that is resolved before the repaint that follows the next * repaint. * * @category DOM: Preventing layout trashing */ export const waitForSubsequentMutateTime = () => waitForMutateTime().then(waitForMeasureTime).then(waitForMutateTime); /** * Returns a Promise that is resolved as soon as possible after the repaint * that follows the next repaint. * * @category DOM: Preventing layout trashing */ export const waitForSubsequentMeasureTime = () => waitForMeasureTime().then(waitForMutateTime).then(waitForMeasureTime); /** * @ignore * @internal * * @since v1.2.0 */ export const asyncMutatorFor = <Args extends unknown[], Ret>(func: (...args: Args) => Ret) => async (...args: Args) => waitForMutateTime().then(() => func(...args)); /** * @ignore * @internal * * @since v1.2.0 */ export const asyncMeasurerFor = <Args extends unknown[], Ret>(func: (...args: Args) => Ret) => async (...args: Args) => waitForMeasureTime().then(() => func(...args)); // ---------------------------------------- type TaskResolver = () => void; type DOMTaskQueue = TaskResolver[]; const scheduledDOMMeasurements: DOMTaskQueue = []; const scheduledDOMMutations: DOMTaskQueue = []; let hasScheduledDOMTasks = false; const scheduleDOMTask = (queue: DOMTaskQueue, resolve: TaskResolver) => { queue.push(resolve); if (!hasScheduledDOMTasks) { hasScheduledDOMTasks = true; MH.onAnimationFrame(runAllDOMTasks); } }; const runAllDOMTasks = async () => { // We suspend (await null) after each queue to ensure that microtasks that // have been added by await waitFor* or waitFor*().then run before the next // queue, so that if they schedule more measurements and/or mutations, they // can be flushed now, in the same batch. // We're inside an animation frame. Run all mutation tasks now. while (MH.lengthOf(scheduledDOMMutations)) { runDOMTaskQueue(scheduledDOMMutations); // wait for tasks awaiting on the resolved promises, then check queue again await null; } // The measurement queue is now empty => scheduling measurements after // this point will result in rescheduling both queues again in the next // frame. // // Schedule the measurement tasks as soon as possible, after the upcoming // paint. Use a macro task with as high priority as possible. scheduleHighPriorityTask(async () => { while (MH.lengthOf(scheduledDOMMeasurements)) { runDOMTaskQueue(scheduledDOMMeasurements); // wait for tasks awaiting on the resolved promises, then check queue again await null; } if (MH.lengthOf(scheduledDOMMutations)) { // There have been mutations added. Schedule another flush. MH.onAnimationFrame(runAllDOMTasks); } else { hasScheduledDOMTasks = false; } }); }; const runDOMTaskQueue = (queue: DOMTaskQueue) => { let resolve: TaskResolver | undefined; while ((resolve = queue.shift())) { try { resolve(); } catch (err) /* istanbul ignore next */ { logError(err); } } }; |