How to reliably know (programmatically), when a page is loaded in Chromium
Asked Answered
P

3

6

I have a problem using the OnLoadEnd event of a TChromium (DCEF1).

I have a form with a TButton and a TChromium.

The OnClick event of the button calls a function which lists the forms of the loaded page. If I wait the page finish loading and then click the button, this function works OK; but if I call this function from the TChromium OnLoadEnd event handler the callback function is never called and thus, I get a empty list.

Button code (read the comments into the code):

procedure TForm2.Button3Click(Sender: TObject);
var
  Q: TWebChromium;
begin
  Q := TWebChromium.Create(Chromium1); // <- class to access DOM
  Q.WebFormNames; // <- method to get forms name
  ShowMessage(Q.Forms.Text); // <- show forms
end;

OnLoadEnd code:

procedure TForm2.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser;
  const frame: ICefFrame; httpStatusCode: Integer; out Result: Boolean);
begin
  if (browser <> nil) and (browser.GetWindowHandle = TChromium(Sender).BrowserHandle) and ((frame = nil) or (frame.IsMain)) then
  begin
    Button3Click(nil);
  end;
end;

Method code to obtain forms name (read the comments into the code):

procedure TWebChromium.WebFormNames;
var
  Finish: Boolean;
  EndTime: TTime;
begin
  FForms.Clear; // <- property (TStringList)
  if not Assigned(FWebBrowser) then // <- FWebBrowser: property that contain the TChromium
    raise Exception.Create('WebBrowser not assigned');
  if not (FWebBrowser is TChromium) then 
    raise Exception.Create('The WebBrowser property is not a TChromium.');

  Finish := False;
  TChromium(FWebBrowser).Browser.MainFrame.VisitDomProc(
        procedure (const doc: ICefDomDocument) // <- this procedure is not called if this method is called from OnLoadEnd event
        begin
          FForms.CommaText := GetFormsName(doc.Body); 
          Finish := True;
        end
  );
  EndTime := IncSecond(Time, 4);

  repeat Application.ProcessMessages until Finish or (Time > EndTime);
  if Time > EndTime then
    raise Exception.Create('Time out');
end;
Pantagruel answered 14/12, 2012 at 8:48 Comment(15)
Did you tried to wait a bit, for example launching the function call with a timer? Unrelated, but IMHO the method name should be named GetWebForms or EnumerateWebForms, and not just WebForms, and you may be leaking memory in the OnClick handler, since you don't destroy the created object.Apiece
I had the same issue in my project, the workaround was adding a TTimer, in the OnTimer event I check if all "required" elements are loaded, and then I trigger the method that will continue the processing.Toney
@jachguate: I will try with a TTimer, thanks. About the others questions, yes, it's possible that GetWebForms is more appropriate. For the leak memory, don't worry, this is only a app test ;-)Pantagruel
Which version of Chromium wrapper are you using ? The old one or DCEF3 ?Earwitness
BTW, the linked reference states the methods will be called on the UI thread, so I don't get the idea about the timeout attempt. In fact, now it appears to raise the exception if the VisitDomProc lasts for more than 4 seconds, but 1. doesn't prevent it to last more and 2. the FForms is still holding the results. (In fact both remains true in the case of a multi-threaded call, just preventing the main thread to wait more than 4 seconds and hoping the local time doesn't change in between).Apiece
@Earwitness I use DCEF1. I don't use DCEF3 because this version is not compatible with FMX and I need a browser compatible with this framework. For the second issue... do you advise me to create my own DOM visitor class (like this example)?Pantagruel
@TLama, I think you're getting it wrong. If you call any procedure inside the visitor proc, IMHO you're doing it right, since the DOM snapshot will remain valid until the method call ends. I don't think the documentation really says you have to put any logic in a single procedure... I can't imagine someone directing you to potentially write thousands of lines in a single procedure, nor I find a technical reason to do so. My reading is you're free to pass any reference to your calling procedures, unless you don't keep the references alive after the method call returns.Apiece
@jachguate, you're right, I got it wrong! I should take a break for a while :-)Earwitness
@jachguate, cadetill, I've rather deleted my comments about that DOM visitor proc implementation to not mislead anyone else. Could you delete your reactions, please ? cadetill, what version exactly are you using ? I know that e.g. this code I've tested and worked (when I've been posting that answer, I've been using version before trunk from that post date). Actually still works with a snapshot I have from the past. In current trunk DOM visiting doesn't seem to work at all.Earwitness
@TLama, I have tried to do your solution and work fine. Now I need to see how to adapt it to my necessities. ThanksPantagruel
You mean how to wait for the DOM visitor to finish, to make it synchronous in a way ?Earwitness
@Pantagruel any luck using a timer?Apiece
Cadetill, please don't a use timer. It's absolutely unreliable. @jachguate, the problem is how to make the asynchronous DOM visitor callback synchronous. There's no problem with DOM visiting of the document from the OnLoadEnd event. The document is ready to be visited.Earwitness
@Earwitness Maybe I'm lost. I read Interface to implement for visiting the DOM. The methods of this class will be called on the UI thread. in the documentation, are you sure the callback is called asynchronously? Doesn't a TThread.Synchronize call make it Synchronous again?Apiece
@TLama, jachguate sorry for late reply, but I've been very busy with my work and this is only a hobby (is for my components, the GMLib). No, I will not use a TTimer, I will use the TLama solution but I need to see how implement it with the GMLib. Thanks ;-)Pantagruel
A
1

Well, i know that this question was asked years ago, but i'm pretty new to TChromium. Here's my solution raised from previous proposals. In general, TChromium sends Event OnLoadEnd, but does so before loading, for example, JS. So I solve the problem so that I wait for some time in the OnLoadEnd procedure in case any scripts still load, and then send a notification, like this

procedure TForm1.OnLoadEndCust(Sender: TObject; const browser: ICefBrowser;
  const frame: ICefFrame; httpStatusCode: Integer);
var EndTime: TTime;
begin
  EndTime := IncSecond(Now, 2);

  repeat Application.ProcessMessages until (Now > EndTime);

end;

So far - that's enough, but is there any other, better or more ellegant solution?

And why we should use VisitDomProc in code above? When we are calling it from OnLoadEnd it seems to be unnecessary. What d'you think guys?

Adrianeadrianna answered 11/4, 2017 at 11:35 Comment(0)
B
0

I have used both DCEF 1 and DCEF 3, if you can switch to 3 you should, lots of improvements.

Here is the link to DCEF 3: https://code.google.com/p/dcef3/

The method you have should definately work:

procedure TMainForm.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser;
  const frame: ICefFrame; httpStatusCode: Integer);
begin
  if IsMain(browser, frame) then begin
    // main window done from here on in
  end;
end;

If it does not it can be 1 of 2 things,

  1. Your form data got messed up and somehow the event is no longer attached, doublecheck the event properties in your component.

  2. Something is wrong with your build, try to run GUIClient in the samples directory, it has the same event and should get triggered, if it does not try to find another build (preferably the DCEF3 version I linked to before)

Good luck

Basinger answered 6/5, 2014 at 9:37 Comment(1)
Didn't see this was asked an eon ago :/Basinger
C
0

I had the same issue and I noticed the page is fully loaded when httpStatusCode is 200. I implemented a boolean variable for my convenience.

  TForm2 = class(TForm)
    procedure Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser;
      const frame: ICefFrame; httpStatusCode: Integer);
    procedure Chromium1LoadStart(Sender: TObject; const browser: ICefBrowser;
  private
    { Private declarations }
    IsFullyDisplayed : Boolean;
  end;

procedure TForm2.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser;
  const frame: ICefFrame; httpStatusCode: Integer);
begin
  if frame = nil then
    exit;

  if httpStatusCode=200 then
  begin
    IsFullyDisplayed:=True;
  end;
end;

procedure TForm2.Chromium1LoadStart(Sender: TObject; const browser: ICefBrowser;
  const frame: ICefFrame);
begin
  IsFullyDisplayed := False;
end;
Costly answered 22/10, 2017 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.