Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Sideload v1.33.0
Sideload.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; using System.Net.WebSockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AngleSharp.Dom; using AngleSharp.Html.Dom; using AngleSharp.Html.Parser; using Esprima; using Esprima.Ast; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Building; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.State; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Input; using Il2CppScheduleOne.UI.Phone; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using Jint; using Jint.Native; using Jint.Native.Function; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Sideload; using Sideload.Bundle; using Sideload.Config; using Sideload.Css; using Sideload.Devtools.Cdp; using Sideload.Dom; using Sideload.Host; using Sideload.Input; using Sideload.Layout; using Sideload.Model; using Sideload.Net; using Sideload.Paint; using Sideload.Phone; using Sideload.Profiling; using Sideload.Script; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Sideload", "1.33.0", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Sideload")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonOptionalDependencies(new string[] { "AngleSharp", "Jint", "Esprima" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("DooDesch")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © DooDesch")] [assembly: AssemblyFileVersion("1.33.0.0")] [assembly: AssemblyInformationalVersion("1.33.0+d80da31b51e4f5a7871540653b74c67308e16437")] [assembly: AssemblyProduct("Sideload")] [assembly: AssemblyTitle("Sideload")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.33.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DooDesch { internal static class ModVersion { internal const string Current = "1.33.0"; } } namespace Sideload { public class Core : MelonMod { internal static Instance Log; private static readonly string[] RuntimeDependencies = new string[3] { "AngleSharp", "Esprima", "Jint" }; private static readonly Dictionary<string, Assembly> _resolved = new Dictionary<string, Assembly>(StringComparer.OrdinalIgnoreCase); public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; HostAllowlist.Log = delegate(string line) { Log.Warning(line); }; Platform.LogMsg = delegate(string line) { Log.Msg(line); }; Platform.LogWarning = delegate(string line) { Log.Warning(line); }; Platform.LogError = delegate(string line) { Log.Error(line); }; Platform.UserDataDirectory = () => MelonEnvironment.UserDataDirectory; Platform.IsPortrait = (string id) => Registry.Find(id)?.Portrait ?? false; Platform.SetOrientation = Registry.SetOrientation; AppDomain.CurrentDomain.AssemblyResolve += ResolveRuntimeDependency; PreloadRuntimeDependency("AngleSharp.dll"); PreloadRuntimeDependency("Esprima.dll"); PreloadRuntimeDependency("Jint.dll"); WarmUp(); try { ((MelonBase)this).HarmonyInstance.PatchAll(); Log.Msg("patches applied."); } catch (Exception ex) { Log.Error("PatchAll failed - no app will reach the phone: " + ex); } TmpCaretGuard.Apply(((MelonBase)this).HarmonyInstance); Preferences.Initialize(); if (Preferences.DevTools) { CdpServer.Start(Preferences.DevToolsPort); } } private static void WarmUp() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown try { Stopwatch stopwatch = Stopwatch.StartNew(); IHtmlDocument val = new HtmlParser().ParseDocument("<style>b{color:#fff;padding:1px}</style><div id=w><b>x</b></div>"); CssParser.Parse("b{color:#fff;padding:1px}"); Engine val2 = new Engine(); val2.Execute("var a=[1,2,3].map(function(v){return v*2;}).join(',');", (string)null); Log.Msg($"warmed the parsers in {stopwatch.ElapsedMilliseconds} ms (document has {((IHtmlCollection<IElement>)(object)((IDocument)val).All).Length} node(s), script returned {val2.Evaluate("a", (string)null)})."); } catch (Exception ex) { Log.Warning("warming the parsers failed, the first page will pay for it: " + ex.Message); } } public override void OnUpdate() { using (Phase.Of("sideload.views")) { WebView.TickAll(Time.deltaTime); } using (Phase.Of("sideload.turninput")) { TurnInput.Tick(); } using (Phase.Of("sideload.globalkeys")) { GlobalKeys.Tick(); } using (Phase.Of("sideload.typingexit")) { TypingExit.Tick(); } using (Phase.Of("sideload.appfade")) { AppFade.Tick(Time.unscaledDeltaTime); } using (Phase.Of("sideload.cdp")) { CdpServer.Pump(); } } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { TurnPrompt.SceneChanged(); } public override void OnDeinitializeMelon() { CdpServer.Stop(); ChromeLauncher.Close(); } private static Assembly ResolveRuntimeDependency(object sender, ResolveEventArgs args) { string name; try { name = new AssemblyName(args.Name).Name; } catch { return null; } if (_resolved.TryGetValue(name, out var value)) { return value; } if (Array.IndexOf(RuntimeDependencies, name) < 0) { return null; } Assembly assembly = LoadEmbeddedAssembly(name + ".dll"); if (assembly == null) { return null; } _resolved[name] = assembly; Log.Msg($"resolved {name} from the embedded copy (requested by {args.RequestingAssembly?.GetName().Name ?? "the runtime"})"); return assembly; } private static void PreloadRuntimeDependency(string fileName) { try { string text = Path.Combine(MelonEnvironment.UserLibsDirectory, fileName); if (!File.Exists(text)) { string text2 = Path.Combine(Path.GetDirectoryName(typeof(Core).Assembly.Location) ?? MelonEnvironment.ModsDirectory, fileName); text = ((!File.Exists(text2)) ? null : text2); } Assembly assembly = ((text != null) ? Assembly.LoadFrom(text) : LoadEmbeddedAssembly(fileName)); if (assembly == null) { Log.Error("runtime dependency " + fileName + " is neither on disk nor embedded - no app will render."); return; } AssemblyName name = assembly.GetName(); _resolved[name.Name] = assembly; Log.Msg($"preloaded {name.Name} {name.Version} {((text != null) ? ("from disk (" + text + ")") : "(embedded)")}"); } catch (Exception ex) { Log.Warning("preloading " + fileName + " failed: " + ex.Message); } } private static Assembly LoadEmbeddedAssembly(string fileName) { string text = "Sideload.Libs." + fileName; using Stream stream = typeof(Core).Assembly.GetManifestResourceStream(text); if (stream == null) { Log.Warning("embedded dependency '" + text + "' is missing from this build."); return null; } byte[] array = new byte[stream.Length]; int i; int num; for (i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } if (i != array.Length) { Log.Warning($"embedded dependency '{text}' is truncated ({i} of {array.Length} bytes)."); return null; } return Assembly.Load(array); } } internal sealed class AppRegistration { internal string Id; internal string Title; internal string IconLabel; internal string BundlePrefix; internal Assembly HostAssembly; internal AppBundle Bundle; internal Sprite IconSprite; internal bool Portrait; internal bool DeclaredPortrait; internal bool CanTurn; internal int Badge; internal bool Iconless; internal Action OrientationChanged; internal bool Supports(bool portrait) { if (!CanTurn) { return DeclaredPortrait == portrait; } return true; } } internal static class Registry { private static readonly List<AppRegistration> _apps = new List<AppRegistration>(); internal static Action<string, int> BadgeTap; internal static Action<string, string, string> NotifyTap; internal static IReadOnlyList<AppRegistration> Apps => _apps; internal static void RegisterApp(string id, string title, string iconLabel, string bundlePrefix, Assembly host) { if (string.IsNullOrWhiteSpace(id) || host == null) { return; } AppRegistration appRegistration = new AppRegistration { Id = id.Trim(), Title = (string.IsNullOrWhiteSpace(title) ? id : title), IconLabel = ((!string.IsNullOrWhiteSpace(iconLabel)) ? iconLabel : (string.IsNullOrWhiteSpace(title) ? id : title)), BundlePrefix = (bundlePrefix ?? ""), HostAssembly = host }; appRegistration.Bundle = new AppBundle(appRegistration.Id, appRegistration.BundlePrefix, host); for (int i = 0; i < _apps.Count; i++) { if (string.Equals(_apps[i].Id, appRegistration.Id, StringComparison.OrdinalIgnoreCase)) { _apps[i] = appRegistration; Instance log = Core.Log; if (log != null) { log.Warning("app '" + appRegistration.Id + "' registered twice; the later registration wins."); } return; } } _apps.Add(appRegistration); Instance log2 = Core.Log; if (log2 != null) { log2.Msg($"app registered: {appRegistration.Id} ('{appRegistration.Title}') from {host.GetName().Name}"); } } internal static void DeclareOrientations(string appId, string orientations) { AppRegistration appRegistration = Find(appId); if (appRegistration == null) { Instance log = Core.Log; if (log != null) { log.Warning("orientation: no app '" + appId + "'."); } return; } OrientationSet orientationSet = OrientationSet.Parse(orientations); foreach (string item in orientationSet.Ignored) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"'{appId}' declared orientation '{item}', which is neither 'portrait' " + "nor 'landscape' - ignored."); } } if (orientationSet.Declared) { appRegistration.DeclaredPortrait = orientationSet.Portrait; appRegistration.CanTurn = orientationSet.CanTurn; appRegistration.Portrait = orientationSet.Portrait; } } internal static void SetOrientation(string appId, string orientation) { AppRegistration appRegistration = Find(appId); if (appRegistration == null) { Instance log = Core.Log; if (log != null) { log.Warning("setOrientation: no app '" + appId + "'."); } return; } bool flag = string.Equals((orientation ?? "").Trim(), "portrait", StringComparison.OrdinalIgnoreCase); if (appRegistration.Portrait == flag) { return; } if (!appRegistration.Supports(flag)) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"'{appRegistration.Id}' was asked for {(flag ? "portrait" : "landscape")} but " + "only declared the other one - ignored. Declare both to allow turning: Apps.Register(...).Orientation(\"landscape\", \"portrait\")."); } } else { appRegistration.Portrait = flag; appRegistration.OrientationChanged?.Invoke(); } } internal static void SetBadge(string appId, int count) { AppRegistration appRegistration = Find(appId); if (appRegistration == null) { Instance log = Core.Log; if (log != null) { log.Warning("badge: no app '" + appId + "'."); } } else { appRegistration.Badge = Math.Max(0, count); BadgeTap?.Invoke(appRegistration.Id, appRegistration.Badge); LiveHost(appId)?.SetBadge(appRegistration.Badge); } } internal static void Notify(string appId, string title, string subtitle) { NotifyFor(appId, title, subtitle, 0f); } internal static void NotifyFor(string appId, string title, string subtitle, float seconds) { NotifyTap?.Invoke(appId, title ?? "", subtitle ?? ""); GlobalKeys.Attend(appId); LiveHost(appId)?.Notify(title, subtitle, seconds); } internal static void ClaimKeys(string appId, string declaration, Func<string, string, bool> handler) { GlobalKeys.Claim(appId, declaration, handler); } internal static bool IsOnScreen(string appId) { return LiveHost(appId)?.IsShowing ?? false; } internal static void SetIconHidden(string appId, bool hidden) { AppRegistration appRegistration = Find(appId); if (appRegistration == null) { Instance log = Core.Log; if (log != null) { log.Warning("icon: no app '" + appId + "'."); } } else { appRegistration.Iconless = hidden; LiveHost(appId)?.SetIconVisible(!hidden); } } internal static void SetAppOpen(string appId, bool open) { PhoneAppHost phoneAppHost = LiveHost(appId); if (phoneAppHost == null) { Instance log = Core.Log; if (log != null) { log.Warning("open: '" + appId + "' is not on a phone."); } } else if (open) { phoneAppHost.Open(); } else { phoneAppHost.Close(); } } internal static bool SetPhoneRaised(bool raised) { if (!raised) { return PhoneScreen.Lower(); } return PhoneScreen.Raise(); } internal static bool IsPhoneRaised() { return PhoneScreen.IsRaised; } internal static bool IsAppOpen(string appId) { return LiveHost(appId)?.IsOpen ?? false; } private static PhoneAppHost LiveHost(string appId) { IReadOnlyList<PhoneAppHost> hosts = HomeScreenPatch.Hosts; for (int i = 0; i < hosts.Count; i++) { if (string.Equals(hosts[i].Id, appId, StringComparison.OrdinalIgnoreCase) && hosts[i].IsAlive) { return hosts[i]; } } return null; } internal static AppRegistration Find(string appId) { if (string.IsNullOrWhiteSpace(appId)) { return null; } for (int i = 0; i < _apps.Count; i++) { if (string.Equals(_apps[i].Id, appId, StringComparison.OrdinalIgnoreCase)) { return _apps[i]; } } return null; } } } namespace Sideload.Script { public sealed class Bridge { public sealed class JsStorage { private readonly string _path; private readonly Dictionary<string, string> _values = new Dictionary<string, string>(StringComparer.Ordinal); private bool _loaded; public int Length { get { Load(); return _values.Count; } } internal JsStorage(string appId) { string path = Path.Combine(Platform.UserDataDirectory(), "Sideload"); _path = Path.Combine(path, Sanitise(appId) + ".json"); } public string Get(string key, string fallback = "") { Load(); if (key == null || !_values.TryGetValue(key, out var value)) { return fallback; } return value; } public void Set(string key, string value) { if (!string.IsNullOrEmpty(key)) { Load(); _values[key] = value ?? ""; Save(); } } public void Remove(string key) { if (!string.IsNullOrEmpty(key)) { Load(); if (_values.Remove(key)) { Save(); } } } public void Clear() { Load(); _values.Clear(); Save(); } public string GetItem(string key) { Load(); if (key == null || !_values.TryGetValue(key, out var value)) { return null; } return value; } public void SetItem(string key, string value) { Set(key, value); } public void RemoveItem(string key) { Remove(key); } public string Key(int index) { Load(); if (index < 0 || index >= _values.Count) { return null; } int num = 0; foreach (string key in _values.Keys) { if (num++ == index) { return key; } } return null; } private void Load() { if (_loaded) { return; } _loaded = true; try { if (!File.Exists(_path)) { return; } foreach (KeyValuePair<string, string> item in MiniJson.ParseObject(File.ReadAllText(_path))) { _values[item.Key] = item.Value; } } catch (Exception ex) { Platform.Warning("storage read failed: " + ex.Message); } } private void Save() { try { Directory.CreateDirectory(Path.GetDirectoryName(_path)); File.WriteAllText(_path, MiniJson.WriteObject(_values)); } catch (Exception ex) { Platform.Warning("storage write failed: " + ex.Message); } } private static string Sanitise(string id) { StringBuilder stringBuilder = new StringBuilder(id?.Length ?? 0); string text = id ?? "app"; foreach (char c in text) { stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '_') ? c : '-'); } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "app"; } } private static readonly Dictionary<string, Func<string, string, string>> _handlers = new Dictionary<string, Func<string, string, string>>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, List<Bridge>> _subscribers = new Dictionary<string, List<Bridge>>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, List<JsValue>> _listeners = new Dictionary<string, List<JsValue>>(StringComparer.OrdinalIgnoreCase); private readonly ScriptHost _host; private readonly string _appId; internal static Action<string, string, string> Tap = null; public JsStorage Storage { get; } public string AppId => _appId; public string Orientation { get { if (!Platform.IsPortrait(_appId)) { return "landscape"; } return "portrait"; } } internal Bridge(ScriptHost host, string appId) { _host = host; _appId = appId; Storage = new JsStorage(appId); } internal static void Handle(string appId, string name, Func<string, string, string> handler) { if (!string.IsNullOrWhiteSpace(name) && handler != null) { _handlers[Key(appId, name)] = handler; } } internal static void Emit(string appId, string name, string payload) { if (string.IsNullOrWhiteSpace(name)) { return; } Tap?.Invoke(appId, name, payload ?? ""); if (!_subscribers.TryGetValue(name, out var value)) { return; } Bridge[] array = value.ToArray(); foreach (Bridge bridge in array) { if (appId == null || string.Equals(bridge._appId, appId, StringComparison.OrdinalIgnoreCase)) { bridge.Deliver(name, payload ?? ""); } } } internal static string Invoke(string appId, string name, string argument) { if (string.IsNullOrWhiteSpace(name)) { return ""; } if (!_handlers.TryGetValue(Key(appId, name), out var value) && !_handlers.TryGetValue(Key(null, name), out value)) { Platform.Warning(appId + ": s1.call('" + name + "') has no handler."); return ""; } try { return value(appId, argument ?? "") ?? ""; } catch (Exception ex) { Platform.Error($"{appId}: s1.call('{name}') threw: {ex.Message}"); return ""; } } private static string Key(string appId, string name) { return appId + "\0" + name.Trim(); } public string Call(string name, string argument = "") { try { return Invoke(_appId, name, argument); } finally { _host?.RestartBudget(); } } public void On(string name, JsValue handler) { if (!string.IsNullOrWhiteSpace(name) && !(handler == (JsValue)null) && JsValueExtensions.IsObject(handler)) { if (!_listeners.TryGetValue(name, out var value)) { value = (_listeners[name] = new List<JsValue>()); } value.Add(handler); if (!_subscribers.TryGetValue(name, out var value2)) { value2 = (_subscribers[name] = new List<Bridge>()); } if (!value2.Contains(this)) { value2.Add(this); } } } public void Log(params object[] args) { Platform.Msg("[" + _appId + "] " + string.Join(" ", Array.ConvertAll(args ?? Array.Empty<object>(), (object a) => a?.ToString() ?? "null"))); } public void SetOrientation(string orientation) { Platform.SetOrientation(_appId, orientation); } internal void Dispose() { foreach (KeyValuePair<string, List<Bridge>> subscriber in _subscribers) { subscriber.Value.Remove(this); } _listeners.Clear(); } private void Deliver(string name, string payload) { if (!_listeners.TryGetValue(name, out var value)) { return; } JsValue[] array = value.ToArray(); foreach (JsValue val in array) { try { _host.Engine.Invoke(val, new object[1] { payload }); } catch (Exception ex) { Platform.Error($"{_appId}: s1.on('{name}') handler failed: {ex.Message}"); } } } } public abstract class JsNode : ObjectInstance { internal ScriptHost Host { get; } internal INode Node { get; } private protected abstract int NodeTypeCode { get; } private protected abstract string NodeName { get; } private protected virtual JsValue NodeValue => JsValue.Null; internal JsNode(ScriptHost host, INode node) : base(host.Engine) { Host = host; Node = node; ObjectInstance val = Interface(host.Types); if ((JsValue)(object)val != (JsValue)null) { ((ObjectInstance)this).Prototype = val; } } private protected virtual ObjectInstance Interface(DomTypes types) { return types?.Node; } public override JsValue Get(JsValue property, JsValue receiver) { if (!JsValueExtensions.IsString(property)) { return ((ObjectInstance)this).Get(property, receiver); } string name = JsValueExtensions.AsString(property); JsValue val = Member(name); if (val != null) { return val; } Func<JsValue[], JsValue> func = Method(name); if (func != null) { return Bind(name, func); } return ((ObjectInstance)this).Get(property, receiver); } public override bool Set(JsValue property, JsValue value, JsValue receiver) { if (JsValueExtensions.IsString(property) && Assign(JsValueExtensions.AsString(property), value)) { return true; } return ((ObjectInstance)this).Set(property, value, receiver); } public override bool HasProperty(JsValue property) { if (JsValueExtensions.IsString(property)) { string name = JsValueExtensions.AsString(property); if (Member(name) != null || Method(name) != null) { return true; } } return ((ObjectInstance)this).HasProperty(property); } private protected virtual JsValue Member(string name) { return (JsValue)(name switch { "nodeType" => JsValue.op_Implicit(NodeTypeCode), "nodeName" => JsValue.op_Implicit(NodeName), "nodeValue" => NodeValue, "textContent" => JsValue.op_Implicit(Node.TextContent ?? ""), "parentNode" => Wrap(Node.Parent), "parentElement" => Wrap((INode)(object)Node.ParentElement), "ownerDocument" => Host.DocumentObject, "isConnected" => JsValue.op_Implicit(Node.Owner != null && Node.Parent != null), "firstChild" => Wrap(Node.FirstChild), "lastChild" => Wrap(Node.LastChild), "nextSibling" => Wrap(Node.NextSibling), "previousSibling" => Wrap(Node.PreviousSibling), "childNodes" => Host.WrapNodes((IEnumerable<INode>)Node.ChildNodes), _ => null, }); } private protected virtual bool Assign(string name, JsValue value) { if (name == "nodeValue" || name == "textContent") { SetText(Text(value)); return true; } return false; } private protected virtual Func<JsValue[], JsValue> Method(string name) { return name switch { "appendChild" => (JsValue[] a) => Insert(Arg(a, 0), null, append: true), "insertBefore" => (JsValue[] a) => Insert(Arg(a, 0), Arg(a, 1), append: false), "removeChild" => (JsValue[] a) => Detach(Arg(a, 0)), "replaceChild" => (JsValue[] a) => Replace(Arg(a, 0), Arg(a, 1)), "remove" => delegate { DetachSelf(); return JsValue.Undefined; }, "cloneNode" => (JsValue[] a) => Clone(a.Length != 0 && TypeConverter.ToBoolean(a[0])), "contains" => (JsValue[] a) => Contains(Arg(a, 0)), "hasChildNodes" => (JsValue[] _) => JsValue.op_Implicit(Node.ChildNodes.Length > 0), "addEventListener" => delegate(JsValue[] a) { Host.AddListener(Node, (a.Length != 0) ? ((object)a[0]).ToString() : null, (a.Length > 1) ? a[1] : null, Capture(a, 2)); return JsValue.Undefined; }, "removeEventListener" => delegate(JsValue[] a) { Host.RemoveListener(Node, (a.Length != 0) ? ((object)a[0]).ToString() : null, (a.Length > 1) ? a[1] : null, Capture(a, 2)); return JsValue.Undefined; }, "dispatchEvent" => (JsValue[] a) => Host.DispatchFromScript(Node, (ObjectInstance)(((object)Arg(a, 0)) ?? ((object)((a.Length != 0) ? /*isinst with value type is only supported in some contexts*/: null)))), _ => null, }; } private protected virtual void SetText(string text) { if (!string.Equals(Node.TextContent ?? "", text, StringComparison.Ordinal)) { Node.TextContent = text; INode node = Node; IElement val = (IElement)(object)((node is IElement) ? node : null); if (val == null || !Host.TryRetext(val, text)) { Host.MarkDirty(); } } } private JsValue Insert(JsNode child, JsNode reference, bool append) { if ((JsValue)(object)child == (JsValue)null) { return JsValue.Null; } if (append || (JsValue)(object)reference == (JsValue)null) { Node.AppendChild(child.Node); } else { Node.InsertBefore(child.Node, reference.Node); } Host.MarkDirty(); return (JsValue)(object)child; } private JsValue Detach(JsNode child) { if ((JsValue)(object)child == (JsValue)null) { return JsValue.Null; } Node.RemoveChild(child.Node); Host.Forget(child.Node); Host.MarkDirty(); return (JsValue)(object)child; } private JsValue Replace(JsNode fresh, JsNode stale) { if ((JsValue)(object)fresh == (JsValue)null || (JsValue)(object)stale == (JsValue)null) { return JsValue.Null; } Node.ReplaceChild(fresh.Node, stale.Node); Host.Forget(stale.Node); Host.MarkDirty(); return (JsValue)(object)stale; } private void DetachSelf() { INode parent = Node.Parent; if (parent != null) { parent.RemoveChild(Node); } Host.Forget(Node); Host.MarkDirty(); } private JsValue Clone(bool deep) { INode val = Node.Clone(deep); if (val != null) { return Wrap(val); } return JsValue.Null; } private JsValue Contains(JsNode other) { if ((JsValue)(object)other == (JsValue)null) { return JsValue.op_Implicit(false); } for (INode val = other.Node; val != null; val = val.Parent) { if (val == Node) { return JsValue.op_Implicit(true); } } return JsValue.op_Implicit(false); } private protected JsValue Wrap(INode node) { return (JsValue)(((object)Host.WrapNode(node)) ?? ((object)JsValue.Null)); } private protected JsNode Arg(JsValue[] args, int index) { if (index >= args.Length) { return null; } return args[index] as JsNode; } private static bool Capture(JsValue[] args, int index) { if (index >= args.Length) { return false; } JsValue val = args[index]; if (JsValueExtensions.IsBoolean(val)) { return JsValueExtensions.AsBoolean(val); } ObjectInstance val2 = (ObjectInstance)(object)((val is ObjectInstance) ? val : null); if (val2 != null) { JsValue val3 = ((JsValue)val2).Get(JsValue.op_Implicit("capture")); if (!JsValueExtensions.IsUndefined(val3)) { return TypeConverter.ToBoolean(val3); } return false; } return false; } private protected static string Text(JsValue value) { if (!JsValueExtensions.IsNull(value) && !JsValueExtensions.IsUndefined(value)) { return ((object)value).ToString(); } return ""; } private JsValue Bind(string name, Func<JsValue[], JsValue> body) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown PropertyDescriptor ownProperty = ((ObjectInstance)this).GetOwnProperty(JsValue.op_Implicit(name)); if (ownProperty != PropertyDescriptor.Undefined) { return ownProperty.Value; } ClrFunction val = new ClrFunction(((ObjectInstance)this).Engine, name, (Func<JsValue, JsValue[], JsValue>)((JsValue _, JsValue[] args) => body(args)), 0, (PropertyFlag)42); ((ObjectInstance)this).FastSetProperty(name, new PropertyDescriptor((JsValue)(object)val, (bool?)true, (bool?)false, (bool?)true)); return (JsValue)(object)val; } } public sealed class JsElement : JsNode { private static readonly HashSet<string> StringProperties = new HashSet<string>(StringComparer.Ordinal) { "value", "type", "name", "placeholder", "src", "href", "alt", "title", "rel", "target", "maxLength", "step", "min", "max", "accept", "pattern", "role" }; private static readonly HashSet<string> BoolProperties = new HashSet<string>(StringComparer.Ordinal) { "disabled", "checked", "selected", "required", "multiple", "hidden", "autofocus", "open" }; internal IElement Native { get; } private protected override int NodeTypeCode => 1; private protected override string NodeName => Native.LocalName?.ToUpperInvariant() ?? ""; private int TabIndex { get { if (!int.TryParse(Native.GetAttribute("tabindex"), out var result)) { return -1; } return result; } } internal JsElement(ScriptHost host, IElement element) : base(host, (INode)(object)element) { Native = element; } private protected override ObjectInstance Interface(DomTypes types) { if (types == null) { return null; } INode node = base.Node; INode obj = ((node is IElement) ? node : null); return types.For((obj != null) ? ((IElement)obj).LocalName : null); } public override string ToString() { return "<" + Native.LocalName + ">"; } private protected override JsValue Member(string name) { switch (name) { case "tagName": return JsValue.op_Implicit(NodeName); case "localName": return JsValue.op_Implicit(Native.LocalName ?? ""); case "id": return JsValue.op_Implicit(Native.Id ?? ""); case "className": return JsValue.op_Implicit(Native.ClassName ?? ""); case "classList": return JsValue.FromObject(((ObjectInstance)this).Engine, (object)new JsClassList(base.Host, Native)); case "style": return (JsValue)(object)new JsStyle(base.Host, Native); case "innerHTML": return JsValue.op_Implicit(Native.InnerHtml ?? ""); case "outerHTML": return JsValue.op_Implicit(Native.OuterHtml ?? ""); case "value": return JsValue.op_Implicit(Native.GetAttribute("value") ?? ""); case "children": return base.Host.WrapNodes((IEnumerable<IElement>)((IParentNode)Native).Children); case "childElementCount": return JsValue.op_Implicit(((IParentNode)Native).ChildElementCount); case "firstElementChild": return Wrap((INode)(object)((IParentNode)Native).FirstElementChild); case "lastElementChild": return Wrap((INode)(object)((IParentNode)Native).LastElementChild); case "nextElementSibling": return Wrap((INode)(object)((INonDocumentTypeChildNode)Native).NextElementSibling); case "previousElementSibling": return Wrap((INode)(object)((INonDocumentTypeChildNode)Native).PreviousElementSibling); case "tabIndex": return JsValue.op_Implicit(TabIndex); case "htmlFor": return JsValue.op_Implicit(Native.GetAttribute("for") ?? ""); case "readOnly": return JsValue.op_Implicit(Native.HasAttribute("readonly")); case "namespaceURI": return JsValue.op_Implicit(Native.NamespaceUri) ?? JsValue.Null; default: if (StringProperties.Contains(name)) { return JsValue.op_Implicit(Native.GetAttribute(Attribute(name)) ?? ""); } if (BoolProperties.Contains(name)) { return JsValue.op_Implicit(Native.HasAttribute(Attribute(name))); } if (IsHandlerName(name)) { return base.Host.InlineHandler((INode)(object)Native, name.Substring(2)); } return base.Member(name); } } private protected override bool Assign(string name, JsValue value) { switch (name) { case "id": return Write("id", JsNode.Text(value)); case "className": return Write("class", JsNode.Text(value)); case "htmlFor": return Write("for", JsNode.Text(value)); case "readOnly": return Flag("readonly", TypeConverter.ToBoolean(value)); case "tabIndex": return Write("tabindex", JsNode.Text(value)); case "innerHTML": if (string.Equals(Native.InnerHtml ?? "", JsNode.Text(value), StringComparison.Ordinal)) { return true; } foreach (INode item in (IEnumerable<INode>)((INode)Native).ChildNodes) { base.Host.Forget(item); } Native.InnerHtml = JsNode.Text(value); base.Host.MarkDirty(); return true; case "textContent": foreach (INode item2 in (IEnumerable<INode>)((INode)Native).ChildNodes) { base.Host.Forget(item2); } SetText(JsNode.Text(value)); return true; default: if (StringProperties.Contains(name)) { return Write(Attribute(name), JsNode.Text(value)); } if (BoolProperties.Contains(name)) { return Flag(Attribute(name), TypeConverter.ToBoolean(value)); } if (IsHandlerName(name)) { base.Host.SetInlineHandler((INode)(object)Native, name.Substring(2), value); return true; } return base.Assign(name, value); } } private protected override Func<JsValue[], JsValue> Method(string name) { switch (name) { case "getAttribute": return (JsValue[] a) => JsValue.op_Implicit(Native.GetAttribute(Name(a, 0))) ?? JsValue.Null; case "hasAttribute": return (JsValue[] a) => JsValue.op_Implicit(Native.HasAttribute(Name(a, 0))); case "setAttribute": return delegate(JsValue[] a) { Write(Name(a, 0), (a.Length > 1) ? JsNode.Text(a[1]) : ""); return JsValue.Undefined; }; case "removeAttribute": return delegate(JsValue[] a) { Erase(Name(a, 0)); return JsValue.Undefined; }; case "querySelector": return (JsValue[] a) => Wrap((INode)(object)Query(() => ((IParentNode)Native).QuerySelector(Name(a, 0)))); case "querySelectorAll": return (JsValue[] a) => base.Host.WrapNodes(QueryAll(() => (IEnumerable<IElement>)((IParentNode)Native).QuerySelectorAll(Name(a, 0)))); case "matches": return (JsValue[] a) => JsValue.op_Implicit(Query(() => (!Native.Matches(Name(a, 0))) ? null : Native) != null); case "closest": return (JsValue[] a) => Wrap((INode)(object)Query(() => Native.Closest(Name(a, 0)))); case "replaceChildren": return delegate { ReplaceChildren(); return JsValue.Undefined; }; case "click": return delegate { base.Host.Dispatch(Native, "click"); return JsValue.Undefined; }; case "focus": return delegate { base.Host.RequestFocus(Native); return JsValue.Undefined; }; case "blur": return delegate { base.Host.RequestBlur(Native); return JsValue.Undefined; }; case "scrollToEnd": return delegate { base.Host.RequestScrollToEnd(Native); return JsValue.Undefined; }; case "rect": case "getBoundingClientRect": return (JsValue[] _) => JsValue.FromObject(((ObjectInstance)this).Engine, (object)Rect()); default: return base.Method(name); } } private JsRect Rect() { float[] array = base.Host.RectOf(Native); return new JsRect { x = array[0], y = array[1], width = array[2], height = array[3], left = array[0], top = array[1], right = array[0] + array[2], bottom = array[1] + array[3] }; } private void ReplaceChildren() { foreach (INode item in (IEnumerable<INode>)((INode)Native).ChildNodes) { base.Host.Forget(item); } Native.InnerHtml = ""; base.Host.MarkDirty(); } private bool Write(string attribute, string value) { if (string.IsNullOrEmpty(attribute)) { return true; } if (string.Equals(Native.GetAttribute(attribute) ?? "", value, StringComparison.Ordinal)) { return true; } Native.SetAttribute(attribute, value ?? ""); base.Host.MarkDirty(); return true; } private bool Flag(string attribute, bool on) { if (Native.HasAttribute(attribute) == on) { return true; } if (on) { Native.SetAttribute(attribute, ""); } else { Native.RemoveAttribute(attribute); } base.Host.MarkDirty(); return true; } private void Erase(string attribute) { if (!string.IsNullOrEmpty(attribute) && Native.HasAttribute(attribute)) { Native.RemoveAttribute(attribute); base.Host.MarkDirty(); } } private static IElement Query(Func<IElement> run) { try { return run(); } catch { return null; } } private static IEnumerable<IElement> QueryAll(Func<IEnumerable<IElement>> run) { try { return run(); } catch { return Array.Empty<IElement>(); } } private static string Name(JsValue[] args, int index) { if (index >= args.Length) { return ""; } return ((object)args[index]).ToString(); } private static bool IsHandlerName(string name) { if (name.Length < 4 || name[0] != 'o' || name[1] != 'n') { return false; } for (int i = 2; i < name.Length; i++) { if (name[i] < 'a' || name[i] > 'z') { return false; } } return true; } private static string Attribute(string property) { return property switch { "className" => "class", "htmlFor" => "for", "readOnly" => "readonly", "tabIndex" => "tabindex", "maxLength" => "maxlength", _ => property, }; } } public sealed class JsText : JsNode { internal IText Native { get; } private protected override int NodeTypeCode => 3; private protected override string NodeName => "#text"; private protected override JsValue NodeValue => JsValue.op_Implicit(((ICharacterData)Native).Data ?? ""); internal JsText(ScriptHost host, IText text) : base(host, (INode)(object)text) { Native = text; } private protected override ObjectInstance Interface(DomTypes types) { return types?.Text; } public override string ToString() { return ((ICharacterData)Native).Data ?? ""; } private protected override JsValue Member(string name) { return (JsValue)(name switch { "data" => JsValue.op_Implicit(((ICharacterData)Native).Data ?? ""), "length" => JsValue.op_Implicit((((ICharacterData)Native).Data ?? "").Length), "wholeText" => JsValue.op_Implicit(((ICharacterData)Native).Data ?? ""), _ => base.Member(name), }); } private protected override bool Assign(string name, JsValue value) { if (name != "data") { return base.Assign(name, value); } SetText(JsNode.Text(value)); return true; } } public sealed class JsComment : JsNode { internal IComment Native { get; } private protected override int NodeTypeCode => 8; private protected override string NodeName => "#comment"; private protected override JsValue NodeValue => JsValue.op_Implicit(((ICharacterData)Native).Data ?? ""); internal JsComment(ScriptHost host, IComment comment) : base(host, (INode)(object)comment) { Native = comment; } private protected override ObjectInstance Interface(DomTypes types) { return types?.Comment; } public override string ToString() { return "<!--" + ((ICharacterData)Native).Data + "-->"; } private protected override JsValue Member(string name) { if (!(name == "data")) { return base.Member(name); } return JsValue.op_Implicit(((ICharacterData)Native).Data ?? ""); } private protected override bool Assign(string name, JsValue value) { if (name != "data") { return base.Assign(name, value); } SetText(JsNode.Text(value)); return true; } } public sealed class JsClassList { private readonly ScriptHost _host; private readonly IElement _element; public int Length => _element.ClassList.Length; internal JsClassList(ScriptHost host, IElement element) { _host = host; _element = element; } public bool Contains(string name) { return _element.ClassList.Contains(name); } public void Add(string name) { _element.ClassList.Add(new string[1] { name }); _host.MarkDirty(); } public void Remove(string name) { _element.ClassList.Remove(new string[1] { name }); _host.MarkDirty(); } public bool Toggle(string name) { bool flag = !_element.ClassList.Contains(name); if (flag) { _element.ClassList.Add(new string[1] { name }); } else { _element.ClassList.Remove(new string[1] { name }); } _host.MarkDirty(); return flag; } } public sealed class JsStyle : ObjectInstance { private const string Attribute = "style"; private readonly ScriptHost _host; private readonly IElement _element; internal JsStyle(ScriptHost host, IElement element) : base(host.Engine) { _host = host; _element = element; } public override JsValue Get(JsValue property, JsValue receiver) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Expected O, but got Unknown string text = (JsValueExtensions.IsString(property) ? JsValueExtensions.AsString(property) : null); if (text == null) { return JsValue.Undefined; } switch (text) { case "cssText": return JsValue.op_Implicit(_element.GetAttribute("style") ?? ""); case "setProperty": return (JsValue)new ClrFunction(((ObjectInstance)this).Engine, text, (Func<JsValue, JsValue[], JsValue>)delegate(JsValue _, JsValue[] a) { Apply((a.Length != 0) ? Kebab(((object)a[0]).ToString()) : "", (a.Length > 1) ? Str(a[1]) : ""); return JsValue.Undefined; }, 0, (PropertyFlag)42); case "removeProperty": return (JsValue)new ClrFunction(((ObjectInstance)this).Engine, text, (Func<JsValue, JsValue[], JsValue>)delegate(JsValue _, JsValue[] a) { string text3 = ((a.Length != 0) ? Kebab(((object)a[0]).ToString()) : ""); JsValue result = JsValue.op_Implicit(Read(text3)) ?? JsValue.op_Implicit(""); Apply(text3, ""); return result; }, 0, (PropertyFlag)42); case "getPropertyValue": return (JsValue)new ClrFunction(((ObjectInstance)this).Engine, text, (Func<JsValue, JsValue[], JsValue>)((JsValue _, JsValue[] a) => JsValue.op_Implicit(Read((a.Length != 0) ? Kebab(((object)a[0]).ToString()) : "")) ?? JsValue.op_Implicit("")), 0, (PropertyFlag)42); default: { string text2 = Read(Kebab(text)); if (text2 == null) { return JsValue.op_Implicit(""); } return JsValue.op_Implicit(text2); } } } public override bool Set(JsValue property, JsValue value, JsValue receiver) { if (!JsValueExtensions.IsString(property)) { return false; } string text = JsValueExtensions.AsString(property); Apply((text == "cssText") ? null : Kebab(text), Str(value)); return true; } public override bool HasProperty(JsValue property) { if (JsValueExtensions.IsString(property)) { if (!(JsValueExtensions.AsString(property) == "cssText")) { return Read(Kebab(JsValueExtensions.AsString(property))) != null; } return true; } return false; } private static string Str(JsValue value) { if (!JsValueExtensions.IsNull(value) && !JsValueExtensions.IsUndefined(value)) { return ((object)value).ToString(); } return ""; } private void Apply(string css, string text) { if (!string.Equals(((css == null) ? _element.GetAttribute("style") : Read(css)) ?? "", text, StringComparison.Ordinal)) { Write(css, text); if (PaintOnlyProperties.Covers(css)) { _host.MarkPaintDirty(_element); } else { _host.MarkDirty(); } } } private string Read(string property) { if (string.IsNullOrEmpty(property)) { return null; } foreach (Declaration item in CssParser.ParseDeclarations(_element.GetAttribute("style") ?? "")) { if (string.Equals(item.Property, property, StringComparison.OrdinalIgnoreCase)) { return item.Value; } } return null; } private void Write(string property, string value) { if (property == null) { _element.SetAttribute("style", value); return; } List<string> list = new List<string>(); foreach (Declaration item in CssParser.ParseDeclarations(_element.GetAttribute("style") ?? "")) { if (!string.Equals(item.Property, property, StringComparison.OrdinalIgnoreCase)) { list.Add(item.Property + ": " + item.Value); } } if (!string.IsNullOrEmpty(value)) { list.Add(property + ": " + value); } _element.SetAttribute("style", string.Join("; ", list)); } private static string Kebab(string name) { if (string.IsNullOrEmpty(name) || name.IndexOf('-') >= 0) { return name; } StringBuilder stringBuilder = new StringBuilder(name.Length + 4); foreach (char c in name) { if (char.IsUpper(c)) { stringBuilder.Append('-'); stringBuilder.Append(char.ToLowerInvariant(c)); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } public sealed class JsDocument { private readonly ScriptHost _host; private readonly IDocument _document; public int NodeType => 9; public string NodeName => "#document"; public string ReadyState => "complete"; public JsElement Body { get { ScriptHost host = _host; IElement body = (IElement)(object)_document.Body; return host.Wrap(body ?? _document.DocumentElement); } } public JsElement DocumentElement => _host.Wrap(_document.DocumentElement); public JsElement Head => _host.Wrap((IElement)(object)_document.Head); public JsElement ActiveElement => _host.Wrap(_host.ActiveElement()); public string Title { get { return _document.Title ?? ""; } set { _document.Title = value ?? ""; } } internal JsDocument(ScriptHost host, IDocument document) { _host = host; _document = document; } public JsElement GetElementById(string id) { return _host.Wrap(((INonElementParentNode)_document).GetElementById(id)); } public JsElement QuerySelector(string selector) { return _host.Wrap(Try(() => ((IParentNode)_document).QuerySelector(selector))); } public JsValue QuerySelectorAll(string selector) { return _host.WrapNodes(Try(() => (IEnumerable<IElement>)((IParentNode)_document).QuerySelectorAll(selector)) ?? Array.Empty<IElement>()); } public JsElement CreateElement(string tag) { return _host.Wrap(_document.CreateElement(tag)); } public JsElement CreateElement(string tag, JsValue options) { return CreateElement(tag); } public JsElement CreateElementNS(string ns, string tag) { return _host.Wrap(string.IsNullOrEmpty(ns) ? _document.CreateElement(tag) : (Try(() => _document.CreateElement(ns, tag)) ?? _document.CreateElement(tag))); } public JsElement CreateElementNS(string ns, string tag, JsValue options) { return CreateElementNS(ns, tag); } public JsNode CreateTextNode(string text) { return _host.WrapNode((INode)(object)_document.CreateTextNode(text ?? "")); } public JsNode CreateComment(string text) { return _host.WrapNode((INode)(object)_document.CreateComment(text ?? "")); } public void AddEventListener(string type, JsValue handler) { AddEventListener(type, handler, (JsValue)(object)JsBoolean.False); } public void AddEventListener(string type, JsValue handler, JsValue options) { ScriptHost host = _host; INode body = (INode)(object)_document.Body; host.AddListener((INode)(((object)body) ?? ((object)_document.DocumentElement)), type, handler, IsCapture(options)); } public void RemoveEventListener(string type, JsValue handler) { RemoveEventListener(type, handler, (JsValue)(object)JsBoolean.False); } public void RemoveEventListener(string type, JsValue handler, JsValue options) { ScriptHost host = _host; INode body = (INode)(object)_document.Body; host.RemoveListener((INode)(((object)body) ?? ((object)_document.DocumentElement)), type, handler, IsCapture(options)); } internal static bool IsCapture(JsValue options) { if (options == (JsValue)null || JsValueExtensions.IsUndefined(options) || JsValueExtensions.IsNull(options)) { return false; } if (JsValueExtensions.IsBoolean(options)) { return JsValueExtensions.AsBoolean(options); } ObjectInstance val = (ObjectInstance)(object)((options is ObjectInstance) ? options : null); if (val != null) { JsValue val2 = ((JsValue)val).Get(JsValue.op_Implicit("capture")); if (!JsValueExtensions.IsUndefined(val2)) { return TypeConverter.ToBoolean(val2); } return false; } return false; } private static T Try<T>(Func<T> run) where T : class { try { return run(); } catch { return null; } } } public sealed class JsRect { public double x { get; set; } public double y { get; set; } public double width { get; set; } public double height { get; set; } public double top { get; set; } public double left { get; set; } public double right { get; set; } public double bottom { get; set; } } public sealed class JsEvent { public string Type { get; } public JsNode Target { get; } public JsNode CurrentTarget { get; internal set; } public int EventPhase { get; internal set; } public bool Bubbles { get; internal set; } = true; public string Value { get; internal set; } = ""; public string Key { get; internal set; } = ""; public bool CtrlKey { get; internal set; } public bool ShiftKey { get; internal set; } public bool AltKey { get; internal set; } public bool Repeat { get; internal set; } public bool HasSelection { get; internal set; } public string Source { get; internal set; } = ""; public float OffsetX { get; internal set; } public float OffsetY { get; internal set; } public float NormX { get; internal set; } public float NormY { get; internal set; } public float DeltaX { get; internal set; } public float DeltaY { get; internal set; } public float WheelDelta { get; internal set; } public float ClientX { get; internal set; } public float ClientY { get; internal set; } public int Button { get; internal set; } public int Detail { get; internal set; } public bool DefaultPrevented { get; private set; } internal bool PropagationStopped { get; private set; } internal bool ImmediatelyStopped { get; private set; } internal JsEvent(string type, JsNode target) { Type = type; Target = target; CurrentTarget = target; } public void PreventDefault() { DefaultPrevented = true; } public void StopPropagation() { PropagationStopped = true; } public void StopImmediatePropagation() { PropagationStopped = true; ImmediatelyStopped = true; } } internal sealed class DomTypes { private const string Bootstrap = "(function () {\n function extend(child, parent) {\n child.prototype = Object.create(parent.prototype);\n child.prototype.constructor = child;\n return child;\n }\n\n function EventTarget() {}\n function Node() {} extend(Node, EventTarget);\n function CharacterData() {} extend(CharacterData, Node);\n function Text() {} extend(Text, CharacterData);\n function Comment() {} extend(Comment, CharacterData);\n function Document() {} extend(Document, Node);\n function Element() {} extend(Element, Node);\n function HTMLElement() {} extend(HTMLElement, Element);\n\n var tags = {\n HTMLDivElement: 'div', HTMLSpanElement: 'span', HTMLParagraphElement: 'p',\n HTMLButtonElement: 'button', HTMLInputElement: 'input', HTMLTextAreaElement: 'textarea',\n HTMLSelectElement: 'select', HTMLOptionElement: 'option', HTMLLabelElement: 'label',\n HTMLAnchorElement: 'a', HTMLImageElement: 'img', HTMLUListElement: 'ul',\n HTMLOListElement: 'ol', HTMLLIElement: 'li', HTMLFormElement: 'form',\n HTMLHeadingElement: 'h1', HTMLTableElement: 'table', HTMLCanvasElement: 'canvas',\n // Nothing is ever one of these. They are here because code branches on them and an absent name throws.\n HTMLIFrameElement: null, HTMLVideoElement: null, HTMLAudioElement: null, SVGElement: null,\n };\n\n // `new CustomEvent('picked', { detail: id })` and `el.dispatchEvent(evt)`, which is how one part of a page\n // tells another that something happened without the two knowing about each other. Only `type` reaches the\n // listeners - see ScriptHost.DispatchFromScript for why nothing else could be honest here.\n function Event(type, init) {\n init = init || {};\n this.type = String(type);\n this.bubbles = !!init.bubbles;\n this.cancelable = !!init.cancelable;\n this.defaultPrevented = false;\n }\n Event.prototype.preventDefault = function () { if (this.cancelable) this.defaultPrevented = true; };\n Event.prototype.stopPropagation = function () {};\n Event.prototype.stopImmediatePropagation = function () {};\n\n function CustomEvent(type, init) { Event.call(this, type, init); this.detail = init ? init.detail : null; }\n extend(CustomEvent, Event);\n\n var byTag = {};\n var globals = {\n EventTarget: EventTarget, Node: Node, CharacterData: CharacterData, Text: Text,\n Comment: Comment, Document: Document, Element: Element, HTMLElement: HTMLElement,\n Event: Event, CustomEvent: CustomEvent,\n };\n\n for (var name in tags) {\n var ctor = new Function('return function ' + name + '() {}')();\n extend(ctor, HTMLElement);\n globals[name] = ctor;\n if (tags[name]) byTag[tags[name]] = ctor.prototype;\n }\n\n for (var key in globals) globalThis[key] = globals[key];\n\n return {\n node: Node.prototype,\n text: Text.prototype,\n comment: Comment.prototype,\n document: Document.prototype,\n element: HTMLElement.prototype,\n byTag: byTag,\n };\n})()"; private readonly ObjectInstance _byTag; internal ObjectInstance Node { get; } internal ObjectInstance Text { get; } internal ObjectInstance Comment { get; } internal ObjectInstance Document { get; } internal ObjectInstance Element { get; } internal DomTypes(Engine engine) { ObjectInstance val = JsValueExtensions.AsObject(engine.Evaluate("(function () {\n function extend(child, parent) {\n child.prototype = Object.create(parent.prototype);\n child.prototype.constructor = child;\n return child;\n }\n\n function EventTarget() {}\n function Node() {} extend(Node, EventTarget);\n function CharacterData() {} extend(CharacterData, Node);\n function Text() {} extend(Text, CharacterData);\n function Comment() {} extend(Comment, CharacterData);\n function Document() {} extend(Document, Node);\n function Element() {} extend(Element, Node);\n function HTMLElement() {} extend(HTMLElement, Element);\n\n var tags = {\n HTMLDivElement: 'div', HTMLSpanElement: 'span', HTMLParagraphElement: 'p',\n HTMLButtonElement: 'button', HTMLInputElement: 'input', HTMLTextAreaElement: 'textarea',\n HTMLSelectElement: 'select', HTMLOptionElement: 'option', HTMLLabelElement: 'label',\n HTMLAnchorElement: 'a', HTMLImageElement: 'img', HTMLUListElement: 'ul',\n HTMLOListElement: 'ol', HTMLLIElement: 'li', HTMLFormElement: 'form',\n HTMLHeadingElement: 'h1', HTMLTableElement: 'table', HTMLCanvasElement: 'canvas',\n // Nothing is ever one of these. They are here because code branches on them and an absent name throws.\n HTMLIFrameElement: null, HTMLVideoElement: null, HTMLAudioElement: null, SVGElement: null,\n };\n\n // `new CustomEvent('picked', { detail: id })` and `el.dispatchEvent(evt)`, which is how one part of a page\n // tells another that something happened without the two knowing about each other. Only `type` reaches the\n // listeners - see ScriptHost.DispatchFromScript for why nothing else could be honest here.\n function Event(type, init) {\n init = init || {};\n this.type = String(type);\n this.bubbles = !!init.bubbles;\n this.cancelable = !!init.cancelable;\n this.defaultPrevented = false;\n }\n Event.prototype.preventDefault = function () { if (this.cancelable) this.defaultPrevented = true; };\n Event.prototype.stopPropagation = function () {};\n Event.prototype.stopImmediatePropagation = function () {};\n\n function CustomEvent(type, init) { Event.call(this, type, init); this.detail = init ? init.detail : null; }\n extend(CustomEvent, Event);\n\n var byTag = {};\n var globals = {\n EventTarget: EventTarget, Node: Node, CharacterData: CharacterData, Text: Text,\n Comment: Comment, Document: Document, Element: Element, HTMLElement: HTMLElement,\n Event: Event, CustomEvent: CustomEvent,\n };\n\n for (var name in tags) {\n var ctor = new Function('return function ' + name + '() {}')();\n extend(ctor, HTMLElement);\n globals[name] = ctor;\n if (tags[name]) byTag[tags[name]] = ctor.prototype;\n }\n\n for (var key in globals) globalThis[key] = globals[key];\n\n return {\n node: Node.prototype,\n text: Text.prototype,\n comment: Comment.prototype,\n document: Document.prototype,\n element: HTMLElement.prototype,\n byTag: byTag,\n };\n})()", (string)null)); Node = JsValueExtensions.AsObject(((JsValue)val).Get(JsValue.op_Implicit("node"))); Text = JsValueExtensions.AsObject(((JsValue)val).Get(JsValue.op_Implicit("text"))); Comment = JsValueExtensions.AsObject(((JsValue)val).Get(JsValue.op_Implicit("comment"))); Document = JsValueExtensions.AsObject(((JsValue)val).Get(JsValue.op_Implicit("document"))); Element = JsValueExtensions.AsObject(((JsValue)val).Get(JsValue.op_Implicit("element"))); _byTag = JsValueExtensions.AsObject(((JsValue)val).Get(JsValue.op_Implicit("byTag"))); } internal ObjectInstance For(string tag) { if (string.IsNullOrEmpty(tag)) { return Element; } JsValue val = ((JsValue)_byTag).Get(JsValue.op_Implicit(tag.ToLowerInvariant())); if (!JsValueExtensions.IsObject(val)) { return Element; } return JsValueExtensions.AsObject(val); } } internal sealed class FetchApi { private const int MaxInFlight = 8; private const string ResponseFactory = "(function (status, statusText, url, headerLines, body, redirected) {\n var map = {};\n var lines = headerLines ? headerLines.split('\\n') : [];\n for (var i = 0; i < lines.length; i++) {\n var at = lines[i].indexOf(':');\n if (at > 0) map[lines[i].substring(0, at).trim().toLowerCase()] = lines[i].substring(at + 1).trim();\n }\n\n return {\n ok: status >= 200 && status < 300,\n status: status,\n statusText: statusText,\n url: url,\n redirected: redirected,\n headers: {\n get: function (name) { var k = String(name).toLowerCase(); return k in map ? map[k] : null; },\n has: function (name) { return String(name).toLowerCase() in map; }\n },\n // Already-settled promises: the body was read before the page was handed the response, so these match the web's\n // shape without ever being pending.\n text: function () { return Promise.resolve(body); },\n json: function () {\n try { return Promise.resolve(JSON.parse(body)); }\n catch (e) { return Promise.reject(e); }\n }\n };\n})"; private const string Wrapper = "(function (call) {\n return function fetch(url, init) {\n return call(url === undefined || url === null ? '' : String(url), init);\n };\n})"; private readonly ConcurrentQueue<Action> _finished = new ConcurrentQueue<Action>(); private readonly Engine _engine; private readonly Promises _promises; private readonly Action<string> _onError; private readonly string _appId; private JsValue _responseFactory; private int _inFlight; internal int Ready => _finished.Count; internal FetchApi(Engine engine, string appId, Promises promises, Action<string> onError) { _engine = engine; _appId = appId ?? ""; _promises = promises; _onError = onError; } internal void Install() { _responseFactory = _engine.Evaluate("(function (status, statusText, url, headerLines, body, redirected) {\n var map = {};\n var lines = headerLines ? headerLines.split('\\n') : [];\n for (var i = 0; i < lines.length; i++) {\n var at = lines[i].indexOf(':');\n if (at > 0) map[lines[i].substring(0, at).trim().toLowerCase()] = lines[i].substring(at + 1).trim();\n }\n\n return {\n ok: status >= 200 && status < 300,\n status: status,\n statusText: statusText,\n url: url,\n redirected: redirected,\n headers: {\n get: function (name) { var k = String(name).toLowerCase(); return k in map ? map[k] : null; },\n has: function (name) { return String(name).toLowerCase() in map; }\n },\n // Already-settled promises: the body was read before the page was handed the response, so these match the web's\n // shape without ever being pending.\n text: function () { return Promise.resolve(body); },\n json: function () {\n try { return Promise.resolve(JSON.parse(body)); }\n catch (e) { return Promise.reject(e); }\n }\n };\n})", (string)null); JsValue val = JsValue.FromObject(_engine, (object)new Func<string, JsValue, JsValue>(Call)); _engine.SetValue("fetch", _engine.Invoke(_engine.Evaluate("(function (call) {\n return function fetch(url, init) {\n return call(url === undefined || url === null ? '' : String(url), init);\n };\n})", (string)null), new object[1] { val })); } internal void Settle() { Action result; while (_finished.TryDequeue(out result)) { try { result(); } catch (Exception ex) { _onError?.Invoke("settling a fetch failed: " + ex.Message); } } } private JsValue Call(string url, JsValue init) { Deferred deferred = _promises.Create(); FetchCall fetchCall = new FetchCall { AppId = _appId }; if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result) || (result.Scheme != Uri.UriSchemeHttp && result.Scheme != Uri.UriSchemeHttps)) { Later(delegate { deferred.Reject("fetch needs an absolute http(s) URL - got '" + Trim(url) + "'."); }); return deferred.Promise; } fetchCall.Url = result; try { ReadInit(_engine, init, fetchCall); } catch (Exception ex) { Exception ex2 = ex; Exception e = ex2; Later(delegate { deferred.Reject("the second argument to fetch is not usable: " + e.Message); }); return deferred.Promise; } if (!HostAllowlist.Allows(_appId, result, out var reason)) { HostAllowlist.ReportOnce(_appId, result, reason); Later(delegate { deferred.Reject(reason); }); return deferred.Promise; } if (_inFlight >= 8) { Later(delegate { deferred.Reject($"too many requests at once - this app already has {8} " + "fetches in flight. Wait for one to settle before starting another."); }); return deferred.Promise; } _inFlight++; Fetcher.Send(fetchCall, delegate(FetchOutcome outcome) { _finished.Enqueue(delegate { _inFlight--; if (outcome.Failed) { deferred.Reject(outcome.Error); } else { deferred.Resolve(Response(outcome)); } }); }); return deferred.Promise; } private void Later(Action settle) { _finished.Enqueue(settle); } private JsValue Response(FetchOutcome outcome) { return _engine.Invoke(_responseFactory, new object[6] { outcome.Status, outcome.StatusText, outcome.Url, outcome.HeaderLines, outcome.Body, outcome.Redirected }); } private static void ReadInit(Engine engine, JsValue init, FetchCall call) { if (init == (JsValue)null || JsValueExtensions.IsUndefined(init) || JsValueExtensions.IsNull(init) || !JsValueExtensions.IsObject(init)) { return; } ObjectInstance obj = JsValueExtensions.AsObject(init); JsValue val = ((JsValue)obj).Get(JsValue.op_Implicit("method")); if (!JsValueExtensions.IsUndefined(val) && !JsValueExtensions.IsNull(val)) { call.Method = ((object)val).ToString(); } JsValue val2 = ((JsValue)obj).Get(JsValue.op_Implicit("body")); bool flag = false; if (!JsValueExtensions.IsUndefined(val2) && !JsValueExtensions.IsNull(val2)) { call.Body = ((object)val2).ToString(); flag = WebApis.IsFormData(engine, val2); } JsValue val3 = ((JsValue)obj).Get(JsValue.op_Implicit("timeout")); if (JsValueExtensions.IsNumber(val3)) { call.TimeoutMs = (int)JsValueExtensions.AsNumber(val3); } bool flag2 = false; JsValue val4 = ((JsValue)obj).Get(JsValue.op_Implicit("headers")); if (JsValueExtensions.IsObject(val4)) { ObjectInstance val5 = JsValueExtensions.AsObject(val4); foreach (KeyValuePair<JsValue, PropertyDescriptor> ownProperty in val5.GetOwnProperties()) { JsValue val6 = ((JsValue)val5).Get(ownProperty.Key); if (!JsValueExtensions.IsUndefined(val6) && !JsValueExtensions.IsNull(val6)) { string text = ((object)ownProperty.Key).ToString(); flag2 |= string.Equals(text, "content-type", StringComparison.OrdinalIgnoreCase); call.Headers.Add(new KeyValuePair<string, string>(text, ((object)val6).ToString())); } } } if (flag && !flag2) { call.Headers.Add(new KeyValuePair<string, string>("Content-Type", "application/x-www-form-urlencoded")); } } private static string Trim(string url) { string text; if (url == null || url.Length <= 80) { text = url; if (text == null) { return ""; } } else { text = url.Substring(0, 80) + "..."; } return text; } } internal static class MiniJson { internal static string WriteObject(Dictionary<string, string> values) { if (values == null || values.Count == 0) { return "{}"; } StringBuilder stringBuilder = new StringBuilder("{"); bool flag = true; foreach (KeyValuePair<string, string> value in values) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('\n').Append(" "); Escape(stringBuilder, value.Key); stringBuilder.Append(": "); Escape(stringBuilder, value.Value); } return stringBuilder.Append("\n}").ToString(); } internal static Dictionary<string, string> ParseObject(string json) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); if (string.IsNullOrWhiteSpace(json)) { return dictionary; } int i = 0; SkipWhitespace(json, ref i); if (i >= json.Length || json[i] != '{') { return dictionary; } i++; while (true) { SkipWhitespace(json, ref i); if (i >= json.Length || json[i] == '}') { break; } if (json[i] == ',') { i++; continue; } if (json[i] != '"') { break; } string key = ReadString(json, ref i); SkipWhitespace(json, ref i); if (i >= json.Length || json[i] != ':') { break; } i++; SkipWhitespace(json, ref i); if (i >= json.Length) { break; } dictionary[key] = ((json[i] == '"') ? ReadString(json, ref i) : ReadBareValue(json, ref i)); } return dictionary; } private static void Escape(StringBuilder sb, string value) { sb.Append('"'); string text = value ?? ""; foreach (char c in text) { switch (c) { case '"': sb.Append("\\\""); continue; case '\\': sb.Append("\\\\"); continue; case '\n': sb.Append("\\n"); continue; case '\r': sb.Append("\\r"); continue; case '\t': sb.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder = sb.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4")); } else { sb.Append(c); } } sb.Append('"'); } private static string ReadString(string json, ref int i) { StringBuilder stringBuilder = new StringBuilder(); i++; while (i < json.Length) { char c = json[i++]; switch (c) { default: stringBuilder.Append(c); continue; case '\\': { if (i >= json.Length) { break; } char c2 = json[i++]; switch (c2) { case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'u': { if (i + 4 <= json.Length && int.TryParse(json.Substring(i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { stringBuilder.Append((char)result); i += 4; } break; } default: stringBuilder.Append(c2); break; } continue; } case '"': break; } break; } return stringBuilder.ToString(); } private static string ReadBareValue(string json, ref int i) { int num = i; while (i < json.Length && json[i] != ',' && json[i] != '}') { i++; } return json.Substring(num, i - num).Trim(); } private static void SkipWhitespace(string json, ref int i) { while (i < json.Length && char.IsWhiteSpace(json[i])) { i++; } } } internal sealed class Promises { private const string Factory = "(function (report) {\n var settle, fail;\n var root = new Promise(function (ok, no) { settle = ok; fail = no; });\n\n function watch(p) {\n var chained = false;\n var then = p.then.bind(p);\n\n p.then = function (ok, no) { chained = true; return watch(then(ok, no)); };\n p.catch = function (no) { chained = true; return watch(then(undefined, no)); };\n p.finally = function (fn) { chained = true; return watch(then(\n function (v) { fn(); return v; },\n function (e) { fn(); throw e; })); };\n\n then(undefined, function (err) {\n if (!chained) report(String(err && err.message ? err.message : err));\n });\n return p;\n }\n\n return {\n promise: watch(root),\n resolve: settle,\n reject: function (message) { fail(new Error(message)); }\n };\n})"; private readonly Engine _engine; private readonly JsValue _factory; private readonly JsValue _report; internal Promises(Engine engine, Action<string> onUnhandledRejection) { _engine = engine; _factory = engine.Evaluate("(function (report) {\n var settle, fail;\n var root = new Promise(function (ok, no) { settle = ok; fail = no; });\n\n function watch(p) {\n var chained = false;\n var then = p.then.bind(p);\n\n p.then = function (ok, no) { chained = true; return watch(then(ok, no)); };\n p.catch = function (no) { chained = true; return watch(then(undefined, no)); };\n p.finally = function (fn) { chained = true; return watch(then(\n function (v) { fn(); return v; },\n function (e) { fn(); throw e; })); };\n\n then(undefined, function (err) {\n if (!chained) report(String(err && err.message ? err.message : err));\n });\n return p;\n }\n\n return {\n promise: watch(root),\n resolve: settle,\n reject: function (message) { fail(new Error(message)); }\n };\n})", (string)null); _report = JsValue.FromObject(engine, (object)(Action<string>)delegate(string m) { onUnhandledRejection?.Invoke(m); }); } internal Deferred Create() { ObjectInstance val = JsValueExtensions.AsObject(_engine.Invoke(_factory, new object[1] { _report })); return new Deferred(_engine, ((JsValue)val).Get(JsValue.op_Implicit("promise")), ((JsValue)val).Get(JsValue.op_Implicit("resolve")), ((JsValue)val).Get(JsValue.op_Implicit("reject"))); } internal void Pump() { _engine.Constraints.Reset(); _engine.Advanced.ProcessTasks(); } } internal sealed class Deferred { private readonly Engine _engine; private readonly JsValue _resolve; private readonly JsValue _reject; internal JsValue Promise { get; } internal Deferred(Engine engine, JsValue promise, JsValue resolve, JsValue reject) { _engine = engine; Promise = promise; _resolve = resolve; _reject = reject; } internal void Resolve(JsValue value) { _engine.Invoke(_resolve, new object[1] { value ?? JsValue.Undefined }); } internal void Reject(string message) { _engine.Invoke(_reject, new object[1] { message ?? "" }); } } internal sealed class ScriptHost { private readonly struct Listener { internal JsValue Handler { get; } internal bool Capture { get; } internal Listener(JsValue handler, bool capture) { Handler = handler; Capture = capture; } } private sealed class Timer { internal int Id; internal JsValue Callback; internal double Remaining; internal double Interval; internal bool Cancelled; } public sealed class Console { private readonly string _appId; internal Console(string appId) { _appId = appId; } public void Log(params object[] args) { Emit("log", args); } public void Info(params object[] args) { Emit("info", args); } public void Warn(params object[] args) { Emit("warn", args); } public void Error(params object[] args) { Emit("error", args); } private void Emit(string level, object[] args) { string text = Format(args); if (!(level == "warn")) { if (level == "error") { Platform.Error(text); } else { Platform.Msg(text); } } else { Platform.Warning(text); } Diagnostics?.Invoke(_appId, level, args, text); } private string Format(object[] args) { if (args == null || args.Length == 0) { return "[" + _appId + "]"; } return "[" + _appId + "] " + string.Join(" ", Array.ConvertAll(args, (object a) => a?.ToString() ?? "null")); } } private readonly TimeBudget _budget = new TimeBudget(); internal static Action<string, string, object[], string> Diagnostics = null; private readonly Dictionary<INode, Dictionary<string, List<Listener>>> _listeners = new Dictionary<INode, Dictionary<string, List<Listener>>>(); private readonly Dictionary<INode, JsNode> _wrappers = new Dictionary<INode, JsNode>(); private readonly List<Timer> _timers = new List<Timer>(); private readonly string _appId; private Promises _promises; private FetchApi _fetch; private IDocument _document; private JsDocument _documentObject; private Bridge _bridge; private int _nextTimerId = 1; private bool _failed; private static readonly Dictionary<string, Prepared<Script>> _prepared = new Dictionary<string, Prepared<Script>>(StringComparer.Ordinal); private const int PrepareFrom = 2048; private static readonly HashSet<string> Dispatchable = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "click", "dblclick", "contextmenu", "mousedown", "mouseup", "mouseenter", "mouseleave", "mouseover", "mouseout", "mousemove", "wheel", "input", "change", "keydown", "focus", "blur", "focusin", "focusout", "dragstart", "drag", "dragend", "orientationchange", "back" }; private static readonly HashSet<string> NonBubbling = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "mouseenter", "mouseleave", "focus", "blur" }; private readonly Dictionary<INode, Dictionary<string, JsValue>> _inlineHandlers = new Dictionary<INode, Dictionary<string, JsValue>>(); internal Engine Engine { get; } internal Action OnDomChanged { get; } internal Action<IElement> OnFocusRequested { get; } internal Action<IElement> OnBlurRequested { get; } internal Action<IElement> OnScrollToEnd { get; } internal Func<IElement, float[]> OnRectRequested { get; } internal Func<IElement> OnActiveElementRequested { get; } internal Func<float[]> OnViewportRequested { get; } internal Action<IElement> OnPaintOnlyChange { get; } internal bool Failed => _failed; internal string LastError { get; private set; } = ""; internal JsValue DocumentObject => JsValue.FromObject(Engine, (object)_documentObject); internal Func<IElement, string, bool> OnTextOnlyChange { get; set; } internal DomTypes Types { get; private set; } internal ScriptHost(string appId, IDocument document, Action onDomChanged, Action<IElement> onFocusRequested, Action<IElement> onScrollToEnd, Action<IElement> onPaintOnlyChange = null, Func<IElement, float[]> onRectRequested = null, Action<IElement> onBlurRequested = null, Func<IElement> onActiveElementRequested = null, Func<float[]> onViewportRequested = null) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown _appId = appId; _document = document; OnDomChanged = onDomChanged; OnFocusRequested = onFocusRequested; OnScrollToEnd = onScrollToEnd; OnPaintOnlyChange = onPaintOnlyChange; OnRectRequested = onRectRequested; OnBlurRequested = onBlurRequested; OnActiveElementRequested = onActiveElementRequested; OnViewportRequested = onViewportRequested; Engine = new Engine((Action<Options>)delegate(Options options) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown options.Constraints.Constraints.Add((Constraint)(object)_budget); ConstraintsOptionsExtensions.MaxStatements(options, 2000000); OptionsExtensions.LimitRecursion(options, 256); options.Strict = false; options.ExperimentalFeatures = (ExperimentalFeature)3; OptionsExtensions.SetTypeResolver(options, new TypeResolver { MemberNameCreator = BothCasings }); }); Bind(); } internal void RestartBudget() { Engine engine = Engine; if (engine != null) { engine.Constraints.Reset(); } } internal void Rebound(IDocument document) { _document = document; _wrappers.Clear(); _documentObject = new JsDocument(this, _document); Engine.SetValue<JsDocument>("document", _documentObject); } internal IElement ActiveElement() { return OnActiveElementRequested?.Invoke(); } internal void Dispose() { _bridge?.Dispose(); _listeners.Clear(); _inlineHandlers.Clear(); _wrappers.Clear(); _timers.Clear(); } internal void Forget(INode node) { if (node == null) { return; } _wrappers.Remove(node); _listeners.Remove(node); _inlineHandlers.Remove(node); foreach (INode item in (IEnumerable<INode>)node.ChildNodes) { Forget(item); } } internal void MarkDirty() { OnDomChanged?.Invoke(); } internal bool TryRetext(IElement element, string text) { if (OnTextOnlyChange != null) { return OnTextOnlyChange(element, text); } return false; } internal void MarkPaintDirty(IElement element) { if (OnPaintOnlyChange == null) { MarkDirty(); } else { OnPaintOnlyChange(element); } } internal void RequestFocus(IElement element) { OnFocusRequested?.Invoke(element); } internal void RequestBlur(IElement element) { OnBlurRequested?.Invoke(element); } internal void RequestScrollToEnd(IElement element) { OnScrollToEnd?.Invoke(element); } internal float[] RectOf(IElement element) { return OnRectRequested?.Invoke(element) ?? new float[4]; } internal JsElement Wrap(IElement element) { return (JsElement)WrapNode((INode)(object)element); } internal JsNode WrapNode(INode node) { if (node == null) { return null; } if (_wrappers.TryGetValue(node, out var value)) { return value; } IElement val = (IElement)(object)((node is IElement) ? node : null); JsNode jsNode; if (val == null) { IText val2 = (IText)(object)((node is IText) ? node : null); if (val2 == null) { IComment val3 = (IComment)(object)((node is IComment) ? node : null); jsNode = ((val3 == null) ? null : new JsComment(this, val3)); } else { jsNode = new JsText(this, val2); } } else { jsNode = new JsElement(this, val); } JsNode jsNode2 = jsNode; if ((JsValue)(object)jsNode2 != (JsValue)null) { _wrappers[node] = jsNode2; } return jsNode2; } internal JsValue WrapNodes(IEnumerable<INode> nodes) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List<JsValue> list = new List<JsValue>(); if (nodes != null) { foreach (INode node in nodes) { JsNode jsNode = WrapNode(node); if ((JsValue)(object)jsNode != (JsValue)null) { list.Add((JsValue)(object)jsNode); } } } return (JsValue)new JsArray(Engine, list.ToArray()); } internal JsValue WrapNodes(IEnumerable<IElement> elements) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List<JsValue> list = new List<JsValue>(); if (elements != null) { foreach (IElement element in elements) { JsNode jsNode = WrapNode((INode)(object)element); if ((JsValue)(object)jsNode != (JsValue)null) { list.Add((JsValue)(object)jsNode); } } } return (JsValue)new JsArray(Engine, list.ToArray()); } internal void Run(string source, string fileName) { if (string.IsNullOrWhiteSpace(source)) { return; } WarnAboutAwaitedFetch(source, fileName); try { _budget.During(Engine, TimeBudget.Load, delegate { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) if (source.Length >= 2048) { if (!_prepared.TryGetValue(source, out var value)) { value = (_prepared[source] = Engine.PrepareScript(source, fileName, false, (ScriptPreparationOptions)null)); } Engine.Execute(ref value); } else { Engine.Execute(source, fileName); } }); Platform.Msg($"{fileName} executed ({source.Length} chars)."); } catch (Exception e) { _failed = true; LastError = Describe(e); Platform.Error(fileName + " failed: " + LastError); Diagnostics?.Invoke(_appId, "exception", null, fileName + ": " + LastError); } } private static void WarnAboutAwaitedFetch(string source, string fileName) { if (source.IndexOf("fetch(", StringComparison.Ordinal) >= 0 && Regex.IsMatch(source, "\\bawait\\b")) { Platform.Warning("[Sideload] " + fileName + " uses both `await` and `fetch(`. Awaiting a PENDING promise freezes the game on this engine - it blocks the main thread that would settle it. Use `fetch(url).then(res => ...)` instead. Awaiting an already-settled promise, such as `res.text()`, is safe."); } } internal string Evaluate(string source, out bool failed) { failed = false; if (string.IsNullOrWhiteSpace(source)) { return ""; } try { JsValue val = Engine.Evaluate(source, (string)null); return JsValueExtensions.IsUndefined(val) ? "undefined" : Describe(val); } catch (Exception e) { failed = true; LastError = Describe(e); return LastError; } } private string Describe(JsValue value) { try { if (JsValueExtensions.IsNull(value)) { return "null"; } if (JsValueExtensions.IsString(value) || JsValueExtensions.IsNumber(value) || JsValueExtensions.IsBoolean(value)) { return ((object)value).ToString(); } JsValue val = JsValueExtensions.Call(Engine.Evaluate("JSON.stringify", (string)null), JsValue.Undefined, value); return JsValueExtensions.IsUndefined(val) ? ((object)value).ToString() : JsValueExtensions.AsString(val); } catch { return ((object)value).ToString(); } } internal static bool Bubbles(string type) { return !NonBubbling.Contains(type ?? ""); } internal void AddListener(INode node, string type, JsValue handler, bool capture = false) { if (node == null || string.IsNullOrEmpty(type) || handler == (JsValue)null || !JsValueExtensions.IsObject(handler)) { return; } if (!Dispatchable.Contains(type)) { Sideload.Model.Diagnostics.Report(DiagnosticKind.DeadEventListener, type); } if (!_listeners.TryGetValue(node, out var value)) { value = (_listeners[node] = new Dictionary<string, List<Listener>>(StringComparer.OrdinalIgnoreCase)); } if (!value.TryGetValue(type, out var value2)) { value2 = (value[type] = new List<Listener>()); } foreach (Listener item in value2) { if (item.Capture == capture && Same(item.Handler, handler)) { return; } } value2.Add(new Listener(handler, capture)); MarkDirty(); } internal void RemoveListener(INode node, string type, JsValue handler, bool capture = false) { if (node != null && !(handler == (JsValue)null) && !string.IsNullOrEmpty(type) && _listeners.TryGetValue(node, out var value) && value.TryGetValue(type, out var value2) && value2.RemoveAll((Listener l) => l.Capture == capture && Same(l.Handler, handler)) > 0) { MarkDirty(); } } private static bool Same(JsValue a, JsValue b) { if (a != b) { return object.Equals(a, b); } return true; } internal JsValue InlineHandler(INode node, string type) { if (_inlineHandlers.TryGetValue(node, out var value) && value.TryGetValue(type, out var value2)) { return value2; } return JsValue.Null; } internal void SetInlineHandler(INode node, string type, JsValue handler) { if (!_inlineHandlers.TryGetValue(node, out var value)) { value = (_inlineHandlers[node] = new Dictionary<string, JsValue>(StringComparer.OrdinalIgnoreCase)); } if (value.TryGetValue(type, out var value2)) { RemoveListener(node, type, value2); } if (handler == (JsValue)null || !JsValueExtensions.IsObject(handler)) { value.Remove(type); return; } value[type] = handler; AddListener(node, type, handler); } internal IEnumerable<IElement> ElementsListeningFor(string type) { foreach (KeyValuePair<INode, Dictionary<string, List<Listener>>> listener in _listeners) { INode key = listener.Key; IElement val = (IElement)(object)((key is IElement) ? key : null); if (val != null && listener.Value.TryGetValue(type, out var value) && value.Count > 0) { yield return val; } } } internal JsEvent Dispatch(IElement target, string type, string value = "", string key = "", string source = "", PointerSpot spot = default(PointerSpot), float deltaX = 0f, float deltaY = 0f, float wheelDelta = 0f, bool ctrl = false, bool shift = false, bool alt = false, bool repeat = false, bool hasSelection = false, bool? bubbles = null, float clientX = 0f, float clientY = 0f, int button = 0, int detail = 0) { JsEvent jsEvent = new JsEvent(type, WrapNode((INode)(object)target)) { Value = (value ?? ""), Key = (key ?? ""), Source = (source ?? ""), OffsetX = spot.OffsetX, OffsetY = spot.OffsetY, NormX = spot.NormX, NormY = spot.NormY, DeltaX = deltaX, DeltaY = deltaY, WheelDelta = wheelDelta, CtrlKey = ctrl, ShiftKey = shift, AltKey = alt, Repeat = repeat, HasSelection = hasSelection, ClientX = clientX, ClientY = clientY, Button = button, Detail = detail }; if (_failed || target == null) { return jsEvent; } bool flag = bubbles ?? Bubbles(type); jsEvent.Bubbles = flag; List<IElement> list = new List<IElement>(); for (IElement val = target; val != null; val = ((INode)val).ParentElement) { list.Add(val); if (!flag) { break; } } for (int num = list.Count - 1; num > 0; num--) { jsEvent.EventPhase = 1; if (!RunHandlers(list[num], type, jsEvent, capture: true)) { return jsEvent; } } jsEvent.EventPhase = 2; if (!RunHandlers(target, type, jsEvent, capture: true)) { return jsEvent; } if (!RunHandlers(target, type, jsEvent, capture: false)) { return jsEvent; } for (int i = 1; i < list.Count; i++) { jsEvent.EventPhase = 3; if (!RunHandlers(list[i], type, jsEvent, capture: false)) { return jsEvent; } } return jsEvent; } private bool RunHandlers(IElement node, string type, JsEvent evt, bool capture) { if (!_listeners.TryGetValue((INode)(object)node, out var value)) { return true; } if (!value.TryGetValue(type, out var value2) || value2.Count == 0) { return true; } JsNode self = (evt.CurrentTarget = WrapNode((INode)(object)node)); Listener[] array = value2.ToArray(); for (int i = 0; i < array.Length; i++) { Listener listener = array[i]; if (listener.Capture == capture) { InvokeOn(listener.Handler, (JsValue)(object)self, type + " handler on <" + node.LocalName + ">", JsValue.FromObject(Engine, (object)evt)); if (evt.ImmediatelyStopped) { return false; } } } return !evt.PropagationStopped; } internal JsValue DispatchFromScript(INode node, ObjectInstance evt) { IElement val = (IElement)(object)((node is IElement) ? node : null); if (val == null || (JsValue)(object)evt == (JsValue)null) { return JsValue.op_Implicit(true); } JsValue val2 = ((JsValue)evt).Get(JsValue.op_Implicit("type")); if (JsValueExtensions.IsUndefined(val2) || JsValueExtensions.IsNull(val2)) { return JsValue.op_Implicit(true); } return JsValue.op_Implicit(!Dispatch(val, ((object)val2).ToString()).DefaultPrevented); } internal void Tick(float deltaSeconds) { if (_failed) { return; } try { _fetch?.Settle(); } catch (Exception e) { Report("settling a fetch", e); } try { _promises?.Pump(); } catch (Exception e2) { Report("a pending promise", e2); } if (_timers.Count == 0) { return; } double num = (double)deltaSeconds * 1000.0; Timer[] array = _timers.ToArray(); foreach (Timer timer in array) { if (timer.Cancelled) { continue; } timer.Remaining -= num; if (!(timer.Remaining > 0.0)) { if (timer.Interval > 0.0) { timer.Remaining += timer.Interval; } else { timer.Cancelled = true; } Invoke(timer.Callback, $"timer {timer.Id}"); } } _timers.RemoveAll((Timer t) => t.Cancelled); } private int AddTimer(JsValue callback, double delayMs, bool repeating) { if (callback == (JsValue)null || !JsValueExtensions.IsObject(callback)) { return 0; } Timer timer = new Timer { Id = _nextTimerId++, Callback = callback, Remaining = Math.Max(delayMs, 0.0), Interval = (repeating ? Math.Max(delayMs, 16.0) : 0.0) }; _timers.Add(timer); return timer.Id; } private void ClearTimer(double id) { foreach (Timer timer in _timers) { if (timer.Id == (int)id) { timer.Cancelled = true; } } } private void Invoke(JsValue callback, string what, params JsValue[] args) { InvokeOn(callback, JsValue.Undefined, what, args); } private void InvokeOn(JsValue callback, JsValue self, string what, params JsValue[] args) { try { Engine.Invoke(callback, (object)self, Array.ConvertAll(args, (Converter<JsValue, object>)((JsValue a) => a))); } catch (Exception e) { LastError = Describe(e); Platform.Error(what + " failed: " + LastError); Diagnostics?.Invoke(_appId, "exception", null, what + " failed: " + LastError); } } private void Report(string what, Exception e) { LastError = Describe(e); Fault(what + " failed: " + LastError); } private void Fault(string line) { Platform.Error("[" + _appId + "] " + line); Diagnostics?.Invoke(_appId, "error", null, "[" + _appId + "] " + line); } private void Bind() { Types = new DomTypes(Engine); _documentObject = new JsDocument(this, _document); Engine.SetValue<JsDocument>("document", _documentObject); _bridge = new Bridge(this, _appId); Engine.SetValue<Bridge>("s1", _bridge); Engine.SetValue<Bridge.JsStorage>("localStorage", _bridge.Storage); Engine.SetValue<Bridge.JsStorage>("sessionStorage", _bridge.Storage); Engine.SetValue<Console>("console", new Console(_appId)); BindWindow(); _promises = new Promises(Engine, delegate(string message) { Fault("unhandled promise rejection: " + message); }); _fetch = new FetchApi(Engine, _appId, _promises, delegate(string line) { Fault(line); }); _fetch.Install(); WebApis.Install(Engine); Engine.SetValue<Func<JsValue, double, int>>("setTimeout", (Func<JsValue, double, int>)((JsValue fn, double ms) => AddTimer(fn, ms, repeating: false))); Engine.SetValue<Func<JsValue, double, int>>("setInterval", (Func<JsValue, double, int>)((JsValue fn, double ms) => AddTimer(fn, ms, repeating: true))); Engine.SetValue<Action<double>>("clearTimeout", (Action<double>)ClearTimer); Engine.SetValue<Action<double>>("clearInterval", (Action<double>)ClearTimer); Engine.SetValue<Func<JsValue, int>>("requestAnimationFrame", (Func<JsValue, int>)((JsValue fn) => AddTimer(fn, 16.0, repeating: false))); Engine.SetValue<Action<double>>("cancelAnimationFrame", (Action<double>)ClearTimer); } private void BindWindow() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown ObjectInstance global = Engine.Global; Engine.SetValue<ObjectInstance>("window", global); Engine.SetValue<ObjectInstance>("self", global); Engine.SetValue<ObjectInstance>("globalThis", global); Engine.SetValue<ClrFunction>("addEventListener", new ClrFunction(Engine, "addEventListener", (Func<JsValue, JsValue[], JsValue>)delegate(JsValue _, JsValue[] a) { AddListener(Root(), (a.Length != 0) ? ((object)a[0]).ToString() : null, (a.Length > 1) ? a[1] : null, JsDocument.IsCapture((a.Length > 2) ? a[2] : null)); return JsValue.Undefined; }, 0, (PropertyFlag)42)); Engine.SetValue<ClrFunction>("removeEventListener", new ClrFunction(Engine, "removeEventListener", (Func<JsValue, JsValue[], JsValue>)delegate(JsValue _, JsValue[] a) { RemoveListener(Root(), (a.Length != 0) ? ((object)a[0]).ToString() : null, (a.Length > 1) ? a[1] : null, JsDocument.IsCapture((a.Length > 2) ? a[2] : null)); return JsValue.Undefined; }, 0, (PropertyFlag)42)); global.FastSetProperty("innerWidth", (PropertyDescriptor)new GetSetPropertyDescriptor((JsValue)new ClrFunction(Engine, "innerWidth", (Func<JsValue, JsValue[], JsValue>)((JsValue _, JsValue[] _) => JsValue.op_Implicit((double)Viewport()[0])), 0, (PropertyFlag)42), (JsValue)null, (bool?)false, (bool?)true)); global.FastSetProperty("innerHeight", (PropertyDescriptor)new GetSetPropertyDescriptor((JsValue)new ClrFunction(Engine, "innerHeight", (Func<JsValue, JsValue[], JsValue>)((JsValue _, JsValue[] _) => JsValue.op_Implicit((double)Viewport()[1])), 0, (PropertyFlag)42), (JsValue)null, (bool?)false, (bool?)true)); Engine.SetValue("devicePixelRatio", 1); } private INode Root() { INode body = (INode)(object)_document.Body; return (INode)(((object)body) ?? ((object)_document.DocumentElement)); } private float[] Viewport() { return OnViewportRequested?.Invoke() ?? new float[2]; } private static IEnumerable<string> BothCasings(MemberInfo member) { string name = member.Name; if (!string.IsNullOrEmpty(name)) { string camel = (char.IsUpper(name[0]) ? (char.ToLowerInvariant(name[0]) + name.Substring(1)) : name); yield return camel; if (camel != name) { yield return name; } } } private static string Describe(Exception e) { JavaScriptException ex = (JavaScriptException)(object)((e is JavaScriptException) ? e : null); if (ex != null) { return $"{((Exception)(object)ex).Message} ({((Location)ex.Location).Source}:{((Location)ex.Location).Start.Line}:{((Location)ex.Location).Start.Column})"; } ParserException ex2 = (ParserException)(object)((e is ParserException) ? e : null); if (ex2 != null) { return "syntax error: " + ((Exception)(object)ex2).Message; } return e.ToString(); } } internal sealed class TimeBudget : Constraint { internal static readonly TimeSpan Handler = TimeSpan.FromMilliseconds(250.0); internal static readonly TimeSpan Load = TimeSpan.FromSeconds(5.0); private DateTime _deadline = DateTime.MaxValue; internal TimeSpan Ceiling { get; set; } = Handler; public override void Reset() { _deadline = DateTime.UtcNow + Ceiling; } public override void Check() { if (DateTime.UtcNow <= _deadline) { return; } throw new TimeoutException($"script ran longer than {Ceiling.TotalMilliseconds:0} ms"); } internal void During(Engine engine, TimeSpan ceiling, Action run) { TimeSpan ceiling2 = Ceiling; Ceiling = ceiling; try { if (engine != null) { engine.Constraints.Reset(); } run(); } finally { Ceiling = ceiling2; if (engine != null) { engine.Constraints.Reset(); } } } } in