diff --git a/boot/boot.js b/boot/boot.js index d993499b6..ea20c83fd 100644 --- a/boot/boot.js +++ b/boot/boot.js @@ -142,15 +142,15 @@ $tw.utils.each = function(object,callback) { var next,f,length; if(object) { if(Object.prototype.toString.call(object) == "[object Array]") { - for (f=0, length=object.length; f $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH - 50) { + // For the first fifty transcludes we climb up, we simply collect signatures. + // We're assuming that those first 50 will likely include all transcludes involved in the loop. + error.signatures[transcludeSignature] = true; + } else if(!error.signatures[transcludeSignature]) { + // Now that we're past the first 50, let's look for the first signature that wasn't in the loop. That'll be where we print the error and resume rendering. + this.children = [this.makeChildWidget({type: "error", attributes: { + "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} + }})]; + this.renderChildren(parent,nextSibling); + return; + } + } + throw error; + } }; /* diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index 69f63a684..cb8e5e881 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -12,9 +12,6 @@ Widget base class /*global $tw: false */ "use strict"; -/* Maximum permitted depth of the widget tree for recursion detection */ -var MAX_WIDGET_TREE_DEPTH = 1000; - /* Create a widget object for a parse tree node parseTreeNode: reference to the parse tree node to be rendered @@ -494,10 +491,8 @@ Widget.prototype.makeChildWidgets = function(parseTreeNodes,options) { this.children = []; var self = this; // Check for too much recursion - if(this.getAncestorCount() > MAX_WIDGET_TREE_DEPTH) { - this.children.push(this.makeChildWidget({type: "error", attributes: { - "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")} - }})); + if(this.getAncestorCount() > $tw.utils.TranscludeRecursionError.MAX_WIDGET_TREE_DEPTH) { + throw new $tw.utils.TranscludeRecursionError(); } else { // Create set variable widgets for each variable $tw.utils.each(options.variables,function(value,name) { diff --git a/editions/test/tiddlers/tests/data/transclude/Recursion.tid b/editions/test/tiddlers/tests/data/transclude/Recursion.tid index d75e671eb..b834f3765 100644 --- a/editions/test/tiddlers/tests/data/transclude/Recursion.tid +++ b/editions/test/tiddlers/tests/data/transclude/Recursion.tid @@ -7,7 +7,8 @@ title: Output \whitespace trim <$transclude $tiddler="Output"/> + + title: ExpectedResult -

Recursive transclusion error in transclude widget

\ No newline at end of file +Recursive transclusion error in transclude widget \ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-plugins.js b/editions/test/tiddlers/tests/test-plugins.js index 29ba4a829..663192a9c 100644 --- a/editions/test/tiddlers/tests/test-plugins.js +++ b/editions/test/tiddlers/tests/test-plugins.js @@ -17,7 +17,7 @@ if($tw.node) { describe("Plugin tests", function() { // Get all the plugins as a hashmap by title of a JSON string with the plugin content - var tiddlers = $tw.utils.getAllPlugins(); + var tiddlers = $tw.utils.getAllPlugins({ignoreEnvironmentVariables: true}); // console.log(JSON.stringify(Object.keys(tiddlers),null,4)); describe("every plugin should have the required standard fields", function() { var titles = Object.keys(tiddlers); diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js index 0d1351f31..1c7665a53 100755 --- a/editions/test/tiddlers/tests/test-widget.js +++ b/editions/test/tiddlers/tests/test-widget.js @@ -160,6 +160,47 @@ describe("Widget module", function() { expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget"); }); + it("should handle single-tiddler recursion with branching nodes", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "<$tiddler tiddler='TiddlerOne'><$transclude /> <$transclude />"}, + ]); + // Test parse tree + var parseTreeNode = {type: "widget", children: [ + {type: "transclude", attributes: { + "tiddler": {type: "string", value: "TiddlerOne"} + }} + ]}; + // Construct the widget node + var widgetNode = createWidgetNode(parseTreeNode,wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget Recursive transclusion error in transclude widget"); + }); + + it("should handle many-tiddler recursion with branching nodes", function() { + var wiki = new $tw.Wiki(); + // Add a tiddler + wiki.addTiddlers([ + {title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/> <$transclude tiddler='TiddlerTwo'/>"}, + {title: "TiddlerTwo", text: "<$transclude tiddler='TiddlerOne'/>"} + ]); + // Test parse tree + var parseTreeNode = {type: "widget", children: [ + {type: "transclude", attributes: { + "tiddler": {type: "string", value: "TiddlerOne"} + }} + ]}; + // Construct the widget node + var widgetNode = createWidgetNode(parseTreeNode,wiki); + // Render the widget node to the DOM + var wrapper = renderWidgetNode(widgetNode); + // Test the rendering + expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget"); + }); + it("should deal with SVG elements", function() { var wiki = new $tw.Wiki(); // Construct the widget node diff --git a/languages/zh-Hans/Fields.multids b/languages/zh-Hans/Fields.multids index b406a56ad..50a37b325 100644 --- a/languages/zh-Hans/Fields.multids +++ b/languages/zh-Hans/Fields.multids @@ -30,6 +30,7 @@ name: 具可读性的插件条目的名称 parent-plugin: 对于一个插件,指定其为哪个插件的子插件 plugin-priority: 插件条目的优先级数值 plugin-type: 插件条目的类型 +stability: 插件的开发状态:已弃用、实验性、稳定或旧版 released: TiddlyWiki 的发布日期 revision: 条目存放于服务器中的修订版本 source: 条目的网址 diff --git a/languages/zh-Hant/Fields.multids b/languages/zh-Hant/Fields.multids index a41e8b65e..74e5383a5 100644 --- a/languages/zh-Hant/Fields.multids +++ b/languages/zh-Hant/Fields.multids @@ -30,6 +30,7 @@ name: 具可讀性的套件條目的名稱 parent-plugin: 對於一個插件,指定其為哪個插件的子插件 plugin-priority: 套件條目的優先級數值 plugin-type: 套件條目的類型 +stability: 插件的開發狀態:已棄用、實驗性、穩定或舊版 released: TiddlyWiki 的釋出日期 revision: 條目存放於伺服器中的修訂版本 source: 條目的網址