4

I have code (and for decades) that:

  • given some HTML string in memory
  • hand that document to an Internet Explorer object
  • and make Internet Explorer (separate process) visible
  • all without littering the user's computer with temporary files

In other words:

void SpawnIEWithSource(string szSourceHTML)
{
   IWebBrowser ie = (IWebBrowser)CoCreateInstance(CLASS_InternetExplorer, null, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, 
         UuidOf(IUnknown));

   ie.Navigate2("about:blank");
   ie.Document.Write(szSourceHtml);
   ie.Document.Close;
   ie.Visible = True;
}

But IE is going away

Microsoft recently announced that Internet Explorer (the product) will no longer come with Windows, but Internet Explorer (the programming api) will continue to work:

As announced today, Microsoft Edge with IE mode is officially replacing the Internet Explorer 11 desktop application on Windows 10. As a result, the Internet Explorer 11 desktop application will go out of support and be retired on June 15, 2022 for certain versions of Windows 10.

Out of scope at the time of this announcement (unaffected):

  • Internet Explorer mode in Microsoft Edge
  • Internet Explorer platform (MSHTML/Trident), including WebOC
  • Internet Explorer 11 desktop application on:
    • Windows 8.1
    • Windows 7 Extended Security Updates (ESU)
    • Windows 10 Server SAC (all versions)
    • Windows 10 IoT Long-Term Servicing Channel (LTSC) (all versions)
    • Windows 10 Server LTSC (all versions)
    • Windows 10 client LTSC (all versions)

What is the MSHTML (Trident) engine? How does that relate to IE mode?

The MSHTML (Trident) engine is the underlying platform for Internet Explorer 11. This is the same engine used by IE mode and it will continue to be supported (in other words, unaffected by this announcement). WebOC will also continue to be supported. If you have a custom or third-party app that relies on the MSHTML platform, you can expect it to continue to work.

(emphasis mine)

Which means that Microsoft is breaking 23 years of backwards compatibility - and replacing it with...nothing.

So i need to find a way to replace it.

  • spawn the default browser
  • give it HTML i want to display
  • all without temporary files

Temporary Buggy Hack

For now i am using @corbin-c's buggy workaround:

procedure SpawnBrowserWithSource(const szSourceHTML: UnicodeString);
var
    html: RawByteString; //utf8 encoded
    dataUri: string;
    szPath: string;

    function GetDefaultBrowser: string;
    var
        nch: DWORD;
        hr: HRESULT;
    begin
        //Get the default browser (i.e. the association for .html file type)
        nch := 1000;
        SetLength(Result, nch);
        hr := AssocQueryString(0, ASSOCSTR_EXECUTABLE, '.html', nil, PChar(Result), @nch);
        if hr = E_POINTER then
        begin
            //Try again if the buffer was too small
            SetLength(Result, nch);
            hr := AssocQueryString(0, ASSOCSTR_EXECUTABLE, '.html', nil, PChar(Result), @nch);
        end;
        OleCheck(hr);

        SetLength(Result, nch-1); //don't include the null terminator
    end;
begin
{
    With Internet Explorer being deprecated, this also means that the automation will fail.

    So we try to marshall the HTML into base64 encoded URI, and spawn that.

    So spawn it as:

        data:text/html;base64,[base64 encoded html]

    E.g.:
        data:text/html;base64,PEhUTUw+PEJPRFk+SGVsbG8sIHdvcmxkITwvQk9EWT48L0hUTUw+

    ExecuteFile('data:text/html;base64,PEhUTUw+PEJPRFk+SGVsbG8sIHdvcmxkITwvQk9EWT48L0hUTUw+');
}

    html := UnicodeToRawByteString(#$FEFF+szSourceHTML, CP_UTF8);
    dataUri := 'data:text/html;base64,'+TBase64.ToBase64String(html);

    szPath := GetDefaultBrowser;
    if szPath = '' then
        raise Exception.Create('Could not locate default browser');

    ExecuteFile(szPath, dataUri);
end;

Bonus Reading

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
  • Would writing to RAM instead of the filesystem be acceptable? – Spyre Nov 01 '21 at 14:50
  • @Spyre That would be acceptable; is that is basically what is happening now. I considered creating a TCP listening socket that serves the content, but some corporate machines block opening listening sockets. – Ian Boyd Nov 01 '21 at 15:33
  • Can you give an example `szSourceHTML` that works with your existing code? – Spyre Nov 01 '21 at 20:09
  • @Spyre `Hello, world!` – Ian Boyd Nov 02 '21 at 16:57
  • Do we need to dynamically adapt to the default browser, or would it be fine to write something built specifically for a browser that will likely not become obsolete in the near future (i.e. Firefox, Chrome/ Chromium, etc.) – Spyre Nov 02 '21 at 20:38
  • 1
    @Spyre The virtue of using Internet Explorer before is that it was a guaranteed part of the OS - every version of Windows had it. Without IE, i can't even know if the old Edge would be installed, or even the new Edge - not to mention Chrome, Firefox, or Brave. The `data:text/html` schema was nice, as that would launch in whatever browser supported it. – Ian Boyd Nov 02 '21 at 21:36

2 Answers2

1

I'd encode HTML into Base64 and make it a Data-URI. All modern browsers are able to handle such URIs. This however won't work if your HTML string is too big.

Basically, it'd go like this:

String Base64Encode(string plainText) 
{
   //Don't forget to add the leading U+FEFF character so that Chrome can recognize the encoding.
   var plainTextBytes = System.Text.Encoding.UTF8.GetBytes("\uFEFF"+plainText);
   return System.Convert.ToBase64String(plainTextBytes);
}
    
void SpawnBrowserWithSource(string szSourceHTML) 
{
   System.Diagnostics.Process.Start("data:text/html;base64," + Base64Encode(szSourceHTML));
}    

Resulting in:

enter image description here

(code snippets are from the following Stackoverflow answers: base64 encoding and opening default browser)

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
corbin-c
  • 669
  • 8
  • 20
  • Hmm, a length limitation is kind of a problem. It's usually used to dump out a ad-hoc database report - few hundred thousand rows in an html table. – Ian Boyd Nov 02 '21 at 16:58
  • according to [this answer](https://stackoverflow.com/a/41755526/8086209), Chrome's limit appears to be 2MB, Firefox doesn't have limit & IE>9/Edge has a 4GB limit. So Chrome is the limiting factor here. Is your HTML bigger than 2MB? – corbin-c Nov 03 '21 at 10:26
  • I suppose the best option is to check for the presence of IE (whatever that looks like once it disappears), and if it's not there launch it as base64 encoded and hope it doesn't get cut off. – Ian Boyd Nov 06 '21 at 14:15
  • Unfortunately the reality is that the maximum you're allowed in `32767` - `MAX_PATH` - which is woefully inadequate for any real world scenarios. – Ian Boyd Jan 21 '22 at 16:14
0

If you know the user uses Chrome or Edge, you can replace the WebBrowser instance with ChromiumWebBrowser, and use the LoadHtml method to inject the content.

Relevant links:

web browser control in winform with google chrome c# https://www.telerik.com/support/kb/winforms/details/how-to-embed-chrome-browser-in-a-winforms-application https://cefsharp.github.io/api/51.0.0/html/T_CefSharp_WinForms_ChromiumWebBrowser.htm

ouflak
  • 2,458
  • 10
  • 44
  • 49
S Shahar
  • 79
  • 4
  • I'm not using a web browser control. I'm launching the *actual* browser out-of-process. – Ian Boyd Nov 06 '21 at 14:14
  • Then you could try to insert the GUID of Chrome or of Edge instead of the CLASS_InternetExplorer parameter of CoCreateInstance, I'm not sure if there will be a constant with that GUID. – S Shahar Nov 07 '21 at 12:15
  • *Edit*: I don't know of a good way to start the default browser when using CCreateInstance. You can try this for Chrome: {418DFBFA-4DE8-41C0-A272-727307252DBD} – S Shahar Nov 07 '21 at 12:30