Fossil SCM
/chat: be more restrictive in what mimetypes we enable embedding for because loading an iframe with an arbitrary mimetype might force the browser to prompt to download the content. Leave the Embed toggle enabled, even during loading, to avoid that such a download prompt leaves the toggle permanently disabled. That latter bit required some refactoring of the auto-iframe-resize to account for toggling while the content is still in transit.
Commit
0423fb8d7b2a6423cc50799f816a7695130b526e0028814c3d7f685441cbe2a2
Parent
cead9178c8c9007…
1 file changed
+43
-17
+43
-17
| --- src/fossil.page.chat.js | ||
| +++ src/fossil.page.chat.js | ||
| @@ -894,18 +894,46 @@ | ||
| 894 | 894 | }; |
| 895 | 895 | |
| 896 | 896 | const canEmbedFile = function f(msg){ |
| 897 | 897 | if(!f.$rx){ |
| 898 | 898 | f.$rx = /\.((html?)|(txt))$/i; |
| 899 | + f.$specificTypes = [ | |
| 900 | + 'text/plain', | |
| 901 | + 'text/html' | |
| 902 | + // add more as we discover which ones Firefox won't | |
| 903 | + // force the user to try to download. | |
| 904 | + ]; | |
| 899 | 905 | } |
| 900 | 906 | if(msg.fmime){ |
| 901 | - return (msg.fmime.startsWith("text/") | |
| 902 | - || msg.fmime.startsWith("image/")); | |
| 907 | + return (msg.fmime.startsWith("image/") | |
| 908 | + || f.$specificTypes.indexOf(msg.fmime)>=0); | |
| 903 | 909 | } |
| 904 | 910 | return msg.fname && f.$rx.test(msg.fname); |
| 905 | 911 | }; |
| 906 | 912 | |
| 913 | + const adjustIFrameSize = function(msgObj){ | |
| 914 | + const iframe = msgObj.e.iframe; | |
| 915 | + const body = iframe.contentWindow.document.querySelector('body'); | |
| 916 | + if(body && !body.style.fontSize){ | |
| 917 | + /** _Attempt_ to force the iframe to inherit the message's text size | |
| 918 | + if the body has no explicit size set. On desktop systems | |
| 919 | + the size is apparently being inherited in that case, but on mobile | |
| 920 | + not. */ | |
| 921 | + body.style.fontSize = window.getComputedStyle(msgObj.e.content); | |
| 922 | + } | |
| 923 | + if('' === iframe.style.maxHeight){ | |
| 924 | + /* Resize iframe height to fit the content. Workaround: if we | |
| 925 | + adjust the iframe height while it's hidden then its height | |
| 926 | + is 0, so we must briefly unhide it. */ | |
| 927 | + const isHidden = iframe.classList.contains('hidden'); | |
| 928 | + if(isHidden) D.removeClass(iframe, 'hidden'); | |
| 929 | + iframe.style.maxHeight = iframe.style.height | |
| 930 | + = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; | |
| 931 | + if(isHidden) D.addClass(iframe, 'hidden'); | |
| 932 | + } | |
| 933 | + }; | |
| 934 | + | |
| 907 | 935 | cf.prototype = { |
| 908 | 936 | scrollIntoView: function(){ |
| 909 | 937 | this.e.content.scrollIntoView(); |
| 910 | 938 | }, |
| 911 | 939 | setMessage: function(m){ |
| @@ -969,32 +997,30 @@ | ||
| 969 | 997 | const embedTarget = this.e.content; |
| 970 | 998 | const self = this; |
| 971 | 999 | const btnEmbed = D.attr(D.checkbox("1", false), 'id', |
| 972 | 1000 | 'embed-'+ds.msgid); |
| 973 | 1001 | const btnLabel = D.label(btnEmbed, "Embed"); |
| 1002 | + /* Maintenance reminder: do not disable the toggle | |
| 1003 | + button while the content is loading because that will | |
| 1004 | + cause it to get stuck in disabled mode if the browser | |
| 1005 | + decides that loading the content should prompt the | |
| 1006 | + user to download it, rather than embed it in the | |
| 1007 | + iframe. */ | |
| 974 | 1008 | btnEmbed.addEventListener('change',function(){ |
| 975 | 1009 | if(self.e.iframe){ |
| 976 | - if(btnEmbed.checked) D.removeClass(self.e.iframe, 'hidden'); | |
| 1010 | + if(btnEmbed.checked){ | |
| 1011 | + D.removeClass(self.e.iframe, 'hidden'); | |
| 1012 | + if(self.e.$iframeLoaded) adjustIFrameSize(self); | |
| 1013 | + } | |
| 977 | 1014 | else D.addClass(self.e.iframe, 'hidden'); |
| 978 | 1015 | return; |
| 979 | 1016 | } |
| 980 | - D.disable(btnEmbed); | |
| 981 | 1017 | const iframe = self.e.iframe = document.createElement('iframe'); |
| 982 | - D.append(embedTarget, iframe); | |
| 1018 | + D.append(embedTarget, iframe); | |
| 983 | 1019 | iframe.addEventListener('load', function(){ |
| 984 | - D.enable(btnEmbed); | |
| 985 | - const body = iframe.contentWindow.document.querySelector('body'); | |
| 986 | - if(body && !body.style.fontSize){ | |
| 987 | - /** _Attempt_ to force the iframe to inherit the message's text size | |
| 988 | - if the body has no explicit size set. On desktop systems | |
| 989 | - the size is apparently being inherited in that case, but on mobile | |
| 990 | - not. */ | |
| 991 | - const cs = window.getComputedStyle(self.e.content); | |
| 992 | - body.style.fontSize = cs.fontSize; | |
| 993 | - } | |
| 994 | - iframe.style.maxHeight = iframe.style.height | |
| 995 | - = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; | |
| 1020 | + self.e.$iframeLoaded = true; | |
| 1021 | + adjustIFrameSize(self); | |
| 996 | 1022 | }); |
| 997 | 1023 | iframe.setAttribute('src', downloadUri); |
| 998 | 1024 | }); |
| 999 | 1025 | D.append(w, btnEmbed, btnLabel); |
| 1000 | 1026 | } |
| 1001 | 1027 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -894,18 +894,46 @@ | |
| 894 | }; |
| 895 | |
| 896 | const canEmbedFile = function f(msg){ |
| 897 | if(!f.$rx){ |
| 898 | f.$rx = /\.((html?)|(txt))$/i; |
| 899 | } |
| 900 | if(msg.fmime){ |
| 901 | return (msg.fmime.startsWith("text/") |
| 902 | || msg.fmime.startsWith("image/")); |
| 903 | } |
| 904 | return msg.fname && f.$rx.test(msg.fname); |
| 905 | }; |
| 906 | |
| 907 | cf.prototype = { |
| 908 | scrollIntoView: function(){ |
| 909 | this.e.content.scrollIntoView(); |
| 910 | }, |
| 911 | setMessage: function(m){ |
| @@ -969,32 +997,30 @@ | |
| 969 | const embedTarget = this.e.content; |
| 970 | const self = this; |
| 971 | const btnEmbed = D.attr(D.checkbox("1", false), 'id', |
| 972 | 'embed-'+ds.msgid); |
| 973 | const btnLabel = D.label(btnEmbed, "Embed"); |
| 974 | btnEmbed.addEventListener('change',function(){ |
| 975 | if(self.e.iframe){ |
| 976 | if(btnEmbed.checked) D.removeClass(self.e.iframe, 'hidden'); |
| 977 | else D.addClass(self.e.iframe, 'hidden'); |
| 978 | return; |
| 979 | } |
| 980 | D.disable(btnEmbed); |
| 981 | const iframe = self.e.iframe = document.createElement('iframe'); |
| 982 | D.append(embedTarget, iframe); |
| 983 | iframe.addEventListener('load', function(){ |
| 984 | D.enable(btnEmbed); |
| 985 | const body = iframe.contentWindow.document.querySelector('body'); |
| 986 | if(body && !body.style.fontSize){ |
| 987 | /** _Attempt_ to force the iframe to inherit the message's text size |
| 988 | if the body has no explicit size set. On desktop systems |
| 989 | the size is apparently being inherited in that case, but on mobile |
| 990 | not. */ |
| 991 | const cs = window.getComputedStyle(self.e.content); |
| 992 | body.style.fontSize = cs.fontSize; |
| 993 | } |
| 994 | iframe.style.maxHeight = iframe.style.height |
| 995 | = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; |
| 996 | }); |
| 997 | iframe.setAttribute('src', downloadUri); |
| 998 | }); |
| 999 | D.append(w, btnEmbed, btnLabel); |
| 1000 | } |
| 1001 |
| --- src/fossil.page.chat.js | |
| +++ src/fossil.page.chat.js | |
| @@ -894,18 +894,46 @@ | |
| 894 | }; |
| 895 | |
| 896 | const canEmbedFile = function f(msg){ |
| 897 | if(!f.$rx){ |
| 898 | f.$rx = /\.((html?)|(txt))$/i; |
| 899 | f.$specificTypes = [ |
| 900 | 'text/plain', |
| 901 | 'text/html' |
| 902 | // add more as we discover which ones Firefox won't |
| 903 | // force the user to try to download. |
| 904 | ]; |
| 905 | } |
| 906 | if(msg.fmime){ |
| 907 | return (msg.fmime.startsWith("image/") |
| 908 | || f.$specificTypes.indexOf(msg.fmime)>=0); |
| 909 | } |
| 910 | return msg.fname && f.$rx.test(msg.fname); |
| 911 | }; |
| 912 | |
| 913 | const adjustIFrameSize = function(msgObj){ |
| 914 | const iframe = msgObj.e.iframe; |
| 915 | const body = iframe.contentWindow.document.querySelector('body'); |
| 916 | if(body && !body.style.fontSize){ |
| 917 | /** _Attempt_ to force the iframe to inherit the message's text size |
| 918 | if the body has no explicit size set. On desktop systems |
| 919 | the size is apparently being inherited in that case, but on mobile |
| 920 | not. */ |
| 921 | body.style.fontSize = window.getComputedStyle(msgObj.e.content); |
| 922 | } |
| 923 | if('' === iframe.style.maxHeight){ |
| 924 | /* Resize iframe height to fit the content. Workaround: if we |
| 925 | adjust the iframe height while it's hidden then its height |
| 926 | is 0, so we must briefly unhide it. */ |
| 927 | const isHidden = iframe.classList.contains('hidden'); |
| 928 | if(isHidden) D.removeClass(iframe, 'hidden'); |
| 929 | iframe.style.maxHeight = iframe.style.height |
| 930 | = iframe.contentWindow.document.documentElement.scrollHeight + 'px'; |
| 931 | if(isHidden) D.addClass(iframe, 'hidden'); |
| 932 | } |
| 933 | }; |
| 934 | |
| 935 | cf.prototype = { |
| 936 | scrollIntoView: function(){ |
| 937 | this.e.content.scrollIntoView(); |
| 938 | }, |
| 939 | setMessage: function(m){ |
| @@ -969,32 +997,30 @@ | |
| 997 | const embedTarget = this.e.content; |
| 998 | const self = this; |
| 999 | const btnEmbed = D.attr(D.checkbox("1", false), 'id', |
| 1000 | 'embed-'+ds.msgid); |
| 1001 | const btnLabel = D.label(btnEmbed, "Embed"); |
| 1002 | /* Maintenance reminder: do not disable the toggle |
| 1003 | button while the content is loading because that will |
| 1004 | cause it to get stuck in disabled mode if the browser |
| 1005 | decides that loading the content should prompt the |
| 1006 | user to download it, rather than embed it in the |
| 1007 | iframe. */ |
| 1008 | btnEmbed.addEventListener('change',function(){ |
| 1009 | if(self.e.iframe){ |
| 1010 | if(btnEmbed.checked){ |
| 1011 | D.removeClass(self.e.iframe, 'hidden'); |
| 1012 | if(self.e.$iframeLoaded) adjustIFrameSize(self); |
| 1013 | } |
| 1014 | else D.addClass(self.e.iframe, 'hidden'); |
| 1015 | return; |
| 1016 | } |
| 1017 | const iframe = self.e.iframe = document.createElement('iframe'); |
| 1018 | D.append(embedTarget, iframe); |
| 1019 | iframe.addEventListener('load', function(){ |
| 1020 | self.e.$iframeLoaded = true; |
| 1021 | adjustIFrameSize(self); |
| 1022 | }); |
| 1023 | iframe.setAttribute('src', downloadUri); |
| 1024 | }); |
| 1025 | D.append(w, btnEmbed, btnLabel); |
| 1026 | } |
| 1027 |