1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @fileoverview Provides a system for memoizing computations applied to 7 * DOM nodes within the same call stack. 8 * 9 * To make a function memoizable - suppose you have a function 10 * isAccessible that takes a node and returns a boolean: 11 * 12 * function isAccessible(node) { 13 * return expensiveComputation(node); 14 * } 15 * 16 * Make it memoizable like this: 17 * 18 * function isAccessible(node) { 19 * return cvox.Memoize.memoize(computeIsAccessible_, 'isAccessible', node); 20 * } 21 * 22 * function computeIsAccessible_(node) { 23 * return expensiveComputation(node); 24 * } 25 * 26 * To take advantage of memoization, you need to wrap a sequence of 27 * computations in a call to memoize.scope() - memoization is only 28 * enabled while in that scope, and all cached data is thrown away at 29 * the end. You should use this only when you're sure the computation 30 * being memoized will not change within the scope. 31 * 32 * cvox.Memoize.scope(function() { 33 * console.log(isAccessible(document.body)); 34 * }); 35 * 36 */ 37 38 39 goog.provide('cvox.Memoize'); 40 41 42 /** 43 * Create the namespace. 44 * @constructor 45 */ 46 cvox.Memoize = function() { 47 }; 48 49 /** 50 * The cache: a map from string function name to a WeakMap from DOM node 51 * to function result. This variable is null when we're out of scope, and it's 52 * a map from string to WeakMap to result when we're in scope. 53 * 54 * @type {?Object.<string, WeakMap.<Node, *> >} 55 * @private 56 */ 57 cvox.Memoize.nodeMap_ = null; 58 59 /** 60 * Keeps track of how many nested times scope() has been called. 61 * @type {number} 62 * @private 63 */ 64 cvox.Memoize.scopeCount_ = 0; 65 66 67 /** 68 * Enables memoization within the scope of the given function. You should 69 * ensure that the DOM is not modified within this scope. 70 * 71 * It's safe to nest calls to scope. The nested calls have 72 * no effect, only the outermost one. 73 * 74 * @param {Function} functionScope The function to call with memoization 75 * enabled. 76 * @return {*} The value returned by |functionScope|. 77 */ 78 cvox.Memoize.scope = function(functionScope) { 79 var result; 80 try { 81 cvox.Memoize.scopeCount_++; 82 if (cvox.Memoize.scopeCount_ == 1) { 83 cvox.Memoize.nodeMap_ = {}; 84 } 85 result = functionScope(); 86 } finally { 87 cvox.Memoize.scopeCount_--; 88 if (cvox.Memoize.scopeCount_ == 0) { 89 cvox.Memoize.nodeMap_ = null; 90 } 91 } 92 return result; 93 }; 94 95 /** 96 * Memoizes the result of a function call, so if you call this again 97 * with the same exact parameters and memoization is currently enabled 98 * (via a call to scope()), the second time the cached result 99 * will just be returned directly. 100 * 101 * @param {Function} functionClosure The function to call and cache the 102 * result of. 103 * @param {string} functionName The name of the function you're calling. 104 * This string is used to store and retrieve the cached result, so 105 * it should be unique. If the function to be memoized takes simple 106 * arguments in addition to a DOM node, you can incorporate those 107 * arguments into the function name. 108 * @param {Node} node The DOM node that should be passed as the argument 109 * to the function. 110 * @return {*} The return value of |functionClosure|. 111 */ 112 cvox.Memoize.memoize = function(functionClosure, functionName, node) { 113 if (cvox.Memoize.nodeMap_ && 114 cvox.Memoize.nodeMap_[functionName] === undefined) { 115 cvox.Memoize.nodeMap_[functionName] = new WeakMap(); 116 } 117 118 // If we're not in scope, just call the function directly. 119 if (!cvox.Memoize.nodeMap_) { 120 return functionClosure(node); 121 } 122 123 var result = cvox.Memoize.nodeMap_[functionName].get(node); 124 if (result === undefined) { 125 result = functionClosure(node); 126 if (result === undefined) { 127 throw 'A memoized function cannot return undefined.'; 128 } 129 cvox.Memoize.nodeMap_[functionName].set(node, result); 130 } 131 132 return result; 133 }; 134