15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// found in the LICENSE file. 45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/** 65f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @fileoverview Provides a system for memoizing computations applied to 75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * DOM nodes within the same call stack. 85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * To make a function memoizable - suppose you have a function 105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * isAccessible that takes a node and returns a boolean: 115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * function isAccessible(node) { 135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * return expensiveComputation(node); 145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * } 155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Make it memoizable like this: 175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * function isAccessible(node) { 195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * return cvox.Memoize.memoize(computeIsAccessible_, 'isAccessible', node); 205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * } 215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * function computeIsAccessible_(node) { 235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * return expensiveComputation(node); 245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * } 255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * To take advantage of memoization, you need to wrap a sequence of 275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * computations in a call to memoize.scope() - memoization is only 285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * enabled while in that scope, and all cached data is thrown away at 295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * the end. You should use this only when you're sure the computation 305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * being memoized will not change within the scope. 315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * cvox.Memoize.scope(function() { 335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * console.log(isAccessible(document.body)); 345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * }); 355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)goog.provide('cvox.Memoize'); 405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/** 435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Create the namespace. 445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @constructor 455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize = function() { 475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)}; 485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/** 505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * The cache: a map from string function name to a WeakMap from DOM node 515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * to function result. This variable is null when we're out of scope, and it's 525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * a map from string to WeakMap to result when we're in scope. 535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @type {?Object.<string, WeakMap.<Node, *> >} 555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @private 565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize.nodeMap_ = null; 585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/** 605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Keeps track of how many nested times scope() has been called. 615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @type {number} 625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @private 635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize.scopeCount_ = 0; 655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/** 685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Enables memoization within the scope of the given function. You should 695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * ensure that the DOM is not modified within this scope. 705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * It's safe to nest calls to scope. The nested calls have 725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * no effect, only the outermost one. 735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @param {Function} functionScope The function to call with memoization 755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * enabled. 765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @return {*} The value returned by |functionScope|. 775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize.scope = function(functionScope) { 795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) var result; 805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) try { 815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) cvox.Memoize.scopeCount_++; 825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (cvox.Memoize.scopeCount_ == 1) { 835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) cvox.Memoize.nodeMap_ = {}; 845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) result = functionScope(); 865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } finally { 875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) cvox.Memoize.scopeCount_--; 885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (cvox.Memoize.scopeCount_ == 0) { 895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) cvox.Memoize.nodeMap_ = null; 905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return result; 935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)}; 945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/** 965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Memoizes the result of a function call, so if you call this again 975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * with the same exact parameters and memoization is currently enabled 985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * (via a call to scope()), the second time the cached result 995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * will just be returned directly. 1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * 1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @param {Function} functionClosure The function to call and cache the 1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * result of. 1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @param {string} functionName The name of the function you're calling. 1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * This string is used to store and retrieve the cached result, so 1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * it should be unique. If the function to be memoized takes simple 1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * arguments in addition to a DOM node, you can incorporate those 1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * arguments into the function name. 1085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @param {Node} node The DOM node that should be passed as the argument 1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * to the function. 1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @return {*} The return value of |functionClosure|. 1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */ 1125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize.memoize = function(functionClosure, functionName, node) { 1135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (cvox.Memoize.nodeMap_ && 1145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) cvox.Memoize.nodeMap_[functionName] === undefined) { 1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) cvox.Memoize.nodeMap_[functionName] = new WeakMap(); 1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) // If we're not in scope, just call the function directly. 1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (!cvox.Memoize.nodeMap_) { 1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return functionClosure(node); 1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) var result = cvox.Memoize.nodeMap_[functionName].get(node); 1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (result === undefined) { 1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) result = functionClosure(node); 1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (result === undefined) { 1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) throw 'A memoized function cannot return undefined.'; 1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) cvox.Memoize.nodeMap_[functionName].set(node, result); 1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) } 1315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) return result; 1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)}; 134