Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
smohanty05 opened this issue Jun 4, 2021 · 42 comments
Labels
bug Something isn't working tracked We are tracking this work internally.

Comments

@smohanty05
Copy link

smohanty05 commented Jun 4, 2021

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.<Window_Loaded>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 champnic added bug Something isn't working tracked We are tracking this work internally. labels Jun 5, 2021
@champnic
Copy link
Member

champnic commented Jun 5, 2021

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
Copy link
Author

Thanks for confirming this is a known issue!!

@ShaunLoganOracle
Copy link

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
Copy link
Member

champnic commented Jun 8, 2021

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
Copy link

clovett commented Jul 14, 2021

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

@champnic
Copy link
Member

champnic commented Aug 3, 2021

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

@champnic champnic closed this as completed Aug 3, 2021
@champnic
Copy link
Member

champnic commented Aug 3, 2021

@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
Copy link

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
Copy link
Member

champnic commented Aug 4, 2021

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

@champnic champnic reopened this Aug 4, 2021
@burleyman24
Copy link

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
Copy link

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
Copy link

@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
Copy link

@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
Copy link

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

@DavidBerg-MSFT
Copy link

DavidBerg-MSFT commented Oct 7, 2021 via email

@FadiRached
Copy link

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
Copy link

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
Copy link

stefansjfw commented May 13, 2022

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
Copy link

tyeth commented May 25, 2022

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
Copy link
Member

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
Copy link

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
Copy link

Viir commented Oct 2, 2022

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
Copy link

Hello, any updates on that issue?

@TorgeirH90
Copy link

TorgeirH90 commented Jan 16, 2023

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

@drittich
Copy link

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

@greatoceansoftware
Copy link

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
Copy link
Collaborator

peiche-jessica commented Mar 17, 2023

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
Copy link

greatoceansoftware commented Apr 3, 2023

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
Copy link

pjy612 commented May 15, 2023

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

@peiche-jessica
Copy link
Collaborator

@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
Copy link

pjy612 commented May 19, 2023

@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
Copy link

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

@overdoignism
Copy link

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

@LaughingJohn
Copy link

LaughingJohn commented Mar 21, 2024

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
Copy link
Collaborator

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
Copy link

robigit commented Apr 10, 2024

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
Copy link

@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
Copy link

robigit commented May 9, 2024

@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
Copy link

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
Copy link

FadiRached commented Aug 13, 2024

@RickStrahl Thanks Rick! A simple yet effective fix.

@greatoceansoftware
Copy link

greatoceansoftware commented Oct 11, 2024

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

@pcinfogmach
Copy link

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

using about blank did not work for me i just used
await EnsureCoreWebView2Async();
instead

many thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working tracked We are tracking this work internally.
Projects
None yet
Development

No branches or pull requests