internal static object ExecuteJavaScript_SimulatorImplementation(string javascript, bool runAsynchronously, bool noImpactOnPendingJSCode = false, params object[] variables)
        {
#if !BUILDINGDOCUMENTATION
            //---------------
            // Due to the fact that it is not possible to pass JavaScript objects between the simulator JavaScript context
            // and the C# context, we store the JavaScript objects in a global dictionary inside the JavaScript context.
            // This dictionary is named "jsSimulatorObjectReferences". It associates a unique integer ID to each JavaScript
            // object. In C# we only manipulate those IDs by manipulating instances of the "JSObjectReference" class.
            // When we need to re-use those JavaScript objects, the C# code passes to the JavaScript context the ID
            // of the object, so that the JavaScript code can retrieve the JavaScript object instance by using the
            // aforementioned dictionary.
            //---------------

            // Verify the arguments:
            if (noImpactOnPendingJSCode && runAsynchronously)
            {
                throw new ArgumentException("You cannot set both 'noImpactOnPendingJSCode' and 'runAsynchronously' to True. The 'noImpactOnPendingJSCode' only has meaning when running synchronously.");
            }

            // Make sure the JS to C# interop is set up:
            if (!IsJavaScriptCSharpInteropSetUp)
            {
#if CSHTML5BLAZOR
                if (Interop.IsRunningInTheSimulator_WorkAround)
                {
#endif
                // Adding a property to the JavaScript "window" object:
                JSObject jsWindow = (JSObject)INTERNAL_HtmlDomManager.ExecuteJavaScriptWithResult("window");
                jsWindow.SetProperty("onCallBack", new OnCallBack(CallbacksDictionary));
#if CSHTML5BLAZOR
            }
            else
            {
                OnCallBack.SetCallbacksDictionary(CallbacksDictionary);
            }
#endif
                IsJavaScriptCSharpInteropSetUp = true;
            }

            string unmodifiedJavascript = javascript;

            // If the javascript code has references to previously obtained JavaScript objects, we replace those references with calls to the "document.jsSimulatorObjectReferences" dictionary.
            for (int i = variables.Length - 1; i >= 0; i--) // Note: we iterate in reverse order because, when we replace ""$" + i.ToString()", we need to replace "$10" before replacing "$1", otherwise it thinks that "$10" is "$1" followed by the number "0". To reproduce the issue, call "ExecuteJavaScript" passing 10 arguments and using "$10".
            {
                var variable = variables[i];
                if (variable is INTERNAL_JSObjectReference)
                {
                    //----------------------
                    // JS Object References
                    //----------------------

                    var    jsObjectReference = (INTERNAL_JSObjectReference)variable;
                    string jsCodeForAccessingTheObject;

                    if (jsObjectReference.IsArray)
                    {
                        jsCodeForAccessingTheObject = string.Format(@"document.jsSimulatorObjectReferences[""{0}""][{1}]", jsObjectReference.ReferenceId, jsObjectReference.ArrayIndex);
                    }
                    else
                    {
                        jsCodeForAccessingTheObject = string.Format(@"document.jsSimulatorObjectReferences[""{0}""]", 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(), string.Format(@"document.getElementById(""{0}"")", 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);

#if CSHTML5NETSTANDARD
                    //Console.WriteLine("Added ID: " + callbackId.ToString());
#endif

                    // Change the JS code to point to that callback:
                    javascript = javascript.Replace("$" + i.ToString(), string.Format(
                                                        @"(function() {{
                        var argsArray = Array.prototype.slice.call(arguments);
                        var idWhereCallbackArgsAreStored = ""callback_args_"" + Math.floor(Math.random() * 1000000);
                        document.jsSimulatorObjectReferences[idWhereCallbackArgsAreStored] = argsArray;
                        setTimeout(
                            function() 
                            {{
                               window.onCallBack.OnCallbackFromJavaScript({0}, idWhereCallbackArgsAreStored, argsArray);
                            }}
                            , 1);
                      }})", callbackId));

                    // 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 jsSimulatorObjectReferences 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));
                }
            }

            UnmodifiedJavascriptCalls.Add(unmodifiedJavascript);
            // Add the callback to the document:
            if (!CallbacksDictionary.ContainsKey(0))
            {
                CallbacksDictionary.Add(0, (Action <string, int>)ShowErrorMessage);
            }

#if CSHTML5NETSTANDARD
            //Console.WriteLine("Added ID: " + callbackId.ToString());
#endif

            // Change the JS code to call ShowErrorMessage in case of error:
            string errorCallBack = string.Format(
                @"var idWhereErrorCallbackArgsAreStored = ""callback_args_"" + Math.floor(Math.random() * 1000000);
                document.jsSimulatorObjectReferences[idWhereErrorCallbackArgsAreStored] = {0};
                var argsArr = [];
                argsArr[0] = error.message;
                argsArr[1] = {0};
window.onCallBack.OnCallbackFromJavaScript(0, idWhereErrorCallbackArgsAreStored, argsArr);", IndexOfNextUnmodifiedJSCallInList
                );
            ++IndexOfNextUnmodifiedJSCallInList;

            // Surround the javascript code with some code that will store the result into the "document.jsSimulatorObjectReferences" for later use in subsequent calls to this method:
            int referenceId = ReferenceIDGenerator.GenerateId();
            javascript = string.Format(
                @"
try {{
var result = eval(""{0}"");
document.jsSimulatorObjectReferences[""{1}""] = result;
result;
}}
catch (error) {{
    eval(""{2}"");
}}
result;
", INTERNAL_HtmlDomManager.EscapeStringForUseInJavaScript(javascript), referenceId, INTERNAL_HtmlDomManager.EscapeStringForUseInJavaScript(errorCallBack));

            // Execute the javascript code:
            object value = null;
            if (!runAsynchronously)
            {
                value = CastFromJsValue(INTERNAL_HtmlDomManager.ExecuteJavaScriptWithResult(javascript, noImpactOnPendingJSCode: noImpactOnPendingJSCode));
            }
            else
            {
                INTERNAL_HtmlDomManager.ExecuteJavaScript(javascript);
            }

            var objectReference = new INTERNAL_JSObjectReference()
            {
                Value       = value,
                ReferenceId = referenceId.ToString()
            };

            return(objectReference);
#else
            return(null);
#endif
        }
Exemplo n.º 2
0
        internal static object ExecuteJavaScript_SimulatorImplementation(string javascript, bool runAsynchronously, bool noImpactOnPendingJSCode = false, params object[] variables)
        {
            //---------------
            // Due to the fact that it is not possible to pass JavaScript objects between the simulator JavaScript context
            // and the C# context, we store the JavaScript objects in a global dictionary inside the JavaScript context.
            // This dictionary is named "jsObjRef". It associates a unique integer ID to each JavaScript
            // object. In C# we only manipulate those IDs by manipulating instances of the "JSObjectReference" class.
            // When we need to re-use those JavaScript objects, the C# code passes to the JavaScript context the ID
            // of the object, so that the JavaScript code can retrieve the JavaScript object instance by using the
            // aforementioned dictionary.
            //---------------

            // Verify the arguments:
            if (noImpactOnPendingJSCode && runAsynchronously)
            {
                throw new ArgumentException("You cannot set both 'noImpactOnPendingJSCode' and 'runAsynchronously' to True. The 'noImpactOnPendingJSCode' only has meaning when running synchronously.");
            }

            // Make sure the JS to C# interop is set up:
            if (!IsJavaScriptCSharpInteropSetUp)
            {
#if OPENSILVER
                if (Interop.IsRunningInTheSimulator_WorkAround)
                {
#endif
                // Adding a property to the JavaScript "window" object:
                dynamic jsWindow = INTERNAL_HtmlDomManager.ExecuteJavaScriptWithResult("window");
                jsWindow.SetProperty("onCallBack", new OnCallBack(CallbacksDictionary));
#if OPENSILVER
            }
            else
            {
                OnCallBack.SetCallbacksDictionary(CallbacksDictionary);
            }
#endif
                IsJavaScriptCSharpInteropSetUp = true;
            }

            string unmodifiedJavascript = javascript;

            // If the javascript code has references to previously obtained JavaScript objects,
            // we replace those references with calls to the "document.jsObjRef"
            // dictionary.
            // Note: we iterate in reverse order because, when we replace ""$" + i.ToString()", we
            // need to replace "$10" before replacing "$1", otherwise it thinks that "$10" is "$1"
            // followed by the number "0". To reproduce the issue, call "ExecuteJavaScript" passing
            // 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));
                }
            }

            UnmodifiedJavascriptCalls.Add(unmodifiedJavascript);

            // Change the JS code to call ShowErrorMessage in case of error:
            string errorCallBackId = IndexOfNextUnmodifiedJSCallInList.ToString();
            ++IndexOfNextUnmodifiedJSCallInList;

            // Surround the javascript code with some code that will store the
            // 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})";

            // Execute the javascript code:
            object value = null;
            if (!runAsynchronously)
            {
                value = CastFromJsValue(INTERNAL_HtmlDomManager.ExecuteJavaScriptWithResult(javascript, noImpactOnPendingJSCode: noImpactOnPendingJSCode));
            }
            else
            {
                INTERNAL_HtmlDomManager.ExecuteJavaScript(javascript);
            }

            var objectReference = new INTERNAL_JSObjectReference()
            {
                Value       = value,
                ReferenceId = referenceId.ToString()
            };

            return(objectReference);
        }