Disclosure Statement: This site contains affiliate links, which means that I may receive a commission if you make a purchase using these links. As an eBay Partner, I earn from qualifying purchases.

Cef4D on Mac with Fpc - Help needed

User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Cef4D on Mac with Fpc - Help needed

Post by salvadordf »

martin_fr wrote: Sat Feb 20, 2021 8:00 pm
salvadordf wrote: Thu Feb 18, 2021 5:53 pm Even when you don't use a "sandbox", Chromium will execute a function related to that feature that will cause problems when it detects more than one thread. That's why we have to call gtk_init after the CEF initialization for the main browser process only :
https://bitbucket.org/chromiumembedded/cef/src/8f5fdc1f9adb0aa81f9db4c3f0d74d08d554ccc5/tests/cefclient/cefclient_gtk.cc#lines-113
But the current example in demos/Lazarus_Linux/TinyBrowser2 does not follow that example.

It uses the normal "Interfaces" unit.

CreateWidgetSet is called during initialization of that unit. And that is before the main body of the .lpr file gets executed. So before the CefApplication is created and run.
You're right! :shock:

That was the first demo that worked in Linux and I didn't notice it contradicts the CEF code comments.

TinyBrowser2 is equivalent to the official cefsimple demo and it's the only one using the single thread mode without an external message pump. Perhaps calling cef_run_message_loop and the settings I mentioned before are the cause for this exception.

The Interfaces unit is not necessary in that demo so I just removed it in the last update.
martin_fr wrote: Sun Feb 21, 2021 2:49 am You did place
function TChromiumWindow.GetChildWindowHandle : THandle;

in an IFDEF WINDOWS

But that gets the handle from the TChronium, and works on all OS.
It is also all-OS on LinkedParentWindow

In the implementation (for fpc) it needs LclType.THandle as result type.
Because sysutils redefines THandle.

Or move "SysUtils" from the implementation to the interface, but put it *first* in the uses list.
Thanks!

I just uploaded the fix for those issues and a few other fixes : focus on Linux, string conversions, etc.
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

Another question:

In many Lazarus demos

Code: Select all

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Timer1.Enabled := False;
  if not(Chromium1.CreateBrowser(CEFLinkedWindowParent1.Handle, CEFLinkedWindowParent1.BoundsRect)) and
     not(Chromium1.Initialized) then
    Timer1.Enabled := True;
end;
I checked one of the Delphi demos and it does not have the "initialized" check.

My question is what happens if "CreateBrowser" returns true, but it takes time until initialized will be set?

Then "CreateBrowser" will be called again? Creating a 2nd browser?
User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Cef4D on Mac with Fpc - Help needed

Post by salvadordf »

Using timers to create the browser is a quick and simple way to solve the initialization race but it's not the most elegant way to solve that issue.

CEF is initialized asynchronously while the application initializes and creates the main form. The problem is that you can never be sure that CEF will be fully initialized by the time the form is shown for the first time. In fact, some users reported initialization problems when we didn't check the CEF initialization status before creating the first browser.

A better way would be to use the solution that we have in the TabbedBrowser2. That demo uses the GlobalCEFApp.OnContextInitialized event to send a custom CEF_INITIALIZED message to the main form to create the browser. The main form also tries to create the browser in the TForm.OnShow event and we avoid using a timer.

Perhaps the best solution would be to use a TEvent or some other synchronization object but that would complicate the demos needlessly.

The "Chromium1.Initialized" check in that event is not that important. You can remove it if you're sure that timer is only used to create the browser.

I just merged your code with some minor changes. Please, update your repo too.

I tried to open the AppHelper project from Linux and Windows with the latest stable Lazarus version but it has no units. Are you using a beta version of Lazarus?

BTW, this is the fpCEF3 issue that relates how they added MacOS support to that project. It may have important information about the subprocesses and CefAppProtocol :
https://github.com/dliw/fpCEF3/issues/12
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

salvadordf wrote: Wed Feb 24, 2021 11:21 am Using timers to create the browser is a quick and simple way to solve the initialization race but it's not the most elegant way to solve that issue.

CEF is initialized asynchronously while the application initializes and creates the main form. The problem is that you can never be sure that CEF will be fully initialized by the time the form is shown for the first time. In fact, some users reported initialization problems when we didn't check the CEF initialization status before creating the first browser.

A better way would be to use the solution that we have in the TabbedBrowser2. That demo uses the GlobalCEFApp.OnContextInitialized event to send a custom CEF_INITIALIZED message to the main form to create the browser. The main form also tries to create the browser in the TForm.OnShow event and we avoid using a timer.
Ok, that is good to know, and explains the need to repeat, if CreateBrowser returns false.
salvadordf wrote: Wed Feb 24, 2021 11:21 am The "Chromium1.Initialized" check in that event is not that important. You can remove it if you're sure that timer is only used to create the browser.
The "Chromium1.Initialized" actually checks if the Browser exists. Which can depend on the value of MultiThreadedMessageLoop.

With only one thread IIRC/afaik it is created synced, and must exist if the CreateBrowser succeeded. (But will not yet exist on the BrowserCreated event).
But if it is created in another thread then there may be a delay. And then IMHO its a race condition to create a 2nd browser.

*** EDIT / AMEND ***
Don't recall the code around that example, but if calls an event / post a message, so the app can start accessing the browser (e.g. LoadUrl), then yes: It needs to wait for Initialized. But it needs to keep state. Call CreateBrowser only once (or only until it once returned true), and after that wait for Initialized.
<<<< EDIT
salvadordf wrote: Wed Feb 24, 2021 11:21 am I tried to open the AppHelper project from Linux and Windows with the latest stable Lazarus version but it has no units. Are you using a beta version of Lazarus?
Ah, yes. I am on svn trunk [1]. And that has a new format for lpi files. It should still compile, and you should be able to open the "project source".
But I will see to get that fixed.

[1] Being part of the Lazarus developer team, means I need trunk to work on....
salvadordf wrote: Wed Feb 24, 2021 11:21 am BTW, this is the fpCEF3 issue that relates how they added MacOS support to that project. It may have important information about the subprocesses and CefAppProtocol :
https://github.com/dliw/fpCEF3/issues/12
Thanks, I just made a pull request for a (what I believe to be) working version for Mac.
Jonas Maebe did help with the CrAppProtocol.
The Helper apps are there as well (see readme and scripts). The problem here is that even the CEF documentation https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-mac-os-x speaks only of ONE helper app, when in fact they changed it to 4 (all 4 are identical / though, if you have stuff to be executed in the GUI or renderer process (DOM,JS), you could add it to the one helper only)

I haven.t done any OSR tests, but with the main bit working...

And TinyBrowser2 can not be ported, because when you directly hand over to the cef eventloop, there is no NsApplication from the LCL, and no way to add the CrAppProtocol (or not that I have found). Anyway running with a normal app-loop seems the more important case.
Last edited by martin_fr on Mon Mar 01, 2021 1:27 am, edited 1 time in total.
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

I added the fixed AppHelper to the pull request.

Note that this AppHelper can also be used on Linux and Window as it is. Not needed for the small demos, but might be of interest for users with bigger main apps.
User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Cef4D on Mac with Fpc - Help needed

Post by salvadordf »

martin_fr wrote: Mon Mar 01, 2021 1:06 am
salvadordf wrote: Wed Feb 24, 2021 11:21 am Using timers to create the browser is a quick and simple way to solve the initialization race but it's not the most elegant way to solve that issue.

CEF is initialized asynchronously while the application initializes and creates the main form. The problem is that you can never be sure that CEF will be fully initialized by the time the form is shown for the first time. In fact, some users reported initialization problems when we didn't check the CEF initialization status before creating the first browser.

A better way would be to use the solution that we have in the TabbedBrowser2. That demo uses the GlobalCEFApp.OnContextInitialized event to send a custom CEF_INITIALIZED message to the main form to create the browser. The main form also tries to create the browser in the TForm.OnShow event and we avoid using a timer.
Ok, that is good to know, and explains the need to repeat, if CreateBrowser returns false.
salvadordf wrote: Wed Feb 24, 2021 11:21 am The "Chromium1.Initialized" check in that event is not that important. You can remove it if you're sure that timer is only used to create the browser.
The "Chromium1.Initialized" actually checks if the Browser exists. Which can depend on the value of MultiThreadedMessageLoop.

With only one thread IIRC/afaik it is created synced, and must exist if the CreateBrowser succeeded. (But will not yet exist on the BrowserCreated event).
But if it is created in another thread then there may be a delay. And then IMHO its a race condition to create a 2nd browser.

*** EDIT / AMEND ***
Don't recall the code around that example, but if calls an event / post a message, so the app can start accessing the browser (e.g. LoadUrl), then yes: It needs to wait for Initialized. But it needs to keep state. Call CreateBrowser only once (or only until it once returned true), and after that wait for Initialized.
<<<< EDIT
The complete sequence is this :
  • 1. GlobalCEFApp.StartMainProcess call
  • 2. GlobalCEFApp.OnContextInitialized event triggered and the GlobalCEFApp.GlobalContextInitialized property is set to true internally.
  • 3. Main form creation and activation. TChromium.CreateBrowser is called in TForm.OnShow or TForm.OnActivate depending on the platform. TChromium.CreateBrowser requires GlobalCEFApp.GlobalContextInitialized to be successful.
  • 4. TChromium.AfterCreation is triggered and the TChromium.Initialized property is set to true internally.
  • 5. Optionally : User interface activation and initial web page is loaded.
The demos using an external message pump will execute all those steps, in that order, in the main application thread synchronously. In this case GlobalCEFApp.OnContextInitialized is triggered inside the GlobalCEFApp.StartMainProcess call and TChromium.AfterCreation is triggered inside the TChromium.CreateBrowser call.

However, the demos that use the GlobalCEFApp.MultiThreadedMessageLoop set to true will execute (2) and (4) in a CEF thread asynchronously.
Sometimes this causes problems because we can't guarantee that the (2) and (3) steps will be executed in that order.
In this case GlobalCEFApp.StartMainProcess returns before triggering GlobalCEFApp.OnContextInitialized and TChromium.CreateBrowser returns before triggering the TChromium.AfterCreation event.
martin_fr wrote: Mon Mar 01, 2021 1:06 am
salvadordf wrote: Wed Feb 24, 2021 11:21 am I tried to open the AppHelper project from Linux and Windows with the latest stable Lazarus version but it has no units. Are you using a beta version of Lazarus?
Ah, yes. I am on svn trunk [1]. And that has a new format for lpi files. It should still compile, and you should be able to open the "project source".
But I will see to get that fixed.

[1] Being part of the Lazarus developer team, means I need trunk to work on....
Impressive work BTW !!! :D
I tested Lazarus 2.0.12 and it works great!
martin_fr wrote: Mon Mar 01, 2021 1:06 am
salvadordf wrote: Wed Feb 24, 2021 11:21 am BTW, this is the fpCEF3 issue that relates how they added MacOS support to that project. It may have important information about the subprocesses and CefAppProtocol :
https://github.com/dliw/fpCEF3/issues/12
Thanks, I just made a pull request for a (what I believe to be) working version for Mac.
Jonas Maebe did help with the CrAppProtocol.
The Helper apps are there as well (see readme and scripts). The problem here is that even the CEF documentation https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-mac-os-x speaks only of ONE helper app, when in fact they changed it to 4 (all 4 are identical / though, if you have stuff to be executed in the GUI or renderer process (DOM,JS), you could add it to the one helper only)

I haven.t done any OSR tests, but with the main bit working...

And TinyBrowser2 can not be ported, because when you directly hand over to the cef eventloop, there is no NsApplication from the LCL, and no way to add the CrAppProtocol (or not that I have found). Anyway running with a normal app-loop seems the more important case.
I asked a collegue to do a quick test in his mac and he told me that there was a build issue in the uceflazaruscocoa.pas unit with Lazarus 2.0.12

I'm not sure if he installed everything correctly. Could you test that?

Thank you so much for all this work!
User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Cef4D on Mac with Fpc - Help needed

Post by salvadordf »

I had to send the previous message quickly and leave. Let me add a few more comments.

First of all, sorry for the confusion about the CEF initialization. As you can see, it can be complicated but your ExternalPumpBrowser demo shouldn't be affected because it uses the external message pump and all events are executed in the main application thread.

The timer can be safely deleted and you should be able to call TChromium.CreateBrowser without needing it.
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

I got compilation fixed for fixes.

But it will not work, unless you merge a fix from svn trunk 63693
https://github.com/User4martin/lazarus/commit/cccd9460506e42d97fc726faba730ad1a65aa035.diff

The script to copy the AppHelper into the bundle will also modify the Info.Plist, adding a NSPrincipalClass. Without that it wont work.

----
For more testing there is https://github.com/User4martin/CEF4Delphi/tree/fpc-work-3
BrowserWindowEx has 2 browsers, that can be opened and closed by the user during runtime (and a modal window, which is a bit tricky on Mac)

That branch is work in progress, so it has a lot of debug output in it. Not yet for merging....
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

About init...

I have an idea there, but want your feedback. Especially, if you maybe want to adapt it for Delphi users (I don't have Delphi)

https://github.com/User4martin/CEF4Delphi/tree/fpc-for-discussion-context-init

1) On Linux the timer is still needed. Not sure what, but using the outer window is not possible immediately. (Either no browser is created, despite init context been done, or x windows crashes)

2) About the AddContextInitializedHandler
I know there is an OnContextInitialized event. But only one callback can be put in there. I found it quite often useful if more than one object can wait for an event.
In this case, I have the component that creates the Browser. If the user puts several of them on the form, each needs to listen.

3) The AddContextInitializedHandler will know if the event already happened. And then can call the Handler immediately.
So the app can be created, without needing to take care who wants to add Handlers.
- Anyone adding there handler before context is init, will wait.
- Anyone adding there handler after context is init, will get called back right away.
Works either way.

4) I used Application.ProcessMessages, as this does a good job in Lazarus.
If Delphi does not have it, it should have TThread.Queue. Does the same job in that case (but had issues in fpc 3.0.4 which is still used by some / though I did not test, if the cef code actually works for 3.0.4)
In either case, the event will be in the main thread. And any call related to TCefApplication.Create should have fully finished, because QueueAsync only runs when the app is back in the mainloop.

5) I do not know what Delphi has in terms of TMethodList, which is convenient to hold and call TNotifyEvents. And allows them to remove themself, while they are called.
----
If you want to adapt that for Delphi, then maybe it should be implemented in TCefApplication, instead of sub-classing it?
User avatar
salvadordf
Posts: 4016
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Cef4D on Mac with Fpc - Help needed

Post by salvadordf »

I'm busy these days and I can't study all this in detail.

Chromium requires that the GTK window is realized and its window handle is valid before creating a browser.
I tested all the Linux demos using the TForm.OnActivate event to create the browser and it seems to be triggered when all the requirements are met.
If that's not always the case then we would have to make some API calls to check those conditions.

I would not recommend using Application.ProcessMessages in Windows. I had many bad experiences with weird errors caused by that procedure including a CEF browser using an external message pump in Delphi.

I'll try to answer the rest in the coming days... :oops:
Post Reply