diff --git a/core/language/en-GB/Misc.multids b/core/language/en-GB/Misc.multids
index d8c091375..646336d75 100644
--- a/core/language/en-GB/Misc.multids
+++ b/core/language/en-GB/Misc.multids
@@ -37,6 +37,7 @@ Error/NetworkErrorAlert: `
''Network Error''
It looks like the connection
Error/PutEditConflict: File changed on server
Error/PutForbidden: Permission denied
Error/PutUnauthorized: Authentication required
+Error/RecursiveButton: Possible Recursive Error: Button in button is not allowed
Error/RecursiveTransclusion: Recursive transclusion error in transclude widget
Error/RetrievingSkinny: Error retrieving skinny tiddler list
Error/SavingToTWEdit: Error saving to TWEdit
diff --git a/core/modules/widgets/button.js b/core/modules/widgets/button.js
index 68f2fcd11..913ae7d9c 100644
--- a/core/modules/widgets/button.js
+++ b/core/modules/widgets/button.js
@@ -22,6 +22,23 @@ Inherit from the base widget class
*/
ButtonWidget.prototype = new Widget();
+/*
+Detect nested buttons
+*/
+ButtonWidget.prototype.isNestedButton = function() {
+ var pointer = this.parentWidget,
+ depth = 0;
+ while(pointer) {
+ if(pointer instanceof ButtonWidget) {
+ // we allow 1 nested button
+ if(depth > 1) return true;
+ depth += 1;
+ }
+ pointer = pointer.parentWidget;
+ }
+ return false;
+}
+
/*
Render this widget into the DOM
*/
@@ -34,6 +51,17 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
// Compute attributes and execute state
this.computeAttributes();
this.execute();
+ // Check "button in button". Return early with an error message
+ // This check also prevents fatal recursion errors using the transclusion widget
+ if(this.isNestedButton()) {
+ var domNode = this.document.createElement("span");
+ var textNode = this.document.createTextNode($tw.language.getString("Error/RecursiveButton"));
+ domNode.appendChild(textNode);
+ domNode.className = "tc-error";
+ parent.insertBefore(domNode,nextSibling);
+ this.domNodes.push(domNode);
+ return; // an error message
+ }
// Create element
if(this.buttonTag && $tw.config.htmlUnsafeElements.indexOf(this.buttonTag) === -1) {
tag = this.buttonTag;
@@ -71,7 +99,7 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
if(this["aria-label"]) {
domNode.setAttribute("aria-label",this["aria-label"]);
}
- if (this.role) {
+ if(this.role) {
domNode.setAttribute("role", this.role);
}
if(this.popup || this.popupTitle) {
diff --git a/editions/test/tiddlers/tests/data/transclude/Recursion-Button-Transclusion.tid b/editions/test/tiddlers/tests/data/transclude/Recursion-Button-Transclusion.tid
new file mode 100644
index 000000000..737b7cba2
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Recursion-Button-Transclusion.tid
@@ -0,0 +1,15 @@
+title: Transclude/Recursion/Button
+description: Transclusion recursion inside a button
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$button>
+<$transclude/>
+$button>
++
+title: ExpectedResult
+
+
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Recursion-Button-in-Button.tid b/editions/test/tiddlers/tests/data/transclude/Recursion-Button-in-Button.tid
new file mode 100644
index 000000000..fd1baccd1
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Recursion-Button-in-Button.tid
@@ -0,0 +1,15 @@
+title: Transclude/Recursion/ButtonInButton
+description: Button in Button
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$button>Test Button
+<$button>Second button
+$button>
++
+title: ExpectedResult
+
+Test ButtonSecond button
\ No newline at end of file