From e01287f8cb5f15b933e03abeffe7c78ae30611ad Mon Sep 17 00:00:00 2001 From: Andi Vlad Date: Tue, 12 Oct 2021 18:22:07 +0300 Subject: [PATCH 1/6] [talentia/stable] Prevent shape redraw when element is no longer present --- .../Runtime/System.Windows.Media/Polygon.cs | 8 +++++++- src/Runtime/Runtime/System.Windows.Shapes/Line.cs | 6 ++++++ .../Runtime/System.Windows.Shapes/Shape.cs | 15 +++++++++++++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Runtime/Runtime/System.Windows.Media/Polygon.cs b/src/Runtime/Runtime/System.Windows.Media/Polygon.cs index 815c08b71..9465d7dac 100644 --- a/src/Runtime/Runtime/System.Windows.Media/Polygon.cs +++ b/src/Runtime/Runtime/System.Windows.Media/Polygon.cs @@ -108,7 +108,7 @@ public override object CreateDomElement(object parentRef, out object domElementW { return INTERNAL_ShapesDrawHelpers.CreateDomElementForPathAndSimilar(this, parentRef, out _canvasDomElement, out domElementWhereToPlaceChildren); } - + override internal protected void Redraw() { if (Points.Count < 2) @@ -148,6 +148,12 @@ override internal protected void Redraw() ApplyMarginToFixNegativeCoordinates(_marginOffsets); } + // Element has been removed prevent redraw + if (! IsStillInDOM()) + { + return; + } + object context = CSHTML5.Interop.ExecuteJavaScriptAsync(@"$0.getContext('2d')", _canvasDomElement); //we remove the previous drawing: diff --git a/src/Runtime/Runtime/System.Windows.Shapes/Line.cs b/src/Runtime/Runtime/System.Windows.Shapes/Line.cs index 8cc550043..5da02db5b 100644 --- a/src/Runtime/Runtime/System.Windows.Shapes/Line.cs +++ b/src/Runtime/Runtime/System.Windows.Shapes/Line.cs @@ -215,6 +215,12 @@ override internal protected void Redraw() ApplyMarginToFixNegativeCoordinates(_marginOffsets); } + // Element has been removed prevent redraw + if (! IsStillInDOM()) + { + return; + } + object context = CSHTML5.Interop.ExecuteJavaScriptAsync($"{CSHTML5.INTERNAL_InteropImplementation.GetVariableStringForJS(_canvasDomElement)}.getContext('2d')"); //Note: we do not use INTERNAL_HtmlDomManager.Get2dCanvasContext here because we need to use the result in ExecuteJavaScript, which requires the value to come from a call of ExecuteJavaScript. string sContext = CSHTML5.INTERNAL_InteropImplementation.GetVariableStringForJS(context); //we remove the previous drawing: diff --git a/src/Runtime/Runtime/System.Windows.Shapes/Shape.cs b/src/Runtime/Runtime/System.Windows.Shapes/Shape.cs index 558a9f0d1..4af64ac72 100644 --- a/src/Runtime/Runtime/System.Windows.Shapes/Shape.cs +++ b/src/Runtime/Runtime/System.Windows.Shapes/Shape.cs @@ -480,8 +480,6 @@ internal void ScheduleRedraw() // instead of "Dispatcher.BeginInvoke" because it has better performance than calling Dispatcher.BeginInvoke directly. INTERNAL_DispatcherHelpers.QueueAction(() => { - _redrawPending = false; - // We check whether the Shape is visible in the HTML DOM tree, because if the HTML canvas is hidden // (due to a "Dispay:none" on one of the ancestors), we cannot draw on it // (this can be seen by hiding a canvas, drawing, and then showing it: it will appear empty): @@ -493,6 +491,7 @@ internal void ScheduleRedraw() { _redrawWhenBecomeVisible = true; } + _redrawPending = false; }); } } @@ -537,6 +536,12 @@ protected override void OnAfterApplyVerticalAlignmentAndWidth() ScheduleRedraw(); } + protected virtual bool IsStillInDOM() + { + // Check if the canvas element is still present + return _canvasDomElement != null && Convert.ToBoolean(CSHTML5.Interop.ExecuteJavaScript("$0.getContext !== undefined", _canvasDomElement)); + } + #endregion #region Helper Methods @@ -623,6 +628,12 @@ internal static void DrawFillAndStroke(Shape shape, double yOffsetToApplyBeforeMultiplication, Size shapeActualSize) { + // Element has been removed prevent redraw + if (! shape.IsStillInDOM()) + { + return; + } + // Note: we do not use INTERNAL_HtmlDomManager.Get2dCanvasContext here because we need // to use the result in ExecuteJavaScript, which requires the value to come from a call of ExecuteJavaScript. object context = CSHTML5.Interop.ExecuteJavaScriptAsync($"{INTERNAL_InteropImplementation.GetVariableStringForJS(shape._canvasDomElement)}.getContext('2d')"); From fbca51ce598c2876b0b986d895f34f14f9c7cbb2 Mon Sep 17 00:00:00 2001 From: roman-pikulenkor Date: Wed, 6 Oct 2021 17:19:52 +1000 Subject: [PATCH 2/6] Performance optimization ($0...$4) --- .../Core/Events/INTERNAL_EventsHelper.cs | 11 +- .../INTERNAL_SimulatorExecuteJavaScript.cs | 25 +- .../Core/Rendering/INTERNAL_HtmlDomManager.cs | 85 ++++--- .../Core/Rendering/INTERNAL_PopupsManager.cs | 20 +- .../Interop/INTERNAL_InteropImplementation.cs | 231 +++++++++--------- .../PublicAPI/Interop/Interop.Obsolete.cs | 9 + .../Runtime/PublicAPI/Interop/Interop.cs | 16 ++ .../Runtime/PublicAPI/Interop/OnCallBack.cs | 4 +- .../Grid_CSSVersion.cs | 8 +- .../Grid_InternalHelpers.cs | 9 +- .../Runtime/System.Windows.Controls/Image.cs | 34 +-- .../System.Windows.Controls/ScrollViewer.cs | 4 +- .../System.Windows.Controls/TextBoxView.cs | 99 +++++--- .../AnimationHelpers.cs | 38 +-- .../ColorAnimation.cs | 18 +- .../DoubleAnimation.cs | 14 +- .../DoubleAnimationUsingKeyFrames.cs | 15 +- .../Runtime/System.Windows.Shapes/Line.cs | 33 +-- .../Runtime/System.Windows.Shapes/Path.cs | 2 +- .../Runtime/System.Windows.Shapes/Shape.cs | 92 +++---- .../KeyRoutedEventArgs.cs | 9 +- .../PointerRoutedEventArgs.cs | 28 ++- src/Runtime/Runtime/System.Windows/Content.cs | 4 +- ...ameworkElement_HandlingSizeAndAlignment.cs | 11 +- .../Runtime/System.Windows/RoutedEventArgs.cs | 3 +- .../Runtime/System.Windows/UIElement.cs | 9 +- .../System.Windows/UIElement_Events.cs | 62 +++-- src/Runtime/Runtime/System.Windows/Window.cs | 12 +- 28 files changed, 509 insertions(+), 396 deletions(-) diff --git a/src/Runtime/Runtime/Core/Events/INTERNAL_EventsHelper.cs b/src/Runtime/Runtime/Core/Events/INTERNAL_EventsHelper.cs index 29785c4a8..f9149142e 100644 --- a/src/Runtime/Runtime/Core/Events/INTERNAL_EventsHelper.cs +++ b/src/Runtime/Runtime/Core/Events/INTERNAL_EventsHelper.cs @@ -55,10 +55,11 @@ public static HtmlEventProxy AttachToDomEvents(string eventName, object domEleme static void AttachEvent(string eventName, object domElementRef, HtmlEventProxy newProxy, Action originalEventHandler) { #if !BUILDINGDOCUMENTATION + string sAction = INTERNAL_InteropImplementation.GetVariableStringForJS((Action)newProxy.OnEvent); if (domElementRef is INTERNAL_HtmlDomElementReference) - Interop.ExecuteJavaScriptAsync(@"document.addEventListenerSafe($0, $1, $2)", ((INTERNAL_HtmlDomElementReference)domElementRef).UniqueIdentifier, eventName, (Action)newProxy.OnEvent); + Interop.ExecuteJavaScriptFastAsync($@"document.addEventListenerSafe(""{((INTERNAL_HtmlDomElementReference)domElementRef).UniqueIdentifier}"", ""{eventName}"", {sAction})"); else - Interop.ExecuteJavaScriptAsync(@"document.addEventListenerSafe($0, $1, $2)", domElementRef, eventName, (Action)newProxy.OnEvent); + Interop.ExecuteJavaScriptFastAsync($@"document.addEventListenerSafe({INTERNAL_InteropImplementation.GetVariableStringForJS(domElementRef)}, ""{eventName}"", {sAction})"); /* DOMEventType eventType; @@ -87,7 +88,11 @@ static void AttachEvent(string eventName, object domElementRef, HtmlEventProxy n internal static void DetachEvent(string eventName, object domElementRef, HtmlEventProxy proxy, Action originalEventHandler) { #if !BUILDINGDOCUMENTATION - Interop.ExecuteJavaScriptAsync(@"document.removeEventListenerSafe($0, $1, $2)", domElementRef, eventName, (Action)proxy.OnEvent); + string sAction = INTERNAL_InteropImplementation.GetVariableStringForJS((Action)proxy.OnEvent); + if (domElementRef is INTERNAL_HtmlDomElementReference) + Interop.ExecuteJavaScriptFastAsync($@"document.removeEventListenerSafe(""{((INTERNAL_HtmlDomElementReference)domElementRef).UniqueIdentifier}"", ""{eventName}"", {sAction})"); + else + Interop.ExecuteJavaScriptFastAsync($@"document.removeEventListenerSafe({INTERNAL_InteropImplementation.GetVariableStringForJS(domElementRef)}, ""{eventName}"", {sAction})"); /* DOMEventType eventType; diff --git a/src/Runtime/Runtime/Core/Main/INTERNAL_SimulatorExecuteJavaScript.cs b/src/Runtime/Runtime/Core/Main/INTERNAL_SimulatorExecuteJavaScript.cs index fc74f6e5b..1d1bfbe9e 100644 --- a/src/Runtime/Runtime/Core/Main/INTERNAL_SimulatorExecuteJavaScript.cs +++ b/src/Runtime/Runtime/Core/Main/INTERNAL_SimulatorExecuteJavaScript.cs @@ -154,23 +154,22 @@ internal static void ExecuteJavaScriptAsync(string javaScriptToExecute, string c #if OPTIMIZATION_LOG Console.WriteLine("[OPTIMIZATION] Calling setTimeout. _isDispatcherPending: " + _isDispatcherPending.ToString()); #endif - CSHTML5.INTERNAL_InteropImplementation.ExecuteJavaScript_SimulatorImplementation( - javascript: "setTimeout($0, 1)", - runAsynchronously: false, - noImpactOnPendingJSCode: true, - variables: new object[] + string action = CSHTML5.INTERNAL_InteropImplementation.GetVariableStringForJS( + (Action)(() => { - (Action)(() => - { #if OPTIMIZATION_LOG Console.WriteLine("[OPTIMIZATION] Executing setTimeout. _isDispatcherPending: " + _isDispatcherPending.ToString()); #endif - if (_isDispatcherPending) // We check again, because in the meantime the dispatcher can be cancelled in case of a forced execution of the pending JS code, for example when making a JavaScript execution that "returns a value". - { - ExecutePendingJavaScriptCode("SETTIMEOUT COMPLETED"); - } - }) - } + if (_isDispatcherPending) // We check again, because in the meantime the dispatcher can be cancelled in case of a forced execution of the pending JS code, for example when making a JavaScript execution that "returns a value". + { + ExecutePendingJavaScriptCode("SETTIMEOUT COMPLETED"); + } + }) + ); + CSHTML5.INTERNAL_InteropImplementation.ExecuteJavaScript_SimulatorImplementation( + javascript: $"setTimeout({action}, 1)", + runAsynchronously: false, + noImpactOnPendingJSCode: true ); } } diff --git a/src/Runtime/Runtime/Core/Rendering/INTERNAL_HtmlDomManager.cs b/src/Runtime/Runtime/Core/Rendering/INTERNAL_HtmlDomManager.cs index d9235c832..efb91b587 100644 --- a/src/Runtime/Runtime/Core/Rendering/INTERNAL_HtmlDomManager.cs +++ b/src/Runtime/Runtime/Core/Rendering/INTERNAL_HtmlDomManager.cs @@ -179,13 +179,14 @@ public static dynamic GetFirstChildDomElement(dynamic domElementRef) #endif public static INTERNAL_HtmlDomElementReference GetChildDomElementAt(INTERNAL_HtmlDomElementReference domElementRef, int index) { - int length = Convert.ToInt32(Interop.ExecuteJavaScript("$0.childNodes.length", domElementRef)); + string sDomElement = INTERNAL_InteropImplementation.GetVariableStringForJS(domElementRef); + int length = Convert.ToInt32(Interop.ExecuteJavaScript($"{sDomElement}.childNodes.length")); if (index < 0 || length <= index) { throw new IndexOutOfRangeException(); } - string childNodeId = Interop.ExecuteJavaScript(@"var child = $0.childNodes[$1]; child.id;", domElementRef, index).ToString(); + string childNodeId = Interop.ExecuteJavaScript($@"var child = {sDomElement}.childNodes[{index.ToInvariantString()}]; child.id;").ToString(); return new INTERNAL_HtmlDomElementReference(childNodeId, domElementRef); } @@ -233,7 +234,8 @@ public static void SetFocus(UIElement element) if (IsRunningInJavaScript()) #endif { - CSHTML5.Interop.ExecuteJavaScriptAsync("setTimeout(function() { $0.focus(); }, 1)", domElementRefConcernedByFocus); + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(domElementRefConcernedByFocus); + CSHTML5.Interop.ExecuteJavaScriptAsync($@"setTimeout(function() {{ {sElement}.focus(); }}, 1)"); } else { @@ -390,10 +392,11 @@ public static string GetTextBoxText(object domElementRef) { #endif string uniqueIdentifier = ((INTERNAL_HtmlDomElementReference)domElementRef).UniqueIdentifier; - object domElement = Interop.ExecuteJavaScriptAsync(@"document.getElementByIdSafe($0)", uniqueIdentifier); + object domElement = Interop.ExecuteJavaScriptAsync($@"document.getElementByIdSafe(""{uniqueIdentifier}"")"); //todo-perfs: replace the code above with a call to the faster "ExecuteJavaScript" method instead of "ExecuteJavaScriptWithResult". To do so, see other methods in this class, or see the class "INTERNAL_HtmlDomStyleReference.cs". - return Interop.ExecuteJavaScript("getTextAreaInnerText($0)", domElement).ToString(); + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(domElement); + return Interop.ExecuteJavaScript($"getTextAreaInnerText({sElement})").ToString(); #if !CSHTML5NETSTANDARD } #endif @@ -424,14 +427,14 @@ public static object AddOptionToNativeComboBox( string elementToAdd, int index) { - var optionDomElement = CSHTML5.Interop.ExecuteJavaScriptAsync(@" -(function(){ + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(nativeComboBoxDomElement); + var optionDomElement = CSHTML5.Interop.ExecuteJavaScriptAsync($@" +(function(){{ var option = document.createElement(""option""); - option.text = $1; - $0.add(option, $2); + option.text = ""{EscapeStringForUseInJavaScript(elementToAdd)}""; + {sElement}.add(option, {index.ToInvariantString()}); return option; -}()) -", nativeComboBoxDomElement, elementToAdd, index); +}}())"); return optionDomElement; } @@ -449,14 +452,14 @@ public static object AddOptionToNativeComboBox(object nativeComboBoxDomElement, }() ", domNode, elementToAdd); */ - var optionDomElement = CSHTML5.Interop.ExecuteJavaScriptAsync(@" -(function(){ + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(nativeComboBoxDomElement); + var optionDomElement = CSHTML5.Interop.ExecuteJavaScriptAsync($@" +(function(){{ var option = document.createElement(""option""); - option.text = $1; - $0.add(option); + option.text = ""{EscapeStringForUseInJavaScript(elementToAdd)}""; + {sElement}.add(option); return option; -}()) -", nativeComboBoxDomElement, elementToAdd); +}}())"); return optionDomElement; /* if (IsRunningInJavaScript()) @@ -499,12 +502,15 @@ public static object AddOptionToNativeComboBox(object nativeComboBoxDomElement, public static void RemoveOptionFromNativeComboBox(object optionToRemove, object nativeComboBoxDomElement) { - CSHTML5.Interop.ExecuteJavaScriptAsync(@"$0.removeChild($1)", nativeComboBoxDomElement, optionToRemove); + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(nativeComboBoxDomElement); + string sToRemove = INTERNAL_InteropImplementation.GetVariableStringForJS(optionToRemove); + CSHTML5.Interop.ExecuteJavaScriptFastAsync($"{sElement}.removeChild({sToRemove})"); } public static void RemoveOptionFromNativeComboBox(object nativeComboBoxDomElement, int index) { - CSHTML5.Interop.ExecuteJavaScriptAsync(@"$0.remove($1)", nativeComboBoxDomElement, index); + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(nativeComboBoxDomElement); + CSHTML5.Interop.ExecuteJavaScriptFastAsync($"{sElement}.remove({index.ToInvariantString()})"); } //public static void AppendChild(dynamic parentDomElement, dynamic childDomElement) @@ -679,12 +685,14 @@ internal static void SetDomElementStylePropertyUsingVelocity(object domElement, { //INTERNAL_HtmlDomManager.SetDomElementStyleProperty(cssEquivalent.DomElement, cssEquivalent.Name, cssValue); object newObj = CSHTML5.Interop.ExecuteJavaScriptAsync(@"new Object()"); - + string sNewobj = INTERNAL_InteropImplementation.GetVariableStringForJS(newObj); + string sCssValue = INTERNAL_InteropImplementation.GetVariableStringForJS(cssValue); foreach (string csspropertyName in cssPropertyNames) { - CSHTML5.Interop.ExecuteJavaScriptAsync(@"$0[$1] = $2;", newObj, csspropertyName, cssValue); + CSHTML5.Interop.ExecuteJavaScriptFastAsync($@"{sNewobj}[""{csspropertyName}""] = {sCssValue};"); } - CSHTML5.Interop.ExecuteJavaScriptAsync(@"Velocity($0, $1, {duration:1, queue:false});", domElement, newObj); + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(domElement); ; + CSHTML5.Interop.ExecuteJavaScriptFastAsync($"Velocity({sElement}, {sNewobj}, {{duration:1, queue:false}});"); } } @@ -717,7 +725,8 @@ public static void RemoveDomElementAttribute(object domElementRef, string attrib public static object GetDomElementAttribute(object domElementRef, string attributeName) { - return Interop.ExecuteJavaScript("$0[$1]", domElementRef, attributeName); + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(domElementRef); + return Interop.ExecuteJavaScript($@"{sElement}[""{attributeName}""]"); // if (IsRunningInJavaScript()) // { @@ -917,11 +926,13 @@ static object CreateDomElementAndAppendIt_ForUseByTheSimulator(string domElement if (parentRef is INTERNAL_HtmlDomElementReference) { parent = (INTERNAL_HtmlDomElementReference)parentRef; - Interop.ExecuteJavaScriptAsync(@"document.createElementSafe($0, $1, $2, $3)", domElementTag, uniqueIdentifier, parent.UniqueIdentifier, index); + string javaScriptToExecute = $@"document.createElementSafe(""{domElementTag}"", ""{uniqueIdentifier}"", ""{parent.UniqueIdentifier}"", {index.ToInvariantString()})"; + Interop.ExecuteJavaScriptFastAsync(javaScriptToExecute); } else { - Interop.ExecuteJavaScriptAsync(@"document.createElementSafe($0, $1, $2, $3)", domElementTag, uniqueIdentifier, parentRef, index); + string sParentRef = INTERNAL_InteropImplementation.GetVariableStringForJS(parentRef); + Interop.ExecuteJavaScriptFastAsync($@"document.createElementSafe(""{domElementTag}"", ""{uniqueIdentifier}"", {sParentRef}, {index.ToInvariantString()})"); } INTERNAL_idsToUIElements.Add(uniqueIdentifier, associatedUIElement); @@ -1188,19 +1199,18 @@ public static object ExecuteJavaScriptWithResult(string javaScriptToExecute, str /// in the visual tree composition at the specified point. public static UIElement FindElementInHostCoordinates_UsedBySimulatorToo(double x, double y) // IMPORTANT: If you rename this method or change its signature, make sure to rename its dynamic call in the Simulator. { - object domElementAtCoordinates = Interop.ExecuteJavaScript(@" -(function(){ - var domElementAtCoordinates = document.elementFromPoint($0, $1); + object domElementAtCoordinates = Interop.ExecuteJavaScript($@" +(function(){{ + var domElementAtCoordinates = document.elementFromPoint({x.ToInvariantString()}, {y.ToInvariantString()}); if (!domElementAtCoordinates || domElementAtCoordinates === document.documentElement) - { + {{ return null; - } + }} else - { + {{ return domElementAtCoordinates; - } -}()) -", x, y); + }} +}}())"); UIElement result = GetUIElementFromDomElement(domElementAtCoordinates); @@ -1213,6 +1223,7 @@ public static UIElement GetUIElementFromDomElement(object domElementRef) while (!IsNullOrUndefined(domElementRef)) { + string sElement = INTERNAL_InteropImplementation.GetVariableStringForJS(domElementRef); // Walk up the DOM tree until we find a DOM element that has a corresponding CSharp object: #if OPENSILVER if (true) @@ -1222,7 +1233,7 @@ public static UIElement GetUIElementFromDomElement(object domElementRef) { // In the Simulator, we get the CSharp object associated to a DOM element by searching for the DOM element ID in the "INTERNAL_idsToUIElements" dictionary. - object jsId = Interop.ExecuteJavaScript("$0.id", domElementRef); + object jsId = Interop.ExecuteJavaScript($"{sElement}.id"); if (!IsNullOrUndefined(jsId)) { string id = Convert.ToString(jsId); @@ -1237,7 +1248,7 @@ public static UIElement GetUIElementFromDomElement(object domElementRef) { // In JavaScript, we get the CSharp object associated to a DOM element by reading the "associatedUIElement" property: - object associatedUIElement = Interop.ExecuteJavaScript("$0.associatedUIElement", domElementRef); + object associatedUIElement = Interop.ExecuteJavaScript($"{sElement}.associatedUIElement"); if (!IsNullOrUndefined(associatedUIElement)) { result = (UIElement)associatedUIElement; @@ -1246,7 +1257,7 @@ public static UIElement GetUIElementFromDomElement(object domElementRef) } // Move to the parent: - domElementRef = Interop.ExecuteJavaScript("$0.parentNode", domElementRef); + domElementRef = Interop.ExecuteJavaScript($"{sElement}.parentNode"); } return result; diff --git a/src/Runtime/Runtime/Core/Rendering/INTERNAL_PopupsManager.cs b/src/Runtime/Runtime/Core/Rendering/INTERNAL_PopupsManager.cs index c14032421..77b861b9d 100644 --- a/src/Runtime/Runtime/Core/Rendering/INTERNAL_PopupsManager.cs +++ b/src/Runtime/Runtime/Core/Rendering/INTERNAL_PopupsManager.cs @@ -112,18 +112,17 @@ public static PopupRoot CreateAndAppendNewPopupRoot(Window parentWindow) // Create a DIV for the PopupRoot in the DOM tree: //-------------------------------------- - CSHTML5.Interop.ExecuteJavaScriptAsync( -@" + CSHTML5.Interop.ExecuteJavaScriptFastAsync( +$@" var popupRoot = document.createElement('div'); -popupRoot.setAttribute('id', $0); +popupRoot.setAttribute('id', ""{uniquePopupRootIdentifier}""); popupRoot.style.position = 'absolute'; popupRoot.style.width = '100%'; popupRoot.style.height = '100%'; popupRoot.style.overflowX = 'hidden'; popupRoot.style.overflowY = 'hidden'; popupRoot.style.pointerEvents = 'none'; -$1.appendChild(popupRoot); -", uniquePopupRootIdentifier, parentWindow.INTERNAL_RootDomElement); +{INTERNAL_InteropImplementation.GetVariableStringForJS(parentWindow.INTERNAL_RootDomElement)}.appendChild(popupRoot);"); //-------------------------------------- // Get the PopupRoot DIV: @@ -138,7 +137,7 @@ public static PopupRoot CreateAndAppendNewPopupRoot(Window parentWindow) #endif popupRootDiv = new INTERNAL_HtmlDomElementReference(uniquePopupRootIdentifier, null); else - popupRootDiv = Interop.ExecuteJavaScriptAsync("document.getElementByIdSafe($0)", uniquePopupRootIdentifier); + popupRootDiv = Interop.ExecuteJavaScriptAsync($@"document.getElementByIdSafe(""{uniquePopupRootIdentifier}"")"); //-------------------------------------- // Create the C# class that points to the PopupRoot DIV: @@ -189,11 +188,10 @@ public static void RemovePopupRoot(PopupRoot popupRoot) // Remove from the DOM: //-------------------------------------- - CSHTML5.Interop.ExecuteJavaScriptAsync( -@" -var popupRoot = document.getElementByIdSafe($0); -$1.removeChild(popupRoot); -", uniquePopupRootIdentifier, parentWindow.INTERNAL_RootDomElement); + CSHTML5.Interop.ExecuteJavaScriptFastAsync( +$@" +var popupRoot = document.getElementByIdSafe(""{uniquePopupRootIdentifier}""); +{INTERNAL_InteropImplementation.GetVariableStringForJS(parentWindow.INTERNAL_RootDomElement)}.removeChild(popupRoot);"); //-------------------------------------- // Remove from the list of popups: diff --git a/src/Runtime/Runtime/PublicAPI/Interop/INTERNAL_InteropImplementation.cs b/src/Runtime/Runtime/PublicAPI/Interop/INTERNAL_InteropImplementation.cs index 876fd90b0..46643bd15 100644 --- a/src/Runtime/Runtime/PublicAPI/Interop/INTERNAL_InteropImplementation.cs +++ b/src/Runtime/Runtime/PublicAPI/Interop/INTERNAL_InteropImplementation.cs @@ -47,6 +47,105 @@ static INTERNAL_InteropImplementation() }; } + + internal static string GetVariableStringForJS(object variable) + { + if (variable is INTERNAL_JSObjectReference) + { + //---------------------- + // JS Object References + //---------------------- + + var jsObjectReference = (INTERNAL_JSObjectReference)variable; + string jsCodeForAccessingTheObject; + + if (jsObjectReference.IsArray) + { + jsCodeForAccessingTheObject = $@"document.jsObjRef[""{jsObjectReference.ReferenceId}""][{jsObjectReference.ArrayIndex}]"; + } + else + { + jsCodeForAccessingTheObject = $@"document.jsObjRef[""{jsObjectReference.ReferenceId}""]"; + } + + return jsCodeForAccessingTheObject; + } + else if (variable is INTERNAL_HtmlDomElementReference) + { + //------------------------ + // DOM Element References + //------------------------ + + string id = ((INTERNAL_HtmlDomElementReference)variable).UniqueIdentifier; + return $@"document.getElementByIdSafe(""{id}"")"; + } + else if (variable is INTERNAL_SimulatorJSExpression) + { + //------------------------ + // JS Expression (simulator only) + //------------------------ + + string expression = ((INTERNAL_SimulatorJSExpression)variable).Expression; + return expression; + } + else if (variable is Delegate) + { + //----------- + // Delegates + //----------- + + Delegate callback = (Delegate)variable; + + // Add the callback to the document: + int callbackId = ReferenceIDGenerator.GenerateId(); + CallbacksDictionary.Add(callbackId, callback); + + var isVoid = callback.Method.ReturnType == typeof(void); + + // Change the JS code to point to that callback: + return string.Format( + @"(function() {{ return document.eventCallback({0}, {1}, {2});}})", callbackId, +#if OPENSILVER + Interop.IsRunningInTheSimulator_WorkAround ? "arguments" : "Array.prototype.slice.call(arguments)", +#elif BRIDGE + "Array.prototype.slice.call(arguments)", +#endif + (!isVoid).ToString().ToLower() + ); + + // Note: generating the random number in JS rather than C# is important in order + // to be able to put this code inside a JavaScript "for" statement (cf. + // deserialization code of the JsonConvert extension, and also ZenDesk ticket #974) + // so that the "closure" system of JavaScript ensures that the number is the same + // before and inside the "setTimeout" call, but different for each iteration of the + // "for" statement in which this piece of code is put. + // Note: we store the arguments in the jsObjRef that is inside + // the JS context, so that the user can access them from the callback. + // Note: "Array.prototype.slice.call" will convert the arguments keyword into an array + // (cf. http://stackoverflow.com/questions/960866/how-can-i-convert-the-arguments-object-to-an-array-in-javascript) + // Note: in the command above, we use "setTimeout" to avoid thread/locks problems. + } + else if (variable == null) + { + //-------------------- + // Null + //-------------------- + + return "null"; + } + else + { + //-------------------- + // Simple value types or other objects + // (note: this includes objects that + // override the "ToString" method, such + // as the class "Uri") + //-------------------- + + return INTERNAL_HtmlDomManager.ConvertToStringToUseInJavaScriptCode(variable); + } + } + #if BRIDGE [Bridge.Template("null")] #endif @@ -97,101 +196,7 @@ internal static object ExecuteJavaScript_SimulatorImplementation(string javascri // 10 arguments and using "$10". for (int i = variables.Length - 1; i >= 0; i--) { - var variable = variables[i]; - if (variable is INTERNAL_JSObjectReference) - { - //---------------------- - // JS Object References - //---------------------- - - var jsObjectReference = (INTERNAL_JSObjectReference)variable; - string jsCodeForAccessingTheObject; - - if (jsObjectReference.IsArray) - { - jsCodeForAccessingTheObject = $@"document.jsObjRef[""{jsObjectReference.ReferenceId}""][{jsObjectReference.ArrayIndex}]"; - } - else - { - jsCodeForAccessingTheObject = $@"document.jsObjRef[""{jsObjectReference.ReferenceId}""]"; - } - - javascript = javascript.Replace("$" + i.ToString(), jsCodeForAccessingTheObject); - } - else if (variable is INTERNAL_HtmlDomElementReference) - { - //------------------------ - // DOM Element References - //------------------------ - - string id = ((INTERNAL_HtmlDomElementReference)variable).UniqueIdentifier; - javascript = javascript.Replace("$" + i.ToString(), $@"document.getElementByIdSafe(""{id}"")"); - } - else if (variable is INTERNAL_SimulatorJSExpression) - { - //------------------------ - // JS Expression (simulator only) - //------------------------ - - string expression = ((INTERNAL_SimulatorJSExpression)variable).Expression; - javascript = javascript.Replace("$" + i.ToString(), expression); - } - else if (variable is Delegate) - { - //----------- - // Delegates - //----------- - - Delegate callback = (Delegate)variable; - - // Add the callback to the document: - int callbackId = ReferenceIDGenerator.GenerateId(); - CallbacksDictionary.Add(callbackId, callback); - - var isVoid = callback.Method.ReturnType == typeof(void); - - // Change the JS code to point to that callback: - javascript = javascript.Replace("$" + i.ToString(), string.Format( - @"(function() {{ return document.eventCallback({0}, {1}, {2});}})", callbackId, -#if OPENSILVER - Interop.IsRunningInTheSimulator_WorkAround ? "arguments" : "Array.prototype.slice.call(arguments)", -#elif BRIDGE - "Array.prototype.slice.call(arguments)", -#endif - (!isVoid).ToString().ToLower() - )); - - // Note: generating the random number in JS rather than C# is important in order - // to be able to put this code inside a JavaScript "for" statement (cf. - // deserialization code of the JsonConvert extension, and also ZenDesk ticket #974) - // so that the "closure" system of JavaScript ensures that the number is the same - // before and inside the "setTimeout" call, but different for each iteration of the - // "for" statement in which this piece of code is put. - // Note: we store the arguments in the jsObjRef that is inside - // the JS context, so that the user can access them from the callback. - // Note: "Array.prototype.slice.call" will convert the arguments keyword into an array - // (cf. http://stackoverflow.com/questions/960866/how-can-i-convert-the-arguments-object-to-an-array-in-javascript) - // Note: in the command above, we use "setTimeout" to avoid thread/locks problems. - } - else if (variable == null) - { - //-------------------- - // Null - //-------------------- - - javascript = javascript.Replace("$" + i.ToString(), "null"); - } - else - { - //-------------------- - // Simple value types or other objects - // (note: this includes objects that - // override the "ToString" method, such - // as the class "Uri") - //-------------------- - - javascript = javascript.Replace("$" + i.ToString(), INTERNAL_HtmlDomManager.ConvertToStringToUseInJavaScriptCode(variable)); - } + javascript = javascript.Replace("$" + i.ToString(), GetVariableStringForJS(variables[i])); } UnmodifiedJavascriptCalls.Add(unmodifiedJavascript); @@ -204,7 +209,7 @@ internal static object ExecuteJavaScript_SimulatorImplementation(string javascri // result into the "document.jsObjRef" for later // use in subsequent calls to this method int referenceId = ReferenceIDGenerator.GenerateId(); - javascript = $"document.callScriptSafe(\"{referenceId.ToString()}\",\"{INTERNAL_HtmlDomManager.EscapeStringForUseInJavaScript(javascript)}\",{errorCallBackId})"; + javascript = $"document.callScriptSafe(\"{referenceId.ToString(System.Globalization.CultureInfo.InvariantCulture)}\",\"{INTERNAL_HtmlDomManager.EscapeStringForUseInJavaScript(javascript)}\",{errorCallBackId})"; // Execute the javascript code: object value = null; @@ -347,31 +352,33 @@ internal static void LoadJavaScriptFile(string url, string callerAssemblyName, A else { _pendingJSFile.Add(html5Path, new List> { new Tuple(callbackOnSuccess, callbackOnFailure) }); + string sSuccessAction = GetVariableStringForJS((Action)LoadJavaScriptFileSuccess); + string sFailureAction = GetVariableStringForJS((Action)LoadJavaScriptFileFailure); CSHTML5.Interop.ExecuteJavaScript( - @"// Add the script tag to the head -var filePath = $0; + $@"// Add the script tag to the head +var filePath = {GetVariableStringForJS(html5Path)}; var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = filePath; // Then bind the event to the callback function // There are several events for cross browser compatibility. -if(script.onreadystatechange != undefined) { -script.onreadystatechange = $1; -} else { -script.onload = function () { $1(filePath) }; -script.onerror = function () { $2(filePath) }; -} +if(script.onreadystatechange != undefined) {{ +script.onreadystatechange = {sSuccessAction}; +}} else {{ +script.onload = function () {{ {sSuccessAction}(filePath) }}; +script.onerror = function () {{ {sFailureAction}(filePath) }}; +}} // Fire the loading -head.appendChild(script);", html5Path, (Action)LoadJavaScriptFileSuccess, (Action)LoadJavaScriptFileFailure); +head.appendChild(script);"); } } private static void LoadJavaScriptFileSuccess(object jsArgument) { // using an Interop call instead of jsArgument.ToString because it causes errors in OpenSilver. - string loadedFileName = Convert.ToString(Interop.ExecuteJavaScript(@"$0", jsArgument)); + string loadedFileName = Convert.ToString(Interop.ExecuteJavaScript(GetVariableStringForJS(jsArgument))); foreach (Tuple actions in _pendingJSFile[loadedFileName]) { actions.Item1(); @@ -383,7 +390,7 @@ private static void LoadJavaScriptFileSuccess(object jsArgument) private static void LoadJavaScriptFileFailure(object jsArgument) { // using an Interop call instead of jsArgument.ToString because it causes errors in OpenSilver. - string loadedFileName = Convert.ToString(Interop.ExecuteJavaScript(@"$0", jsArgument)); + string loadedFileName = Convert.ToString(Interop.ExecuteJavaScript(GetVariableStringForJS(jsArgument))); foreach (Tuple actions in _pendingJSFile[loadedFileName]) { actions.Item2(); @@ -427,13 +434,15 @@ internal static void LoadCssFile(string url, Action callback) string html5Path = INTERNAL_UriHelper.ConvertToHtml5Path(url); + string sHtml5Path = GetVariableStringForJS(html5Path); + string sCallback = GetVariableStringForJS(callback); CSHTML5.Interop.ExecuteJavaScript( -@"// Add the link tag to the head +$@"// Add the link tag to the head var head = document.getElementsByTagName('head')[0]; var link = document.createElement('link'); link.rel = 'stylesheet'; link.type = 'text/css'; -link.href = $0; +link.href = {sHtml5Path}; link.media = 'all'; // Fire the loading @@ -442,8 +451,8 @@ internal static void LoadCssFile(string url, Action callback) // Some browsers do not support the 'onload' event of the 'link' element, // therefore we use the 'onerror' event of the 'img' tag instead, which is always triggered: var img = document.createElement('img'); -img.onerror = $1; -img.src = $0;", html5Path, callback); +img.onerror = {sCallback}; +img.src = {sHtml5Path};"); } internal static void LoadCssFiles(List urls, Action onCompleted) diff --git a/src/Runtime/Runtime/PublicAPI/Interop/Interop.Obsolete.cs b/src/Runtime/Runtime/PublicAPI/Interop/Interop.Obsolete.cs index 1d60ae722..f611be13e 100644 --- a/src/Runtime/Runtime/PublicAPI/Interop/Interop.Obsolete.cs +++ b/src/Runtime/Runtime/PublicAPI/Interop/Interop.Obsolete.cs @@ -81,6 +81,15 @@ public static object ExecuteJavaScriptAsync(string javascript, params object[] v return OpenSilver.Interop.ExecuteJavaScriptAsync(javascript, variables); } + /// + /// Allows calling JavaScript code from within C#. + /// + /// The JavaScript code to execute. + internal static void ExecuteJavaScriptFastAsync(string javascript) + { + OpenSilver.Interop.ExecuteJavaScriptFastAsync(javascript); + } + /// /// Unboxes the value passed as a parameter. It is particularly useful for the variables of the ExecuteJavaScript Methods calls aimed at using third party libraries. /// diff --git a/src/Runtime/Runtime/PublicAPI/Interop/Interop.cs b/src/Runtime/Runtime/PublicAPI/Interop/Interop.cs index 4e748a5b3..ce6557e2d 100644 --- a/src/Runtime/Runtime/PublicAPI/Interop/Interop.cs +++ b/src/Runtime/Runtime/PublicAPI/Interop/Interop.cs @@ -82,6 +82,22 @@ public static object ExecuteJavaScriptAsync(string javascript, params object[] v return CSHTML5.INTERNAL_InteropImplementation.ExecuteJavaScript_SimulatorImplementation(javascript, runAsynchronously: true, variables: variables); } + /// + /// Execute JavaScript code without document.callScriptSafe + /// + public static void ExecuteJavaScriptFastAsync(string javascript) + { +#if OPENSILVER + if (CSHTML5.Interop.IsRunningInTheSimulator_WorkAround) +#elif BRIDGE + if (CSHTML5.Interop.IsRunningInTheSimulator) +#endif + CSHTML5.INTERNAL_InteropImplementation.ExecuteJavaScript_SimulatorImplementation(javascript, runAsynchronously: true); + else + INTERNAL_SimulatorExecuteJavaScript.ExecuteJavaScriptAsync(javascript); + } + + /// /// Unboxes the value passed as a parameter. It is particularly useful for the variables of the ExecuteJavaScript Methods calls aimed at using third party libraries. /// diff --git a/src/Runtime/Runtime/PublicAPI/Interop/OnCallBack.cs b/src/Runtime/Runtime/PublicAPI/Interop/OnCallBack.cs index 9ac419515..00b77543e 100644 --- a/src/Runtime/Runtime/PublicAPI/Interop/OnCallBack.cs +++ b/src/Runtime/Runtime/PublicAPI/Interop/OnCallBack.cs @@ -60,8 +60,8 @@ public static void OnCallbackFromJavaScriptError(string idWhereCallbackArgsAreSt { Action action = () => { - string errorMessage = Convert.ToString(Interop.ExecuteJavaScript("document.jsObjRef[$0][0]", idWhereCallbackArgsAreStored)); - int indexOfNextUnmodifiedJSCallInList = Convert.ToInt32(Interop.ExecuteJavaScript("document.jsObjRef[$0][1]", idWhereCallbackArgsAreStored)); + string errorMessage = Convert.ToString(Interop.ExecuteJavaScript($@"document.jsObjRef[""{idWhereCallbackArgsAreStored}""][0]")); + int indexOfNextUnmodifiedJSCallInList = Convert.ToInt32(Interop.ExecuteJavaScript($@"document.jsObjRef[""{idWhereCallbackArgsAreStored}""][1]")); INTERNAL_InteropImplementation.ShowErrorMessage(errorMessage, indexOfNextUnmodifiedJSCallInList); }; diff --git a/src/Runtime/Runtime/System.Windows.Controls/Grid_CSSVersion.cs b/src/Runtime/Runtime/System.Windows.Controls/Grid_CSSVersion.cs index ceddd71fe..d17605c89 100644 --- a/src/Runtime/Runtime/System.Windows.Controls/Grid_CSSVersion.cs +++ b/src/Runtime/Runtime/System.Windows.Controls/Grid_CSSVersion.cs @@ -162,15 +162,15 @@ internal void LocallyManageChildrenChanged_CSSVersion() //--------------------------------------------------------------- if (Grid_InternalHelpers.IsAllCollapsedColumns(this, elementColumn, elementLastColumn)) { - CSHTML5.Interop.ExecuteJavaScriptAsync(@"$0.style.overflow = 'hidden'", uiElement.INTERNAL_OuterDomElement); - CSHTML5.Interop.ExecuteJavaScriptAsync(@"$0.setAttribute('data-isCollapsedDueToHiddenColumn', true)", uiElement.INTERNAL_OuterDomElement); + string sElement = CSHTML5.INTERNAL_InteropImplementation.GetVariableStringForJS(uiElement.INTERNAL_OuterDomElement); + CSHTML5.Interop.ExecuteJavaScriptFastAsync($"{sElement}.style.overflow = 'hidden'"); + CSHTML5.Interop.ExecuteJavaScriptFastAsync($"{sElement}.setAttribute('data-isCollapsedDueToHiddenColumn', true)"); } else { // Note: we set to Visible only if it was previously Hidden due to the fact that a Grid column is hidden, to avoid conflicts such as replacing the "overflow" property set by the ScrollViewer or by the "ClipToBounds" property. //setAttribute('data-isCollapsedDueToHiddenColumn', false) - CSHTML5.Interop.ExecuteJavaScriptAsync(@"document.setGridCollapsedDuetoHiddenColumn($0)", - ((INTERNAL_HtmlDomElementReference)uiElement.INTERNAL_OuterDomElement).UniqueIdentifier); + CSHTML5.Interop.ExecuteJavaScriptFastAsync($@"document.setGridCollapsedDuetoHiddenColumn(""{((INTERNAL_HtmlDomElementReference)uiElement.INTERNAL_OuterDomElement).UniqueIdentifier}"")"); } } } diff --git a/src/Runtime/Runtime/System.Windows.Controls/Grid_InternalHelpers.cs b/src/Runtime/Runtime/System.Windows.Controls/Grid_InternalHelpers.cs index a9419aaf7..4dbd239d3 100644 --- a/src/Runtime/Runtime/System.Windows.Controls/Grid_InternalHelpers.cs +++ b/src/Runtime/Runtime/System.Windows.Controls/Grid_InternalHelpers.cs @@ -789,15 +789,16 @@ internal static void RefreshAllColumnsWidth_CSSVersion(Grid grid, List