diff --git a/core/modules/commands/savelibrarytiddlers.js b/core/modules/commands/savelibrarytiddlers.js index d0c6d44fe..44feea071 100644 --- a/core/modules/commands/savelibrarytiddlers.js +++ b/core/modules/commands/savelibrarytiddlers.js @@ -43,7 +43,7 @@ Command.prototype.execute = function() { basepath = this.params[2], skinnyListTitle = this.params[3]; // Get the container tiddler as data - var containerData = self.commander.wiki.getTiddlerData(containerTitle,undefined); + var containerData = self.commander.wiki.getTiddlerDataCached(containerTitle,undefined); if(!containerData) { return "'" + containerTitle + "' is not a tiddler bundle"; } diff --git a/core/modules/commands/unpackplugin.js b/core/modules/commands/unpackplugin.js index abc754bcb..6f85c066f 100644 --- a/core/modules/commands/unpackplugin.js +++ b/core/modules/commands/unpackplugin.js @@ -29,7 +29,7 @@ Command.prototype.execute = function() { } var self = this, title = this.params[0], - pluginData = this.commander.wiki.getTiddlerData(title); + pluginData = this.commander.wiki.getTiddlerDataCached(title); if(!pluginData) { return "Plugin '" + title + "' not found"; } diff --git a/core/modules/filters/indexes.js b/core/modules/filters/indexes.js index 09d1ece9e..3bbb22659 100644 --- a/core/modules/filters/indexes.js +++ b/core/modules/filters/indexes.js @@ -18,7 +18,7 @@ Export our filter function exports.indexes = function(source,operator,options) { var results = []; source(function(tiddler,title) { - var data = options.wiki.getTiddlerData(title); + var data = options.wiki.getTiddlerDataCached(title); if(data) { $tw.utils.pushTop(results,Object.keys(data)); } diff --git a/core/modules/filters/plugintiddlers.js b/core/modules/filters/plugintiddlers.js index abb20f8f9..489ec52e5 100644 --- a/core/modules/filters/plugintiddlers.js +++ b/core/modules/filters/plugintiddlers.js @@ -18,7 +18,7 @@ Export our filter function exports.plugintiddlers = function(source,operator,options) { var results = []; source(function(tiddler,title) { - var pluginInfo = options.wiki.getPluginInfo(title) || options.wiki.getTiddlerData(title,{tiddlers:[]}); + var pluginInfo = options.wiki.getPluginInfo(title) || options.wiki.getTiddlerDataCached(title,{tiddlers:[]}); if(pluginInfo && pluginInfo.tiddlers) { $tw.utils.each(pluginInfo.tiddlers,function(fields,title) { results.push(title); diff --git a/core/modules/storyviews/zoomin.js b/core/modules/storyviews/zoomin.js index f2490ac33..be65b8e3a 100644 --- a/core/modules/storyviews/zoomin.js +++ b/core/modules/storyviews/zoomin.js @@ -18,7 +18,7 @@ var ZoominListView = function(listWidget) { var self = this; this.listWidget = listWidget; // Get the index of the tiddler that is at the top of the history - var history = this.listWidget.wiki.getTiddlerData(this.listWidget.historyTitle,[]), + var history = this.listWidget.wiki.getTiddlerDataCached(this.listWidget.historyTitle,[]), targetTiddler; if(history.length > 0) { targetTiddler = history[history.length-1].title; diff --git a/core/modules/upgraders/themetweaks.js b/core/modules/upgraders/themetweaks.js index 86ddc0f7e..e411b285a 100644 --- a/core/modules/upgraders/themetweaks.js +++ b/core/modules/upgraders/themetweaks.js @@ -44,7 +44,7 @@ exports.upgrade = function(wiki,titles,tiddlers) { var mapping = MAPPINGS[title]; if(mapping) { var tiddler = new $tw.Tiddler(tiddlers[title]), - tiddlerData = wiki.getTiddlerData(tiddler,{}); + tiddlerData = wiki.getTiddlerDataCached(tiddler,{}); for(var index in mapping) { var mappedTitle = mapping[index]; if(!tiddlers[mappedTitle] || tiddlers[mappedTitle].title !== mappedTitle) { diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js index eed24a840..1e08837a5 100644 --- a/core/modules/utils/utils.js +++ b/core/modules/utils/utils.js @@ -171,6 +171,17 @@ exports.extendDeepCopy = function(object,extendedProperties) { return result; }; +exports.deepFreeze = function deepFreeze(object) { + var property, key; + Object.freeze(object); + for(key in object) { + property = object[key]; + if($tw.utils.hop(object,key) && (typeof property === "object") && !Object.isFrozen(property)) { + deepFreeze(property); + } + } +}; + exports.slowInSlowOut = function(t) { return (1 - ((Math.cos(t * Math.PI) + 1) / 2)); }; diff --git a/core/modules/widgets/list.js b/core/modules/widgets/list.js index 7244ee280..4c5b265f2 100755 --- a/core/modules/widgets/list.js +++ b/core/modules/widgets/list.js @@ -156,7 +156,7 @@ Handle any changes to the history list */ ListWidget.prototype.handleHistoryChanges = function() { // Get the history data - var newHistory = this.wiki.getTiddlerData(this.historyTitle,[]); + var newHistory = this.wiki.getTiddlerDataCached(this.historyTitle,[]); // Ignore any entries of the history that match the previous history var entry = 0; while(entry < newHistory.length && entry < this.history.length && newHistory[entry].title === this.history[entry].title) { diff --git a/core/modules/widgets/navigator.js b/core/modules/widgets/navigator.js index 43542f439..967f06ec7 100755 --- a/core/modules/widgets/navigator.js +++ b/core/modules/widgets/navigator.js @@ -539,7 +539,7 @@ NavigatorWidget.prototype.handleImportTiddlersEvent = function(event) { NavigatorWidget.prototype.handlePerformImportEvent = function(event) { var self = this, importTiddler = this.wiki.getTiddler(event.param), - importData = this.wiki.getTiddlerData(event.param,{tiddlers: {}}), + importData = this.wiki.getTiddlerDataCached(event.param,{tiddlers: {}}), importReport = []; // Add the tiddlers to the store importReport.push("The following tiddlers were imported:\n"); diff --git a/core/modules/wiki.js b/core/modules/wiki.js index f4fab675f..ff9d1c360 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -577,7 +577,7 @@ exports.sortByList = function(array,listTitle) { }; exports.getSubTiddler = function(title,subTiddlerTitle) { - var bundleInfo = this.getPluginInfo(title) || this.getTiddlerData(title); + var bundleInfo = this.getPluginInfo(title) || this.getTiddlerDataCached(title); if(bundleInfo && bundleInfo.tiddlers) { var subTiddler = bundleInfo.tiddlers[subTiddlerTitle]; if(subTiddler) { @@ -613,6 +613,29 @@ Other types currently just return null. titleOrTiddler: string tiddler title or a tiddler object defaultData: default data to be returned if the tiddler is missing or doesn't contain data + +Note that the same value is returned for repeated calls for the same tiddler data. The value is frozen to prevent modification; otherwise modifications would be visible to all callers +*/ +exports.getTiddlerDataCached = function(titleOrTiddler,defaultData) { + var self = this, + tiddler = titleOrTiddler; + if(!(tiddler instanceof $tw.Tiddler)) { + tiddler = this.getTiddler(tiddler); + } + if(tiddler) { + return this.getCacheForTiddler(tiddler.fields.title,"data",function() { + // Return the frozen value + var value = self.getTiddlerData(tiddler.fields.title,defaultData); + $tw.utils.deepFreeze(value); + return value; + }); + } else { + return defaultData; + } +}; + +/* +Alternative, uncached version of getTiddlerDataCached(). The return value can be mutated freely and reused */ exports.getTiddlerData = function(titleOrTiddler,defaultData) { var tiddler = titleOrTiddler, @@ -621,20 +644,18 @@ exports.getTiddlerData = function(titleOrTiddler,defaultData) { tiddler = this.getTiddler(tiddler); } if(tiddler && tiddler.fields.text) { - return this.getCacheForTiddler(tiddler.fields.title,"data",function() { - switch(tiddler.fields.type) { - case "application/json": - // JSON tiddler - try { - data = JSON.parse(tiddler.fields.text); - } catch(ex) { - return defaultData; - } - return data; - case "application/x-tiddler-dictionary": - return $tw.utils.parseFields(tiddler.fields.text); - } - }); + switch(tiddler.fields.type) { + case "application/json": + // JSON tiddler + try { + data = JSON.parse(tiddler.fields.text); + } catch(ex) { + return defaultData; + } + return data; + case "application/x-tiddler-dictionary": + return $tw.utils.parseFields(tiddler.fields.text); + } } return defaultData; };