3

I'm using this code from StackOverflow (How can I get HTML source code from TWebBrowser) to get the full response from a webpage:

function TMain.GetWebBrowserHTML(const WebBrowser: TWebBrowser): String;
var
  LStream: TStringStream;
  Stream: IStream;
  LPersistStreamInit: IPersistStreamInit;
begin
  if not Assigned(WebBrowser.Document) then
    Exit;
  LStream := TStringStream.Create('');
  try
    LPersistStreamInit := WebBrowser.Document as
      IPersistStreamInit;
    Stream := TStreamAdapter.Create(LStream, soReference);
    LPersistStreamInit.Save(Stream, True);
    Result := LStream.DataString;
  finally
    LStream.Free();
  end;
end;

After a couple of hundred calls to the routine with some large web pages, I'm out of memory.

Apparently there is a known problem with the component's Document property, but the suggestion of replacing WebBrowser.Document with WebBrowser.DefaultInterface.Document doesn't help. I really don't want to try to fix the VCL, and the other suggestion of calling Release might work if I knew where and how to do it. And the leak could be something else entirely. This code is above my pay grade.

I can't use TIdHTTP because of some scripting that has to occur, and I need the visual anyway.

See also: TWebbrowser massive memory leaks : no solution so far

  • Do you know what is the leak exactly? If yes, what is it? If not, use a tool like madExcept and it will tell you what has been allocate and where, but not freed. This could help to know that information. – fpiette Feb 10 '21 at 16:35
  • Interesting, I have worked with `TWebBrowser` many times over the years, and was never aware of this leak. "*the suggestion of replacing `WebBrowser.Document` with `WebBrowser.DefaultInterface.Document` doesn't help*" - why not? – Remy Lebeau Feb 10 '21 at 17:30
  • @DelphiCoder `TStreamAdapter` implements the `IStream` interface, and is being assigned to an `IStream` variable. So normal interface reference counting will handle freeing it. – Remy Lebeau Feb 10 '21 at 17:32
  • @KevinDavidson do you have all of the Updates and Patches installed for 10.3? [RSP-19473](https://quality.embarcadero.com/browse/RSP-19473), which is related to this issue, was closed as "fixed" in 10.3.3. But I have just filed [RSP-32393](https://quality.embarcadero.com/browse/RSP-32393) for the original issue, just in case. – Remy Lebeau Feb 10 '21 at 18:34
  • @RemyLebeau I clearly missed that IStream interface! – Delphi Coder Feb 10 '21 at 18:41
  • 1
    @KevinDavidson the `TOleControl` issue that you are claiming to be encountering was actually fixed in 10.0 Seattle, so you shouldn't be seeing it in 10.3, unless this is a regression, or an entirely new leak. – Remy Lebeau Feb 10 '21 at 22:16
  • The IDE says it's 10.3, but when I looked at the file I installed from, it 10.3.3. I don't have any patches installed. – Kevin Davidson Feb 12 '21 at 01:08
  • Rather than fighting this, I punted, and I am very pleased with the TNetHTTPClient with straightforward access to the information I need. For visual, I'll just shell out to the system browser. This is for my own use, not commercial code. – Kevin Davidson Feb 14 '21 at 00:59

1 Answers1

7

Apparently there is a known problem with the component's Document property

For reference to anyone seeing this:

RSP-32393: Reference leak in TOleControl.GetIDispatchProp and TOleControl.GetIUnknownProp

UPDATE: this issue was reportedly fixed in 10.0 Seattle, so it should not be happening anymore in 10.3.

I really don't want to try to fix the VCL, and the other suggestion of calling Release might work if I knew where and how to do it.

You would call it like this:

function TMain.GetWebBrowserHTML(const WebBrowser: TWebBrowser): String;
var
  Disp: IDispatch;
  LStream: TStringStream;
  Stream: IStream;
  LPersistStreamInit: IPersistStreamInit;
begin
  Disp := WebBrowser.Document;
  if not Assigned(Disp) then
    Exit;
  try
    LStream := TStringStream.Create('');
    try
      LPersistStreamInit := Disp as IPersistStreamInit;
      Stream := TStreamAdapter.Create(LStream, soReference);
      LPersistStreamInit.Save(Stream, True);
      Result := LStream.DataString;
    finally
      LStream.Free;
    end;
  finally
    Disp._Release;
  end;
end;

Thus:

  • the TWebBrowser.Document property returns an IDispatch whose refcount has been erroneously incremented +2 instead of +1 due to a bug in TOleControl
  • the assignment to Disp increments the refcount +1
  • the cast+assignment to LPersistStreamInit increments the refcount +1.

When the function exits:

  • the explicit _Release() decrements the refcount -1 to workaround the bug
  • an implicit _Release() when LPersistStreamInit goes out of scope decrements the refcount -1
  • an implicit _Release() when Disp goes out of scope decrements the refcount -1
  • an implicit _Release() on the Document property's return value decrements the refcount -1.

The refcount is balanced properly.

Alternatively, you can do this instead:

function TMain.GetWebBrowserHTML(const WebBrowser: TWebBrowser): String;
var
  Disp: IDispatch;
  LStream: TStringStream;
  Stream: IStream;
  LPersistStreamInit: IPersistStreamInit;
begin
  Pointer(Disp) := WebBrowser.Document;
  if not Assigned(Disp) then
    Exit;
  LStream := TStringStream.Create('');
  try
    LPersistStreamInit := Disp as IPersistStreamInit;
    Stream := TStreamAdapter.Create(LStream, soReference);
    LPersistStreamInit.Save(Stream, True);
    Result := LStream.DataString;
  finally
    LStream.Free;
  end;
end;

This way, you don't need the explicit _Release() anymore:

  • the TWebBrowser.Document property still returns an IDispatch whose refcount has been erroneously incremented +2 instead of +1
  • the assignment to Disp WON'T increment the refcount +1
  • the cast+assignment to LPersistStreamInit increments the refcount +1.

When the function exits:

  • an implicit _Release() when LPersistStreamInit goes out of scope decrements the refcount -1
  • an implicit _Release() when Disp goes out of scope decrements the refcount -1
  • an implicit _Release() on the Document property's return value decrements the refcount -1.

The refcount is balanced properly.

Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
  • Checked in 10.4.1 and there is an added assignment to nil for the temp variables in the two functions mentioned in the support ticket. – Brian Feb 10 '21 at 21:52
  • Yes, I just confirmed that the `nil` assignment was introduced in 10.0 Seattle. So this code should not be leaking in 10.3 – Remy Lebeau Feb 10 '21 at 22:18