MicrosoftEdge / WebView2Feedback

Feedback and discussions about Microsoft Edge WebView2
https://aka.ms/webview2
452 stars 55 forks source link

NavigateToString with a very large string shows error "Value does not fall within the expected range" #1355

Open smohanty05 opened 3 years ago

smohanty05 commented 3 years ago

Hi, I am using WPF Webview2 control. I saw with some specific large data NavigateToString method throws below error: _System.ArgumentException HResult=0x80070057 Message=Value does not fall within the expected range. Source=Microsoft.Web.WebView2.Core StackTrace: at Microsoft.Web.WebView2.Core.Raw.ICoreWebView2.NavigateToString(String htmlContent) at Microsoft.Web.WebView2.Core.CoreWebView2.NavigateToString(String htmlContent) at WebView2Sample.MainWindow.d_1.MoveNext()

Note: The issue is not seen using Navigate method or setting Source property.

Is this a known issue? NavigateToString has any limitations?

AB#33629225

champnic commented 3 years ago

There probably is some limitation to the size of string that can be navigated. I've opened this as a doc bug to figure out what that limit is and update our documentation. Thanks!

smohanty05 commented 3 years ago

Thanks for confirming this is a known issue!!

ShaunLoganOracle commented 3 years ago

In the version of the SDK I use (1.0.705.50), for WinForms, I see CoreWebView2.NavigateToString has a 2MB limit specified in the doc for that method.

      //
      // Summary:
      //     Initiates a navigation to htmlContent as source HTML of a new document.
      //
      // Parameters:
      //   htmlContent:
      //     A source HTML of a new document.
      //
      // Remarks:
      //     The htmlContent parameter may not be larger than 2 MB in total size. The origin
      //     of the new page is about:blank.
      public void NavigateToString (string htmlContent);
champnic commented 3 years ago

Nice find @ShaunLoganOracle! It looks like we have that remark in the CoreWebView2 version of the function, but not in the WPF or WinForms WebView2 versions. https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.navigatetostring?view=webview2-dotnet-1.0.864.35#remarks

clovett commented 3 years ago

Why the limit at all? This is very annoying. I did not have this limit on the old System.Windows.Forms.WebBrowser component ?!

champnic commented 3 years ago

We've updated the docs to be consistent, thanks!

champnic commented 3 years ago

@clovett I believe the issue is the cross-process call from the host app to the WebView2 processes. The WebBrowser didn't have this limitation because it was all in-proc.

lovettchris commented 3 years ago

I really don't care about your implementation details, you should find a solution. Pushing weird limitations like this off on the customer is why people complain about windows API's all the time. Couldn't you wrap the call in your WinForms, Wpf libraries and solve this some hidden way so it is not exposed to the customer like this?

champnic commented 3 years ago

Sure I can reopen the bug to take a look at the underlying limitation. Thanks!

burleyman24 commented 3 years ago

The 2MB limitation prevents any real ability to include inline images in the the NavigateToString method. This is a key functionality that existed previously in the WebBrowser component and would be a key ability for us to migrate to WebView2. Here is our up vote to find a solution to this limitation.

DavidBerg-MSFT commented 3 years ago

Same here. I just ported my app to WebView2. It passed my basic tests, then I went to demo and got this crash. I checked, the string I was trying to navigate to was 4MB. Fortunately I put in a switch, so I can just change the default and revert, but it's annoying. I'm mostly still using the old (IE based) WebBrowser because both WebView and WebView2 have blocking bugs (also, my WebView2 code is more complex since I have to initialize the component and then wait for it before I can do anything else).

Is there any other way to pass a large string? (I suppose I could write it to a temp file, then navigate to the file, but then I have to remember to clean up the temp file).

How hard would it be for the NavigateToString method to chunk the string up and pass it across process in parts? (Or create an appropriate sized shared memory region?)

lovettchris commented 3 years ago

@DavidBerg-MSFT yeah, I wish they could fix this, I had to create a temp file when using webview2. See XsltControl and I also fall back on the old WebBrowser component if I can't find webview2.... definitely makes my code more complicated. Would be nice if they did this under the covers in WebBrowser control so I didn't need to change anything :-)

DavidBerg-MSFT commented 3 years ago

@lovettchris thanks for the code reference. FYI, I double checked, I was using WebView not WebBrowser (in that case, there's another location where I'm still using WebBrowser and got confused). I also wrote a wrapper control so I could switch back and forth (it supports all three), but it's not as sophisticated as yours (yet :-)). I hadn't thought about the need to fall back for installation failures.

I still need to track down all the places in my code and see if I can't at least consolidate on the wrapper control. That would make it easy to add the length check and switch to a temp file, like you did.

I think the 2MB limit is because of a concern or limit for passing large amounts of data cross process... (I can think of many ways to fix that, but it requires changing code on both sides), and, of course 2MB becomes 1M chars since C# strings are Unicode.

Main thing is that it's certainly not just a plug and play replacement.

lovettchris commented 3 years ago

all true, but it's worth the pain, it is way faster, even including writing to a temp file :-)

DavidBerg-MSFT commented 3 years ago

Thanks. Good to know. Actually, I have some shared code that I've been passing in a string, I wonder if it would work if I wrote that to a [semi-persistent] temp file and referenced it? (I tried loading it directly off the web, but that yielded a trust zone violation.)

From: Chris Lovett @.> Sent: Wednesday, October 6, 2021 6:22 PM To: MicrosoftEdge/WebView2Feedback @.> Cc: David Berg @.>; Mention @.> Subject: Re: [MicrosoftEdge/WebView2Feedback] NavigateToString with a very large string shows error "Value does not fall within the expected range" (#1355)

all true, but it's worth the pain, it is way faster, even including writing to a temp file :-)

- You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2FMicrosoftEdge%2FWebView2Feedback%2Fissues%2F1355%23issuecomment-937369411&data=04%7C01%7CDavid.Berg%40Microsoft.com%7Cbd8859906fcc4959b09f08d98930df19%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637691665281511302%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=zq1EQzWM102nbEJ%2B2umc6klpEerIF6TOqUEtBISr7L8%3D&reserved=0, or unsubscribehttps://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FACGECRRBQ7LXXDSV47VEPMTUFTY3XANCNFSM46CFHPOQ&data=04%7C01%7CDavid.Berg%40Microsoft.com%7Cbd8859906fcc4959b09f08d98930df19%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637691665281521263%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=A8KEd%2F%2FsTrqMR8Jf%2FRTUpETRrtZE0zOqY%2FzLkNKUDZo%3D&reserved=0. Triage notifications on the go with GitHub Mobile for iOShttps://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fapps.apple.com%2Fapp%2Fapple-store%2Fid1477376905%3Fct%3Dnotification-email%26mt%3D8%26pt%3D524675&data=04%7C01%7CDavid.Berg%40Microsoft.com%7Cbd8859906fcc4959b09f08d98930df19%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637691665281521263%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=p5dsTzkq67fmXo5Ts%2Fjs0QQ4dmG74uoFSV3YNG%2BBqFs%3D&reserved=0 or Androidhttps://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dcom.github.android%26referrer%3Dutm_campaign%253Dnotification-email%2526utm_medium%253Demail%2526utm_source%253Dgithub&data=04%7C01%7CDavid.Berg%40Microsoft.com%7Cbd8859906fcc4959b09f08d98930df19%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637691665281531218%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=P87NEpLc%2BxbduNrsqqlakgX9b%2FOPBycYXZoEEk%2FZ1RA%3D&reserved=0.

FadiRached commented 2 years ago

The old WebView control had no limit on string size when Navigating to a string. We have had to add a switch to revert back to the old WebView just because of this single function as our html strings exceed 2mb. Please fix.

jamesrolf commented 2 years ago

I have worked around this issue by writing the text content locally then loading from file:

...
private string latestFilename = String.Empty;
...

public void NavigateToStringWorkaround(string content) {
    Guid filename = Guid.NewGuid();
    File.WriteAllText($"{filename}.html}", content);
    Source = new Uri($"{Directory.GetCurrentDirectory()}/{filename}.html");
    if (latestFilename != String.Empty)
        File.Delete($"{Directory.GetCurrentDirectory()}/{latestFilename}.html");
    latestFilename = filename.ToString();
}

I appreciate this might not be suitable in all applications.

stefansjfw commented 2 years ago

For me it's not even 2MB, but ~1.5MB. E.g. for 1572927B svg file I get this error, and for 1572769B svg file I don't 🤷‍♂️

tyeth commented 2 years ago

This is why we struggle to love Edge / Windows (especially 11). The rhyme and reason for majorly impactful decisions is left to internal codebase comments or random tech blog posts, and all github issues are closed as dupes or wontfix making the knowledge harder to find (people search open issues by default).

The cause and issue has been identified a year ago and the impact grows with each day (more people update). @champnic any update or is there a microsoft hitsquad that can tackle such issues? It seems PowerToys have got around it, but can we fix it at the root of the issue, i.e. Edge?

champnic commented 2 years ago

Hey @tyeth - If you see any of our issues incorrectly getting closed as dupes or won't fix, please let me know and I'll take a look.

I've bumped the priority on this issue and we'll see if we can getting someone looking at it soon. While I like the idea of a Microsoft hit-squad for-hire that goes around fixing issues, that doesn't exist quite yet :P

burleyman24 commented 2 years ago

We had to abandon a previous project's desire to use WebView2 due to this limitation. Checking back here a year later it looks like no progress has been made which is disappointing. Would really like to upgrade our project but this is a deal breaker.

We went the rounds trying to make it work through other means but always ran into errors. The only real work around was to discard NavigateToString, create local copies of the desired files, and then navigate to files locally. Not a very clean solution especially when dealing with sensitive information that we didn't want stored on a computer; We are dealing with encrypted data so the ideal situation is to decrypt and display without saving sensitive data to a file system. Eagerly awaiting a review and possible solution.

Viir commented 2 years ago

The only real work around was to discard NavigateToString, create local copies of the desired files, and then navigate to files locally. Not a very clean solution especially when dealing with sensitive information that we didn't want stored on a computer; We are dealing with encrypted data so the ideal situation is to decrypt and display without saving sensitive data to a file system. Eagerly awaiting a review and possible solution.

@burleyman24

I see a workaround fitting your application better: Start an HTTP server to deliver the sensitive content in the same process that hosts WebView2. This way, we don't need to write the sensitive data into a file. This is cleaner and avoids the race condition where the process is stopped before it gets to delete the said file. The information stays in the same process where you have it already anyway.

Maciejszuchta commented 2 years ago

Hello, any updates on that issue?

TorgeirH90 commented 1 year ago

A 2MB limit is a bit low, hopefully this will be fixed in the future

drittich commented 1 year ago

The max allowed content length appears to be 1,572,834.

greatoceansoftware commented 1 year ago

In a 64-bit world, why is this a limitation? Please fix. The whole point of NavigateToString is working in memory, not disk.

peiche-jessica commented 1 year ago

Hi all, my apologies for the late reply.

To workaroud the 2MB limit on NavigateToString, you can AddWebResourceRequestedFilter to a custom URL of yours, subscribe and handle the WebResourceRequested event. During the event, you can intercept the web resource request and set the response using the larger html content.

private bool _replaceLargeHtmlString = false;

CoreWebView2Environment _webViewEnvironment;
CoreWebView2Environment WebViewEnvironment
{
    get
    {
        if (_webViewEnvironment == null && webView?.CoreWebView2 != null)
        {
            _webViewEnvironment = webView.CoreWebView2.Environment;
        }
        return _webViewEnvironment;
    }
}

void WebView_OnWebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs e)
{
    // Intercept the web resource request set the response as the large html content string.
    string responseDataString = "<html><head><title>Hello World</title></head><body><h1>Large content</h1></body></html>";
    UTF8Encoding utfEncoding = new UTF8Encoding();
    byte[] responseData = utfEncoding.GetBytes(
        responseDataString);
    MemoryStream responseDataStream = new MemoryStream(responseDataString.Length);
    responseDataStream.Write(responseData, 0, responseData.Length);
    responseDataStream.Seek(0, SeekOrigin.Begin);
    CoreWebView2WebResourceResponse webResourceResponse =
        WebViewEnvironment.CreateWebResourceResponse(
        responseDataStream,
        200,
        "OK",
        "Content-Type: text/html\r\n");
    e.Response = webResourceResponse;
}

void NavigateWithWebLargeStringCmdExecuted(object target, ExecutedRoutedEventArgs e)
{
    try
    {
        if (!_replaceLargeHtmlString)
        {
            // Add a filter to intercept requests made to https://example.com
            // Then replace the response with a large html string.
            webView.CoreWebView2.AddWebResourceRequestedFilter("https://example.com", CoreWebView2WebResourceContext.All);
            webView.CoreWebView2.WebResourceRequested += WebView_OnWebResourceRequested;
        }
        else
        {
            webView.CoreWebView2.RemoveWebResourceRequestedFilter("*example.com*", CoreWebView2WebResourceContext.All);
            webView.CoreWebView2.WebResourceRequested -= WebView_OnWebResourceRequested;
        }
        _replaceLargeHtmlString = !_replaceLargeHtmlString;
    }
    catch (NotImplementedException exception)
    {
        MessageBox.Show(this, "Web Resource Requested Listeners: " + exception.Message,
                        "Web Resource Requested Listeners");
    }
}
greatoceansoftware commented 1 year ago

Thank you for this, Jessica. Although I can't get it to work. I can't find any name space for WebViewEnvironment. I did find CoreWebView2Environment, but that takes Windows.Storage streams and buffers parameters instead of the System.IO streams.

Also, I'm unsure why it's necessary to flip back and forth between replacement and not replacement in your solution (replaceLargeHtmlString). Why not replace all the time? Is it because the WebResourceRequestedFilter is required to trigger the OnWebResourceRequested event? I am foregoing your Command approach and registering the event when the page is loaded, but can't seem to get the event to fire.

pjy612 commented 1 year ago

Is there any latest progress? There is a possibility of encountering this issue at present.

peiche-jessica commented 1 year ago

@greatoceansoftware My apologies for the confusion. You can obtain the CoreWebView2Environment object via CoreWebView2. WebResourceRequestedFilter is required for the WebResourceRequested events to fired for the filtered resource.

@pjy612 Does the current workaround work with your scenario?

pjy612 commented 1 year ago

@greatoceansoftware My apologies for the confusion. You can obtain the CoreWebView2Environment object via CoreWebView2. WebResourceRequestedFilter is required for the WebResourceRequested events to fired for the filtered resource.

@pjy612 Does the current workaround work with your scenario?

the currently researched solution for use in UWP

public static class WebView2LargeHtmlExtension
{
    /// <summary>
    /// htmlTemplate Cache
    /// </summary>
    static readonly Dictionary<string, string> TemplateCache = new Dictionary<string, string>();

    public static async void NavigateToHtml(this WebView2 browser, string template)
    {
        string key = Guid.NewGuid().ToString();
        TemplateCache[key] = template;
        browser.Source = new Uri($"https://template/?key={key}");
    }

    public static void RegLargeHtmlHandler(this CoreWebView2 cwv2)
    {
        cwv2.AddWebResourceRequestedFilter("https://template/*", CoreWebView2WebResourceContext.All);
        cwv2.WebResourceRequested += CoreWebView2_WebResourceRequested_LargeHtmlHandler;
    }
    private static async void CoreWebView2_WebResourceRequested_LargeHtmlHandler(CoreWebView2 sender, CoreWebView2WebResourceRequestedEventArgs args)
    {
        string requestUri = args.Request.Uri;
        if (requestUri.StartsWith("https://template/"))
        {
            Deferral def = args.GetDeferral();
            try
            {
                Uri uri = new Uri(requestUri);
                NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(uri.Query);
                string headers = $"Content-Type: text/html; charset=utf-8";
                if (TemplateCache.Remove(queryString["key"], out string html))
                {
                    InMemoryRandomAccessStream ms = new InMemoryRandomAccessStream();
                    using (var dataWriter = new DataWriter(ms))
                    {
                        dataWriter.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                        dataWriter.ByteOrder = ByteOrder.LittleEndian;
                        dataWriter.WriteString(html);
                        await dataWriter.StoreAsync();
                        await dataWriter.FlushAsync();
                        dataWriter.DetachStream();
                        ms.Seek(0);
                    }
                    args.Response = sender.Environment.CreateWebResourceResponse(ms, 200, "OK", headers);
                }
            }
            catch (Exception)
            {
                args.Response = sender.Environment.CreateWebResourceResponse(null, 404, "Not found", "");
            }
            finally
            {
                def.Complete();
            }
        }
    }
}

private async void Webview2_CoreWebView2Initialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
{
    sender.CoreWebView2.RegLargeHtmlHandler();
    //other WebView2 CoreWebView2 init code
}
greatoceansoftware commented 10 months ago

Any updates on this issue? Would really like this to work.

overdoignism commented 8 months ago

The problem remains, very annoying It is already 2024/3 :(

LaughingJohn commented 7 months ago

Just wanted to add my voice to this issue! The data we wish to display is sensitive so the workaround of writing to a file doesn't help unfortunately.

peiche-jessica commented 7 months ago

Hi, just in case people missed this in this super long thread, this is our suggested solution for this issue at the moment. I cleaned up the previous snippet I sent to only include the parts relevant.

It is an in-memory solution, and does not require writing to a file. This uses the WebResourceRequested method of dynamically loading local content as described in our documentation Working with local content in WebView2 apps - Microsoft Edge Developer documentation | Microsoft Learn.

void AddResourceFilterCmdExecuted(object target, ExecutedRoutedEventArgs e)
{
    // Add a filter to intercept requests made to https://example.com
    // Then replace the response with a large html string.
    webView.CoreWebView2.AddWebResourceRequestedFilter("https://example.com", CoreWebView2WebResourceContext.All);
    webView.CoreWebView2.WebResourceRequested += WebView_OnWebResourceRequested;
}

void WebView_OnWebResourceRequested(object sender, CoreWebView2WebResourceRequestedEventArgs e)
{
    // Intercept the web resource request; set the response as the large html content string.
    string responseDataString = "<html><head><title>Hello World</title></head><body><h1>Large content</h1></body></html>";
    UTF8Encoding utfEncoding = new UTF8Encoding();
    byte[] responseData = utfEncoding.GetBytes(
        responseDataString);
    MemoryStream responseDataStream = new MemoryStream(responseDataString.Length);
    responseDataStream.Write(responseData, 0, responseData.Length);
    responseDataStream.Seek(0, SeekOrigin.Begin);
    CoreWebView2WebResourceResponse webResourceResponse =
        webView.CoreWebView2.Environment.CreateWebResourceResponse(
        responseDataStream,
        200,
        "OK",
        "Content-Type: text/html\r\n");
    e.Response = webResourceResponse;
}

Please let us know if this does not address your needs.

robigit commented 7 months ago

Hi, I'm having the same problem in a 4.8 WinForm App. I tried to implement the fix suggested by @peiche-jessica but the WebView_OnWebResourceRequested is never hit, even if I put "*" in the AddWebResourceRequestedFilter. Do you have any suggestion ? Thanks

danielklecha commented 6 months ago

@robigit I use WinForm 4.8 and this extension worked correctly:

public static void NavigateToLargeString(this WebView2 webView, string value)
{
    var hostname = $"{Guid.NewGuid()}.com";
    var url = $"https://{hostname}/";
    var filter = $"*://{hostname}/*";
    EventHandler<CoreWebView2WebResourceRequestedEventArgs> webResourceRequestedHandler = null;
    webResourceRequestedHandler = (sender, e) =>
    {
        if (!e.Request.Uri.Equals(url, StringComparison.OrdinalIgnoreCase))
            return;
        var def = e.GetDeferral();
        try
        {
            e.Response = webView.CoreWebView2.Environment.CreateWebResourceResponse(new MemoryStream(Encoding.UTF8.GetBytes(value)), 200, "OK", "Content-Type: text/html; charset=utf-8");
        }
        catch (Exception)
        {
            e.Response = webView.CoreWebView2.Environment.CreateWebResourceResponse(null, 404, "Not found", "Content-Type: text/html; charset=utf-8");
        }
        finally
        {
            def.Complete();
        }
    };
    EventHandler<CoreWebView2NavigationStartingEventArgs> navigationStartingEvent = null;
    navigationStartingEvent = (sender, e) =>
    {
        if (!e.Uri.Equals(url, StringComparison.OrdinalIgnoreCase))
        {
            webView.CoreWebView2.RemoveWebResourceRequestedFilter(filter, CoreWebView2WebResourceContext.Document);
            if (webResourceRequestedHandler != null)
                webView.CoreWebView2.WebResourceRequested -= webResourceRequestedHandler;
            if (navigationStartingEvent != null)
                webView.CoreWebView2.NavigationStarting -= navigationStartingEvent;
        }
    };
    webView.CoreWebView2.AddWebResourceRequestedFilter(filter, CoreWebView2WebResourceContext.Document);
    webView.CoreWebView2.WebResourceRequested += webResourceRequestedHandler;
    webView.CoreWebView2.NavigationStarting += navigationStartingEvent;
    webView.Navigate(url);
}
robigit commented 6 months ago

@danielklecha Thank you very much, it works also for me. I've found that it is quite slower than the standard CoreWebView2.NavigateToString, I think I'm going to use it only if I catch the exception.

RickStrahl commented 3 months ago

Another workaround is to navigate to a blank page then set the document content via ExecuteScript()

public virtual async Task NavigateToString(string html)
{   
    WebBrowser.Source = new Uri("about:blank");

    string encodedHtml = JsonConvert.SerializeObject(html);
    string script = "window.document.write(" + encodedHtml + ")";

    await Task.Yield();
    await WebBrowser.ExecuteScriptAsync(script);            
}
FadiRached commented 2 months ago

@RickStrahl Thanks Rick! A simple yet effective fix.

greatoceansoftware commented 4 weeks ago

Any update on this? I would really just like to use NavigateToString.