Home

Experimental support for Chromium Engine

edited March 2021

The latest LINQPad 6 beta can now use Edge's Chromium engine to render results. To enable, go to Edit | Preferences > Results. Benefits include sticky table headers, correct double/triple click behavior, and the ability to emit custom HTML and JavaScript that leverages the latest standards. LINQPad's HTML controls also work better with Chrome in that you can capture event data (such as mouse position) when subscribing to events (see the integrated Tutorial/Reference for samples). Another bonus is that you can right-click the browser or press Ctrl+Shift+I for DevTools.

Note that to use the Chromium engine, you must either have a relatively recent version of Edge installed (which will be the case if you have Windows updates enabled), or install the WebView2 runtime. If both are present, LINQPad will use whichever is later.

You can check what version LINQPad is using in Help | About.

Let me know if you run into any issues. This is a non-trivial project, so some glitches are likely.

«1

Comments

  • edited March 2021

    Very nice!

    Tiny glitch - Export to Word/Excel exports with formatting, Export to Word/Excel With Formatting exports raw HTML to Word/Excel.

    Also, on closing query window with results (F5 => CTRL-W), a window appears for a fraction of a moment.

  • edited March 2021

    This is great! This will allow me to show Postgres query plan inside LINQPad with https://github.com/dalibo/pev2/

    Two questions:

    Is it possible to detect programmatically whether the query is using the new engine? Also, if the user doesn't have the new engine can the LINQPad installer prompt the user to install runtime to increase the adoption? You can even download the runtime programmatically and launch it: Online-only deployment

  • I'll add a Util.BrowserEngine.IsInternetExplorer property to the next build.

    Regarding adoption of the Edge Chromium browser, this shouldn't be a problem soon:
    https://www.computerworld.com/article/3606788/microsoft-to-replace-legacy-edge-in-april-with-chromium-based-version.html

    It may be a few months before it's the default in LINQPad (assuming all goes well). Note that LINQPad doesn't require WebView2 - it clones the main Edge Chromium installation unless a newer version of WebView2 is present.

  • edited March 2021

    Actually, I'm not sure whether I need IsInternetExplorer or not. Even if the user hasn't selected to use the new engine, my query plan visualizer will use that one if it is available on the system. A more useful property for my use case would probably be Util.IsChromiumSupported so that I don't have to code that one from scratch.

  • edited March 2021

    The adoption will be quick on Windows 10 but what about older versions of Windows? I don't know LINQPad users windows version distribution but if someone is using an older version of Windows they won't have the new edge browser installed automatically.

  • So are you calling an external browser? If that's the case, I'm guessing you'll need to query Windows for the default association for HTML files, and see what browser and version it maps to.

    IsInternetExplorer will return true if LINQPad's HTML result output window uses the legacy IE engine. This might be relevant if you plan to use Util.RawHtml or LINQPad's controls to push custom HTML into the output window. There may be more browser support in the future if a cross-platform version becomes reality, so a IsChromium property is less useful (what should it return for Safari?)

    There aren't many LINQPad 6 users on older versions of Windows.

  • This is great! I wrote plugins to dump WebView2. Will the built-in implementation be faster or more efficient in some way over Winforms and WPF plugins? Plugins provide more control, not sure of any possible overhead issues. My plugins work without any noticeable performance problems even when rendering 60+fps 3D scenes. With WebView2 I use LINQPad as a web development/scratchpad tool.

  • bug report:
    get Text from (TextBox or TextArea)

    before:
    {
    "locations": [
    {"name": "Seattle", "state": "WA"},
    {"name": "New York", "state": "NY"},
    {"name": "Bellevue", "state": "WA"},
    {"name": "Olympia", "state": "WA"}
    ]
    }

    webview2:

    {
    \"locations\": [
    {\"name\": \"Seattle\", \"state\": \"WA\"},
    {\"name\": \"New York\", \"state\": \"NY\"},
    {\"name\": \"Bellevue\", \"state\": \"WA\"},
    {\"name\": \"Olympia\", \"state\": \"WA\"}
    ]
    }

  • It depends on how you use it. If you output HTML via Util.RawHtml or LINQPad.Controls.Literal, there should be little difference in performance over dumping a WPF/WinForms WebView2 control. However, if you subsequently communicate with the browser via methods such as InvokeScript/ExecuteScriptAsync, the round-trip overhead will be lower with your own WebView2 control in that you're crossing one less process boundary. A big factor is the number of round-trips - batching JavaScript into a single call will be much faster than lots of small calls.

    The built-it WebView2 will be smoother in terms of integrating with the parent process. (LINQPad hotkeys will work correctly, you can scroll the browser without focusing it with the Alt+cursor keys, etc).

  • @nescafe and @yuzd - thanks - these bugs should now be fixed in 6.13.4.

  • No, I don't call an external browser but I dump a custom Winforms Control that hosts a web-browser control. So even if the user hasn't selected to use the new WebView2 I want to host it if LINQPad and the user's environment support it.

  • I see. A simple IsChromiumSupported property won't be enough for this: if it returned true, you wouldn't be able to find LINQPad's WebView2 folder and the control would fail to initialize (unless the user had installed the Evergreen runtime).

    What it sounds like you need is a Util.BrowserEngine.GetWebView2ExecutableFolder() method. This would call back to the LINQPad host, update the installation if necessary, and return either a valid WebView2 folder or null if unavailable. Here's how you would use it:

    async Task Main()
    {
        var env = await GetWeb2EnvironmentAsync();
        if (env != null)
        {
            var browser = new WebView2().Dump();
            await browser.EnsureCoreWebView2Async (env);
            browser.Source = new Uri ("https://www.linqpad.net");
        }
        else
        {
            // WebView2 not available...
        }
    }
    
    static Task<CoreWebView2Environment> _environment;
    public static Task<CoreWebView2Environment> GetWeb2EnvironmentAsync()
    {
        return _environment ??= Get();
    
        async Task<CoreWebView2Environment> Get()
        {
            var browserFolder = Util.BrowserEngine.GetWebView2ExecutableFolder();
            if (browserFolder == null) return null;
    
            var dataFolder = Path.Combine (
                Environment.GetFolderPath (System.Environment.SpecialFolder.LocalApplicationData),
                "MyApp",
                "WebView2Data");
    
            try
            {
                return await CoreWebView2Environment.CreateAsync (browserFolder, dataFolder);
            }
            catch (Exception ex)
            {
                // TODO: Log exception...
                return null;
            }
        }
    }
    
  • I haven't looked in detail at how WebView2 works but is it required to use the same folder as the LINQPad one? I think if I create a new folder it will initialize with default settings and launch a new process.

  • You can use whatever folder you like - as long as you're willing to download WebView2 yourself - or copy the files from the Edge Chromium installation. In which case you don't need any help from me.

  • I'm not sure what you mean by download. If the user has Edge or the runtime installed isn't the control available for all apps on the system?

  • Yes and no:
    If the user has the WebView2 Evergreen runtime installed, it IS available to all apps on the system.
    if the user simply has Edge installed, it's NOT available to all apps on the system.

    In the medium/long term, I believe MS is planning to ship WebView2 runtime with Windows 10, but right now, you can't count on WebView2 being installed, and an Edge installation will not do the same job. This is why LINQPad includes a mechanism to copy the latest Edge installation into another folder - this allows it to be used without downloading and installing WebView2 runtime.

    It's explained in detail here:
    https://github.com/MicrosoftEdge/WebView2Feedback/issues/348

  • The Export to [Word/Excel] With Formatting issue still exists in 6.13.4 after DumpContainer refresh: http://share.linqpad.net/lmvl8w.linq

    var dc = new DumpContainer("abc").Dump();
    dc.Refresh();
    

    Outputted Text/Cell: "\u003Cdiv id=\"final\">\u003Cdiv id=\"c1\">\u003Cdiv>abc\u003C/div>\u003C/div>\u003C/div>\n"

    The window flashing issue still exists. Created a trace of closing the results window in Process Monitor. This is the Process Activity Summary:

    Closing the query window with results pane with Internet Explorer:

    Closing the query window with results pane with Edge Chromium:

    Perhaps the actual window creation can be profiled with Spy++, but I could not find a working version yet.

  • In that case it will be very helpful if there is extensibility point so that visualizers can use WebView2 too

  • Both issues fixed - thanks.

  • edited March 2021

    Not sure if related but there is a horizontal scrolling issue - scrambled text after scrolling until repaint (e.g. context menu or tooltip popup in editor).

    When scrolling to the right, the right portion of the screen is scrambled.

    When scrolling to the left, the left portion of the screen is scrambled.

  • not work well in webview2
    can not get return value

    public static object RunJavaScript(this LINQPad.Controls.Literal literal,
                                               string jsFunction, params object[] p)
        {
            return literal.Dump().HtmlElement.InvokeScript(true, jsFunction, p);
        }
    
  • That's a roundabout way of doing it... have you tried Util.InvokeScript?

    And can you give an example of what script you're running?

  • edited March 2021

    ("function test(){return 'aaaa'}").CreateJavaScript().RunJavaScript("test").Dump();

    MyExtention.cs

    public static object RunJavaScript(this LINQPad.Controls.Literal literal,
                                               string jsFunction, params object[] p)
        {
            return literal.Dump().HtmlElement.InvokeScript(true, jsFunction, p);
        }
    
        public static LINQPad.Controls.Literal CreateJavaScript(this string jsFunction)
        {
            return new LINQPad.Controls.Literal("script", jsFunction);
        }
        public static object RunJavaScript(Expression<Func<LINQPad.Controls.Literal>> expr,
                                          params object[] p)
        {
            LINQPad.Controls.Literal exprValue = expr.Compile()();
            string jsFunction = ((MemberExpression)expr.Body).Member.Name;
            return exprValue.Dump().HtmlElement.InvokeScript(true, jsFunction, p);
        }
    

    i change my code to

    Util.HtmlHead.AddScript
    Util.InvokeScript
    

    work well now . thanks

  • Unless your script needs to be called directly by HTML elements that you dump, it can be easiest to execute JavaScript directly via an eval (without defining a function).

    For example:

    "abc".ToCharArray().Dump();
    "def".ToCharArray().Dump();
    
    var script = @"
        var tables = document.getElementsByTagName ('table');
    
        for (let table of tables)
            table.style.border = '4px red solid';
    
        tables.length;  // return the number of tables to the caller";
    
    int tableCount = int.Parse (Util.InvokeScript (true, "eval", script).ToString());
    tableCount.Dump ("Number of tables");
    

    Note that when the Chromium browser is enabled, LINQPad doesn't actually call "eval" in this scenario: When the script name is "eval" and a single argument follows, it passes that argument (i.e., script) directly to the WebView2 instance's ExecuteScriptAsync method.

  • edited March 2021

    I just gotta say, I love this so much. The IE engine was frustrating to use, especially when you wanted to perform searches within the results.

    I'm not too familiar with the capabilities of webview but from what I've found, I see some potential for automation scripting for web apps. I know using Selennium is pretty standard procedure nowadays, but it would be awesome if LINQPad could set up a framework for loading a page and interacting with it. Possibly even manipulating it further injecting scripts to add additional functionality if possible to augment the experience.

  • The window appearing for a fraction of a second issue is still there if you output in mixed mode (results + grid).

    Code to reproduce (C# Statement(s) mode):

    Enumerable.Range(0, 10).Dump(true);
    Enumerable.Range(0, 10).Dump(false);
    

    1) Run code (F5)
    2) Close window (CTRL-W)

    (Note that the IEnumerable tab is active on closing action. The window does not appear if the Results tab is active when closing.)

  • Try 6.14.2: It now de-parents the control as well as hiding it.

  • Any way to disable smooth scrolling? It's probably just me but it makes the application feel not as snappy as before.

    In Edge browser this can be disabled via edge://flags/#smooth-scrolling

Sign In or Register to comment.