From eb5f75785fd28acb50f1b30434e89223fff00185 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 7 Oct 2022 00:33:04 -0400
Subject: improvements to sizing webpages

---
 src/client/views/nodes/WebBox.tsx | 28 ++++++++++++++++++++--------
 1 file changed, 20 insertions(+), 8 deletions(-)

(limited to 'src/client/views/nodes/WebBox.tsx')

diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 8288810b1..460edb7c2 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -243,6 +243,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         );
     }
     @action componentWillUnmount() {
+        this._iframetimeout && clearTimeout(this._iframetimeout);
+        this._iframetimeout = undefined;
         Object.values(this._disposers).forEach(disposer => disposer?.());
         // this._iframe?.removeEventListener('wheel', this.iframeWheel, true);
         // this._iframe?.contentDocument?.removeEventListener("pointerup", this.iframeUp);
@@ -382,6 +384,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length);
     }
 
+    _iframetimeout: any = undefined;
     @action
     iframeLoaded = (e: any) => {
         const iframe = this._iframe;
@@ -409,16 +412,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             }
             this.submitURL(requrlraw, undefined, true);
         }
-        if (iframe?.contentDocument) {
-            iframe.contentDocument.addEventListener('pointerup', this.iframeUp);
-            iframe.contentDocument.addEventListener('pointerdown', this.iframeDown);
-            this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument.body.scrollHeight);
-            setTimeout(
-                action(() => (this._scrollHeight = Math.max(this._scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0))),
+        const iframeContent = iframe?.contentDocument;
+        if (iframeContent) {
+            iframeContent.addEventListener('pointerup', this.iframeUp);
+            iframeContent.addEventListener('pointerdown', this.iframeDown);
+            const initHeights = () => {
+                this._scrollHeight = Math.max(this._scrollHeight, (iframeContent.body.children[0] as any)?.scrollHeight || 0);
+                if (this._scrollHeight) {
+                    this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight);
+                    this.layoutDoc.height = Math.min(this.layoutDoc[HeightSym](), (this.layoutDoc[WidthSym]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth));
+                }
+            };
+            initHeights();
+            this._iframetimeout && clearTimeout(this._iframetimeout);
+            this._iframetimeout = setTimeout(
+                action(() => initHeights),
                 5000
             );
             iframe.setAttribute('enable-annotation', 'true');
-            iframe.contentDocument.addEventListener(
+            iframeContent.addEventListener(
                 'click',
                 undoBatch(
                     action((e: MouseEvent) => {
@@ -882,7 +894,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 onWheel={StopEvent} // block wheel events from propagating since they're handled by the iframe
                 onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)}
                 onPointerDown={this.onMarqueeDown}>
-                <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered ? NumCast(this.scrollHeight, 50) : '100%', pointerEvents }}>
+                <div className={'webBox-innerContent'} style={{ height: this._webPageHasBeenRendered && this._scrollHeight ? this.scrollHeight : '100%', pointerEvents }}>
                     {this.content}
                     {<div style={{ display: DragManager.docsBeingDragged.length ? 'none' : undefined, mixBlendMode: 'multiply' }}>{renderAnnotations(this.transparentFilter)}</div>}
                     {renderAnnotations(this.opaqueFilter)}
-- 
cgit v1.2.3-70-g09d2


From dd5cfe5302279d708bd8fbc7b9cad7ea082758c4 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 13 Oct 2022 10:39:33 -0400
Subject: some basic error checking.   avoid querying background for non-toggle
 buttons

---
 src/client/util/CurrentUserUtils.ts                |   2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |   2 +-
 src/client/views/nodes/WebBox.tsx                  |  24 ++-
 src/client/views/nodes/WebBoxRenderer.js           | 183 ++++++++++-----------
 .../views/nodes/formattedText/DashDocView.tsx      |   2 +-
 5 files changed, 111 insertions(+), 102 deletions(-)

(limited to 'src/client/views/nodes/WebBox.tsx')

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index eb0812cba..1c9f89fa0 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -689,7 +689,7 @@ export class CurrentUserUtils {
         };
         const reqdFuncs:{[key:string]:any} = {
             ...params.funcs,
-            backgroundColor: params.scripts?.onClick /// a bit hacky.  if onClick is set, then we assume it returns a color value when queried with '_readOnly_'.  This will be true for toggle buttons, but not generally
+            backgroundColor: params.btnType === ButtonType.ToggleButton ? params.scripts?.onClick:undefined /// a bit hacky.  if onClick is set, then we assume it returns a color value when queried with '_readOnly_'.  This will be true for toggle buttons, but not generally
         }
         return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs);
     }
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index a48906372..04c7b96e3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -222,7 +222,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.();
     panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.();
     screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y);
-    focusDoc = (doc: Doc) => this.props.focus(doc);
+    focusDoc = (doc: Doc) => this.props.focus(doc, {});
     returnThis = () => this;
     render() {
         TraceMobx();
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 460edb7c2..db493934a 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -156,6 +156,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
             this.rootDoc.thumbLockout = true; // lock to prevent multiple thumb updates.
             CreateImage(this._webUrl.endsWith('/') ? this._webUrl.substring(0, this._webUrl.length - 1) : this._webUrl, this._iframe.contentDocument?.styleSheets ?? [], htmlString, nativeWidth, nativeHeight, scrollTop)
                 .then((data_url: any) => {
+                    if (data_url.includes('<!DOCTYPE')) {
+                        console.log('BAD DATA IN THUMB CREATION');
+                        return;
+                    }
                     VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + '-icon' + new Date().getTime(), true, this.layoutDoc[Id] + '-icon').then(returnedfilename =>
                         setTimeout(
                             action(() => {
@@ -369,10 +373,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
     iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale;
 
     addStyleSheet(document: any, styleType: string = 'text/css') {
-        const style = document.createElement('style');
-        style.type = styleType;
-        const sheets = document.head.appendChild(style);
-        return (sheets as any).sheet;
+        if (document) {
+            const style = document.createElement('style');
+            style.type = styleType;
+            const sheets = document.head.appendChild(style);
+            return (sheets as any).sheet;
+        }
     }
     addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') {
         const propText =
@@ -381,7 +387,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 : Object.keys(css)
                       .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p]))
                       .join(';');
-        return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length);
+        return sheet?.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length);
     }
 
     _iframetimeout: any = undefined;
@@ -394,7 +400,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
 
         this.addStyleSheetRule(this.addStyleSheet(this._iframe?.contentDocument), '::selection', { color: 'white', background: 'orange' }, '');
 
-        let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
+        let href: Opt<string>;
+        try {
+            href = iframe?.contentWindow?.location.href;
+        } catch (e) {
+            href = undefined;
+        }
+        let requrlraw = decodeURIComponent(href?.replace(Utils.prepend('') + '/corsProxy/', '') ?? this._url.toString());
         if (requrlraw !== this._url.toString()) {
             if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
                 const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g);
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index f3f1bcf5c..cebb94d86 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -1,14 +1,13 @@
 /**
- * 
- * @param {StyleSheetList} styleSheets 
+ *
+ * @param {StyleSheetList} styleSheets
  */
 var ForeignHtmlRenderer = function (styleSheets) {
-
     const self = this;
 
     /**
-     * 
-     * @param {String} binStr 
+     *
+     * @param {String} binStr
      */
     const binaryStringToBase64 = function (binStr) {
         return new Promise(function (resolve) {
@@ -16,7 +15,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
             reader.readAsDataURL(binStr);
             reader.onloadend = function () {
                 resolve(reader.result);
-            }
+            };
         });
     };
 
@@ -24,11 +23,11 @@ var ForeignHtmlRenderer = function (styleSheets) {
         return window.location.origin + extension;
     }
     function CorsProxy(url) {
-        return prepend("/corsProxy/") + encodeURIComponent(url);
+        return prepend('/corsProxy/') + encodeURIComponent(url);
     }
     /**
-     * 
-     * @param {String} url 
+     *
+     * @param {String} url
      * @returns {Promise}
      */
     const getResourceAsBase64 = function (webUrl, inurl) {
@@ -37,35 +36,30 @@ var ForeignHtmlRenderer = function (styleSheets) {
             //const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl;
             //const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl;
             var url = inurl;
-            if (inurl.startsWith("/static")) {
-                url = (new URL(webUrl).origin + inurl);
-            } else
-                if ((inurl.startsWith("/") && !inurl.startsWith("//"))) {
-                    url = CorsProxy(new URL(webUrl).origin + inurl);
-                } else if (!inurl.startsWith("http") && !inurl.startsWith("//")) {
-                    url = CorsProxy(webUrl + "/" + inurl);
-                }
-            xhr.open("GET", url);
+            if (inurl.startsWith('/static')) {
+                url = new URL(webUrl).origin + inurl;
+            } else if (inurl.startsWith('/') && !inurl.startsWith('//')) {
+                url = CorsProxy(new URL(webUrl).origin + inurl);
+            } else if (!inurl.startsWith('http') && !inurl.startsWith('//')) {
+                url = CorsProxy(webUrl + '/' + inurl);
+            }
+            xhr.open('GET', url);
             xhr.responseType = 'blob';
 
             xhr.onreadystatechange = async function () {
                 if (xhr.readyState === 4 && xhr.status === 200) {
                     const resBase64 = await binaryStringToBase64(xhr.response);
 
-                    resolve(
-                        {
-                            "resourceUrl": inurl,
-                            "resourceBase64": resBase64
-                        }
-                    );
+                    resolve({
+                        resourceUrl: inurl,
+                        resourceBase64: resBase64,
+                    });
                 } else if (xhr.readyState === 4) {
-                    console.log("COULDN'T FIND: " + (inurl.startsWith("/") ? webUrl + inurl : inurl));
-                    resolve(
-                        {
-                            "resourceUrl": "",
-                            "resourceBase64": inurl
-                        }
-                    );
+                    console.log("COULDN'T FIND: " + (inurl.startsWith('/') ? webUrl + inurl : inurl));
+                    resolve({
+                        resourceUrl: '',
+                        resourceBase64: inurl,
+                    });
                 }
             };
 
@@ -74,8 +68,8 @@ var ForeignHtmlRenderer = function (styleSheets) {
     };
 
     /**
-     * 
-     * @param {String[]} urls 
+     *
+     * @param {String[]} urls
      * @returns {Promise}
      */
     const getMultipleResourcesAsBase64 = function (webUrl, urls) {
@@ -87,13 +81,13 @@ var ForeignHtmlRenderer = function (styleSheets) {
     };
 
     /**
-     * 
-     * @param {String} str 
-     * @param {Number} startIndex 
-     * @param {String} prefixToken 
+     *
+     * @param {String} str
+     * @param {Number} startIndex
+     * @param {String} prefixToken
      * @param {String[]} suffixTokens
-     * 
-     * @returns {String|null} 
+     *
+     * @returns {String|null}
      */
     const parseValue = function (str, startIndex, prefixToken, suffixTokens) {
         const idx = str.indexOf(prefixToken, startIndex);
@@ -111,17 +105,17 @@ var ForeignHtmlRenderer = function (styleSheets) {
         }
 
         return {
-            "foundAtIndex": idx,
-            "value": val
-        }
+            foundAtIndex: idx,
+            value: val,
+        };
     };
 
     /**
-     * 
-     * @param {String} cssRuleStr 
+     *
+     * @param {String} cssRuleStr
      * @returns {String[]}
      */
-    const getUrlsFromCssString = function (cssRuleStr, selector = "url(", delimiters = [')'], mustEndWithQuote = false) {
+    const getUrlsFromCssString = function (cssRuleStr, selector = 'url(', delimiters = [')'], mustEndWithQuote = false) {
         const urlsFound = [];
         let searchStartIndex = 0;
 
@@ -133,7 +127,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
             searchStartIndex = url.foundAtIndex + url.value.length;
             if (mustEndWithQuote && url.value[url.value.length - 1] !== '"') continue;
             const unquoted = removeQuotes(url.value);
-            if (!unquoted  /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") )*/ || unquoted === 'http://' || unquoted === 'https://') {
+            if (!unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") )*/ || unquoted === 'http://' || unquoted === 'https://') {
                 continue;
             }
 
@@ -144,24 +138,24 @@ var ForeignHtmlRenderer = function (styleSheets) {
     };
 
     /**
-     * 
-     * @param {String} html 
+     *
+     * @param {String} html
      * @returns {String[]}
      */
     const getImageUrlsFromFromHtml = function (html) {
-        return getUrlsFromCssString(html, "src=", [' ', '>', '\t'], true);
+        return getUrlsFromCssString(html, 'src=', [' ', '>', '\t'], true);
     };
     const getSourceUrlsFromFromHtml = function (html) {
-        return getUrlsFromCssString(html, "source=", [' ', '>', '\t'], true);
+        return getUrlsFromCssString(html, 'source=', [' ', '>', '\t'], true);
     };
 
     /**
-     * 
+     *
      * @param {String} str
      * @returns {String}
      */
     const removeQuotes = function (str) {
-        return str.replace(/["']/g, "");
+        return str.replace(/["']/g, '');
     };
 
     const escapeRegExp = function (string) {
@@ -169,37 +163,33 @@ var ForeignHtmlRenderer = function (styleSheets) {
     };
 
     /**
-     * 
-     * @param {String} contentHtml 
+     *
+     * @param {String} contentHtml
      * @param {Number} width
      * @param {Number} height
-     * 
+     *
      * @returns {Promise<String>}
      */
     const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll, xoff) {
-
         return new Promise(async function (resolve, reject) {
-
             /* !! The problems !!
-            *  1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with)
-            *  2. Platform won't wait for external assets to load (fonts, images, etc.)
-            */
+             *  1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with)
+             *  2. Platform won't wait for external assets to load (fonts, images, etc.)
+             */
 
             // copy styles
-            let cssStyles = "";
+            let cssStyles = '';
             let urlsFoundInCss = [];
 
             for (let i = 0; i < styleSheets.length; i++) {
                 try {
-                    const rules = styleSheets[i].cssRules
+                    const rules = styleSheets[i].cssRules;
                     for (let j = 0; j < rules.length; j++) {
                         const cssRuleStr = rules[j].cssText;
                         urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr));
                         cssStyles += cssRuleStr;
                     }
-                } catch (e) {
-
-                }
+                } catch (e) {}
             }
 
             // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss);
@@ -210,30 +200,32 @@ var ForeignHtmlRenderer = function (styleSheets) {
             //     }
             // }
 
-            contentHtml = contentHtml.replace(/<source[^>]*>/g, "") // <picture> tags have a <source> which has a srcset field of image refs.  instead of converting each, just use the default <img> of the picture
-                .replace(/noscript/g, "div").replace(/<div class="mediaset"><\/div>/g, "")  // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s.  But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
-                .replace(/<link[^>]*>/g, "")  // don't need to keep any linked style sheets because we've already processed all style sheets above
-                .replace(/srcset="([^ "]*)[^"]*"/g, "src=\"$1\""); // instead of converting each item in the srcset to a data url, just convert the first one and use that
-            let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith("data:"));
+            contentHtml = contentHtml
+                .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs.  instead of converting each, just use the default <img> of the picture
+                .replace(/noscript/g, 'div')
+                .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s.  But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag
+                .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above
+                .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that
+            let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:'));
             const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : [];
             for (let i = 0; i < fetchedResources.length; i++) {
                 const r = fetchedResources[i];
                 if (r.resourceUrl) {
-                    contentHtml = contentHtml.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64);
+                    contentHtml = contentHtml.replace(new RegExp(escapeRegExp(r.resourceUrl), 'g'), r.resourceBase64);
                 }
             }
 
-            const styleElem = document.createElement("style");
-            styleElem.innerHTML = cssStyles.replace("&gt;", ">").replace("&lt;", "<");
+            const styleElem = document.createElement('style');
+            styleElem.innerHTML = cssStyles.replace('&gt;', '>').replace('&lt;', '<');
 
-            const styleElemString = new XMLSerializer().serializeToString(styleElem).replace(/&gt;/g, ">").replace(/&lt;/g, "<");
+            const styleElemString = new XMLSerializer().serializeToString(styleElem).replace(/&gt;/g, '>').replace(/&lt;/g, '<');
 
             // create DOM element string that encapsulates styles + content
-            const contentRootElem = document.createElement("body");
-            contentRootElem.style.zIndex = "1111";
+            const contentRootElem = document.createElement('body');
+            contentRootElem.style.zIndex = '1111';
             // contentRootElem.style.transform = "scale(0.08)"
             contentRootElem.innerHTML = styleElemString + contentHtml;
-            contentRootElem.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
+            contentRootElem.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
             //document.body.appendChild(contentRootElem);
 
             const contentRootElemString = new XMLSerializer().serializeToString(contentRootElem);
@@ -256,17 +248,17 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * @param {String} html
      * @param {Number} width
      * @param {Number} height
-     * 
+     *
      * @return {Promise<Image>}
      */
     this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) {
         return new Promise(async function (resolve, reject) {
             const img = new Image();
-            console.log("BUILDING SVG for:" + webUrl);
+            console.log('BUILDING SVG for:' + webUrl);
             img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff);
 
             img.onload = function () {
-                console.log("IMAGE SVG created:" + webUrl);
+                console.log('IMAGE SVG created:' + webUrl);
                 resolve(img);
             };
         });
@@ -276,7 +268,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * @param {String} html
      * @param {Number} width
      * @param {Number} height
-     * 
+     *
      * @return {Promise<Image>}
      */
     this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) {
@@ -298,7 +290,7 @@ var ForeignHtmlRenderer = function (styleSheets) {
      * @param {String} html
      * @param {Number} width
      * @param {Number} height
-     * 
+     *
      * @return {Promise<String>}
      */
     this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) {
@@ -307,24 +299,30 @@ var ForeignHtmlRenderer = function (styleSheets) {
             resolve(canvas.toDataURL('image/png'));
         });
     };
-
 };
 
-
 export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xoff = 0, oversample = 1) {
-    const val = (new ForeignHtmlRenderer(styleSheets)).renderToBase64Png(webUrl, html.replace(/docView-hack/g, 'documentView-hack').replace(/\n/g, "").replace(/<script((?!\/script).)*<\/script>/g, ""), width, height, scroll, xoff, oversample);
-    return val;
+    return new ForeignHtmlRenderer(styleSheets).renderToBase64Png(
+        webUrl,
+        html
+            .replace(/docView-hack/g, 'documentView-hack')
+            .replace(/\n/g, '')
+            .replace(/<script((?!\/script).)*<\/script>/g, ''),
+        width,
+        height,
+        scroll,
+        xoff,
+        oversample
+    );
 }
 
-
-
-var ClipboardUtils = new function () {
+var ClipboardUtils = new (function () {
     var permissions = {
         'image/bmp': true,
         'image/gif': true,
         'image/png': true,
         'image/jpeg': true,
-        'image/tiff': true
+        'image/tiff': true,
     };
 
     function getType(types) {
@@ -387,9 +385,8 @@ var ClipboardUtils = new function () {
             callback(null, 'Clipboard is not supported.');
         }
     };
-};
-
+})();
 
 export function pasteImageBitmap(callback) {
     return ClipboardUtils.readImage(callback);
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx
index 73a711b9d..5f576be41 100644
--- a/src/client/views/nodes/formattedText/DashDocView.tsx
+++ b/src/client/views/nodes/formattedText/DashDocView.tsx
@@ -149,7 +149,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> {
         const { scale, translateX, translateY } = Utils.GetScreenTransform(this._spanRef.current);
         return new Transform(-translateX, -translateY, 1).scale(1 / scale);
     };
-    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document); // ideally, this would scroll to show the focus target
+    outerFocus = (target: Doc) => this._textBox.props.focus(this._textBox.props.Document, {}); // ideally, this would scroll to show the focus target
 
     onKeyDown = (e: any) => {
         e.stopPropagation();
-- 
cgit v1.2.3-70-g09d2

