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

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: Sat Mar 20, 2021 2:06 pm I'll move some of the TLazChromium code to TChromiumCore as soon as I have time. :oops:
If I understand, this is about handling "CloseBrowser" calls, before the "OnBrowserCreated" event?
(if not, ignore the below)

Any idea how to do that in MultiBrowser mode?
There may be more than one browser, that is in creation. How to know which one to close?

It would be possible to have a field in TBrowserInfo to indicate that it is a dummy entry for a browser in creation.
Then that could be added, right before the call to create the browser.

But with more that one browser in creation, there is no way to say in which order they receive their call to TChromiumCore.doOnAfterCreated.
(i.e. who knows, an OSR browser may be created faster than a none OSR? If not now, then maybe in future).
So how would TChromiumCore.doOnAfterCreated know which "in creation TBrowserInfo" to update?

Browser in creation do not have an id, but with an "in creation TBrowserInfo" they would be accessible via BrowserByIndex.
That means the code calling CreateBrowser can store BrowserCount as the new index.
But that index only stays valid, if TChromiumCore.doOnAfterCreated can find the correct entry.

=======
Without the access by index, the user can only select fully created browsers. (Because the current BrowserId will always be set to one of them).
So the user can only call "CloseBrowser" for a "browser in creation" if there are no (or no more) fully created browsers.

So having access by index, would be nice.

Any ideas?
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

Suggested updates to https://www.briskbard.com/index.php?lang=en&pageid=cef


"Compatibility"
CEF4Delphi can be used in VCL, FMX and LCL Windows applications using Delphi or Lazarus/FPC. CEF4Delpi can also be used in Linux applications with Lazarus/FPC.
change to
CEF4Delphi can be used in VCL, FMX and LCL Windows applications using Delphi or Lazarus/FPC. CEF4Delpi can also be used in Linux and MacOS applications with Lazarus/FPC. Under MacOS only none-OSR Browsers are fully supported
==================================

Not related to the MacOS support, but may I suggest in the "Installation" section to at least refer to the "CEF binaries"?
Maybe something like
You also need to download the CEF binaries from ... See the "Usage" section on where to copy the downloaded files.
Or maybe move part of the CEF binary download/copy/install into the "Installation" section?
Just an idea.

Or the CEF download can be an extra section between "Installation" and "Usage"

==================================
"Usage"
Once you've downloaded and decompressed the Standard Distribution package for the operating system of your choice, you must copy the contents of the Release and Resources directories to the directory of your application's executable. Here's an screenshot of the final layout in Windows :
The last sentence "Here's an screenshot of the final layout in Windows" should probably be in a new paragraph. So that there will be 3 paragraphs, one for each OS.

List of Linux files / Probably not needed to list all the locales.
On Linux the layout should look like:

Code: Select all

YourApp
cef_100_percent.pak
cef_200_percent.pak
cef_extensions.pak
cef.pak
chrome-sandbox
devtools_resources.pak
icudtl.dat
libcef.so
libEGL.so
libGLESv2.so
locales
  am.pak
  ar.pak
  bg.pak
  bn.pak
  ca.pak
  cs.pak
  da.pak
  de.pak
  el.pak
  en-GB.pak
  en-US.pak
  es-419.pak
  es.pak
  et.pak
  fa.pak
  fil.pak
  fi.pak
  fr.pak
  gu.pak
  he.pak
  hi.pak
  hr.pak
  hu.pak
  id.pak
  it.pak
  ja.pak
  kn.pak
  ko.pak
  lt.pak
  lv.pak
  ml.pak
  mr.pak
  ms.pak
  nb.pak
  nl.pak
  pl.pak
  pt-BR.pak
  pt-PT.pak
  ro.pak
  ru.pak
  sk.pak
  sl.pak
  sr.pak
  sv.pak
  sw.pak
  ta.pak
  te.pak
  th.pak
  tr.pak
  uk.pak
  vi.pak
  zh-CN.pak
  zh-TW.pak
snapshot_blob.bin
swiftshader
  libEGL.so
  libGLESv2.so
v8_context_snapshot.bin
On MacOs the layout should look like:

Code: Select all

YourApp
YourApp.app
  Contents
    FrameWorks
      Chromium Embedded Framework.framework
        Chromium Embedded Framework
        Libraries
          *.dylib
          vk_swiftshader.icd.json
        Resources
          Info.plist
          *.lproj
          *.bin
          *.dat
          *.pak
    YourApp Helper (GPU).app/*
    YourApp Helper (Plugin).app/*
    YourApp Helper (Rendere).app/*
    YourApp Helper.app/*
    Info.plist
    MacOS
       YourApp
    PkgInfo
    Resources
The YourApp Helper folders are explained under "Usage".

Note that the "YourApp" outside the YourApp.app folder is only for Lazarus. It would normally be in YourApp.app/Contents/MacOS/YourApp, but lazarus uses a symbolic link in the MacOS folder.

==================================
"Usage"
(this is at the begin of "Usage" / further up than the last part)
Since TApplication must only be initialized and run in the main process, it's necessary to create GlobalCEFApp and call GlobalCEFApp.StartMainProcess to detect if that is the main process.
should be
Since TApplication must only be initialized and run in the main process, it's necessary to create GlobalCEFApp and call GlobalCEFApp.StartMainProcess to detect if that is the main process.
On MacOS the subprocess must be a separate executable (YourApp Helper). Therefore there is no need to detect, if the process is the main process. The helper executables must be installed according to the layout in the "Installation" section. All 4 helpers can be copies of the same executable. The helper application should look like

Code: Select all

begin
  GlobalCEFApp                  := TCefApplicationCore.Create;
  GlobalCEFApp.InitLibLocationFromArgs;
  GlobalCEFApp.StartSubProcess;
  GlobalCEFApp.Free;
end.
[/color]
User avatar
salvadordf
Posts: 4040
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 Mar 20, 2021 6:33 pm Just an observation... It works, but....

Setting

Code: Select all

  MultiThreadedMessageLoop  := False;
  MultiBrowserMode := true;
Means that AddBrowser is called twice for the same browser.
Anyway AddBrowser checks if the ID is already in the list. So the 2nd call does not do any harm.

But then why have code like

Code: Select all

procedure TChromiumCore.doOnAfterCreated(const browser: ICefBrowser);
begin
  if MultithreadApp or MultiBrowserMode then
    AddBrowser(browser);
Why not always call AddBrowser?
In case of "MultiThreadedMessageLoop := False;" the following is run.

Code: Select all

function TChromiumCore.CreateBrowserHostSync(      ....
begin
  TempURL     := CefString(aURL);
  TempBrowser := TCefBrowserRef.UnWrap(cef_browser_host_create_browser_sync(aWindowInfo, FHandler.Wrap, @TempURL, aSettings, CefGetData(aExtraInfo), CefGetData(aContext)));
  Result      := AddBrowser(TempBrowser);
Which adds the browser from the Main-Thread of the App (because in single thread mode that is the "browser process UI thread").

However, the only benefit (that I can think of) is, that by the time "CreateBrowser" returns, the browser has definitely been added.
Without this, or in Multi-Thread mode the browser is only added when the OnBrowserCreated event (in the UI thread) was executed. And that can be at a later time.

However the downside is, OnBrowserCreated can be called before the "CreateBrowser" call returns. In that case, any code running in the event does not see the browser as being added (and Chromium.Initialized may return false).

If "doOnAfterCreated" would call "AddBrowser" unconditionally, the browser would always be added at the earliest time possible (which ever comes first).

In my tests, in single-thread mode, on Windows, the event is actually called before the cef_browser_host_create_browser_sync call returns.
But that may not be guaranteed.
The AddBrowser call in the TChromiumCore.doOnAfterCreated procedure is also called for new popup windows and new tabs that are created when the user clicks on a link.

CEF has reference counting for its structures :
https://bitbucket.org/chromiumembedded/cef/wiki/UsingTheCAPI.md

If a function return a pointer to an structure then we have to decrease its reference count. In this situation it's just easier to create a TCefBrowserRef and let the compiler decrease its counter when its released.

We are going to to have a TCefBrowserRef in any case so we add it to the browser list at this point. In some situations it will be redundant but it will never be added twice.
martin_fr wrote: Sun Mar 21, 2021 2:13 pm
salvadordf wrote: Sat Mar 20, 2021 2:06 pm I'll move some of the TLazChromium code to TChromiumCore as soon as I have time. :oops:
If I understand, this is about handling "CloseBrowser" calls, before the "OnBrowserCreated" event?
(if not, ignore the below)

Any idea how to do that in MultiBrowser mode?
There may be more than one browser, that is in creation. How to know which one to close?

It would be possible to have a field in TBrowserInfo to indicate that it is a dummy entry for a browser in creation.
Then that could be added, right before the call to create the browser.

But with more that one browser in creation, there is no way to say in which order they receive their call to TChromiumCore.doOnAfterCreated.
(i.e. who knows, an OSR browser may be created faster than a none OSR? If not now, then maybe in future).
So how would TChromiumCore.doOnAfterCreated know which "in creation TBrowserInfo" to update?

Browser in creation do not have an id, but with an "in creation TBrowserInfo" they would be accessible via BrowserByIndex.
That means the code calling CreateBrowser can store BrowserCount as the new index.
But that index only stays valid, if TChromiumCore.doOnAfterCreated can find the correct entry.

=======
Without the access by index, the user can only select fully created browsers. (Because the current BrowserId will always be set to one of them).
So the user can only call "CloseBrowser" for a "browser in creation" if there are no (or no more) fully created browsers.

So having access by index, would be nice.

Any ideas?
I'll try to answer all this as soon as I can. :oops:
martin_fr wrote: Sun Mar 21, 2021 3:13 pm Suggested updates to https://www.briskbard.com/index.php?lang=en&pageid=cef


"Compatibility"
CEF4Delphi can be used in VCL, FMX and LCL Windows applications using Delphi or Lazarus/FPC. CEF4Delpi can also be used in Linux applications with Lazarus/FPC.
change to
CEF4Delphi can be used in VCL, FMX and LCL Windows applications using Delphi or Lazarus/FPC. CEF4Delpi can also be used in Linux and MacOS applications with Lazarus/FPC. Under MacOS only none-OSR Browsers are fully supported
==================================

Not related to the MacOS support, but may I suggest in the "Installation" section to at least refer to the "CEF binaries"?
Maybe something like
You also need to download the CEF binaries from ... See the "Usage" section on where to copy the downloaded files.
Or maybe move part of the CEF binary download/copy/install into the "Installation" section?
Just an idea.

Or the CEF download can be an extra section between "Installation" and "Usage"

==================================
"Usage"
Once you've downloaded and decompressed the Standard Distribution package for the operating system of your choice, you must copy the contents of the Release and Resources directories to the directory of your application's executable. Here's an screenshot of the final layout in Windows :
The last sentence "Here's an screenshot of the final layout in Windows" should probably be in a new paragraph. So that there will be 3 paragraphs, one for each OS.

List of Linux files / Probably not needed to list all the locales.
On Linux the layout should look like:

Code: Select all

YourApp
cef_100_percent.pak
cef_200_percent.pak
cef_extensions.pak
cef.pak
chrome-sandbox
devtools_resources.pak
icudtl.dat
libcef.so
libEGL.so
libGLESv2.so
locales
  am.pak
  ar.pak
  bg.pak
  bn.pak
  ca.pak
  cs.pak
  da.pak
  de.pak
  el.pak
  en-GB.pak
  en-US.pak
  es-419.pak
  es.pak
  et.pak
  fa.pak
  fil.pak
  fi.pak
  fr.pak
  gu.pak
  he.pak
  hi.pak
  hr.pak
  hu.pak
  id.pak
  it.pak
  ja.pak
  kn.pak
  ko.pak
  lt.pak
  lv.pak
  ml.pak
  mr.pak
  ms.pak
  nb.pak
  nl.pak
  pl.pak
  pt-BR.pak
  pt-PT.pak
  ro.pak
  ru.pak
  sk.pak
  sl.pak
  sr.pak
  sv.pak
  sw.pak
  ta.pak
  te.pak
  th.pak
  tr.pak
  uk.pak
  vi.pak
  zh-CN.pak
  zh-TW.pak
snapshot_blob.bin
swiftshader
  libEGL.so
  libGLESv2.so
v8_context_snapshot.bin
On MacOs the layout should look like:

Code: Select all

YourApp
YourApp.app
  Contents
    FrameWorks
      Chromium Embedded Framework.framework
        Chromium Embedded Framework
        Libraries
          *.dylib
          vk_swiftshader.icd.json
        Resources
          Info.plist
          *.lproj
          *.bin
          *.dat
          *.pak
    YourApp Helper (GPU).app/*
    YourApp Helper (Plugin).app/*
    YourApp Helper (Rendere).app/*
    YourApp Helper.app/*
    Info.plist
    MacOS
       YourApp
    PkgInfo
    Resources
The YourApp Helper folders are explained under "Usage".

Note that the "YourApp" outside the YourApp.app folder is only for Lazarus. It would normally be in YourApp.app/Contents/MacOS/YourApp, but lazarus uses a symbolic link in the MacOS folder.

==================================
"Usage"
(this is at the begin of "Usage" / further up than the last part)
Since TApplication must only be initialized and run in the main process, it's necessary to create GlobalCEFApp and call GlobalCEFApp.StartMainProcess to detect if that is the main process.
should be
Since TApplication must only be initialized and run in the main process, it's necessary to create GlobalCEFApp and call GlobalCEFApp.StartMainProcess to detect if that is the main process.
On MacOS the subprocess must be a separate executable (YourApp Helper). Therefore there is no need to detect, if the process is the main process. The helper executables must be installed according to the layout in the "Installation" section. All 4 helpers can be copies of the same executable. The helper application should look like

Code: Select all

begin
  GlobalCEFApp                  := TCefApplicationCore.Create;
  GlobalCEFApp.InitLibLocationFromArgs;
  GlobalCEFApp.StartSubProcess;
  GlobalCEFApp.Free;
end.
[/color]
Thank you!!! Updating that page was on my to-do list since you started adding MacOS support :D
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: Mon Mar 22, 2021 10:01 am
martin_fr wrote: Sat Mar 20, 2021 6:33 pm Just an observation... It works, but....
martin_fr wrote: Sat Mar 20, 2021 6:33 pm However the downside is, OnBrowserCreated can be called before the "CreateBrowser" call returns. In that case, any code running in the event does not see the browser as being added (and Chromium.Initialized may return false).

If "doOnAfterCreated" would call "AddBrowser" unconditionally, the browser would always be added at the earliest time possible (which ever comes first).
CEF has reference counting for its structures :
https://bitbucket.org/chromiumembedded/cef/wiki/UsingTheCAPI.md

We are going to to have a TCefBrowserRef in any case so we add it to the browser list at this point. In some situations it will be redundant but it will never be added twice.
As I said, it currently works.
I am aware of the ref counting. As far as I can see this is not related.

This is merely a timing question.
If user code is run in the OnBrowserCreated event. Or if it is run in a PostMessage/QueueAsyncCall triggered in OnBrowserCreated.
Then this user code could potentially run, before "AddBrowser" was called and would not be able to access the browser.

So as you said
salvadordf wrote: Mon Mar 22, 2021 10:01 am We are going to to have a TCefBrowserRef in any case so we add it to the browser list at this point. In some situations it will be redundant but it will never be added twice.
Only: It is not always added in "doOnAfterCreated".

I think

Code: Select all

procedure TChromiumCore.doOnAfterCreated(const browser: ICefBrowser);
begin
  if MultithreadApp or MultiBrowserMode then
    AddBrowser(browser);
Should instead be

Code: Select all

procedure TChromiumCore.doOnAfterCreated(const browser: ICefBrowser);
begin
    AddBrowser(browser);
And the "AddBrowser in

Code: Select all

function TChromiumCore.CreateBrowserHostSync(      aWindowInfo : PCefWindowInfo; ........
begin
  TempURL     := CefString(aURL);
  TempBrowser := TCefBrowserRef.UnWrap(cef_browser_host_create_browser_sync(aWindowInfo, FHandler.Wrap, @TempURL, aSettings, CefGetData(aExtraInfo), CefGetData(aContext)));
  Result      := AddBrowser(TempBrowser);
end;
would remain as it is.

Whichever one happens to be first at runtime will do the work.

It's not a big issue. I just think, it may make some user code easier.
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

Ok, I just realized, there is a problem.

But: It already exists.

If,
MultithreadApp = False
and
MultiBrowserMode = True

Then currently this can happen

Code: Select all

User calls CreateBrowser
  => CreateBrowserHostSync
    => doOnAfterCreated
      => AddBrowser
      => FOnAfterCreated()
         => User calls CloseBrower
             // Here it depends on the timing of CEF
  AddBrowser (in CreateBrowserHostSync)
If the timing of CEF is such, that CloseBrowser finishes (including callbacks) before the FOnAfterCreated event returns, then the browser gets removed correctly from TChromium. But it then gets added again.

I have not tested that, but if such timing can happen, then the AddBrowser in CreateBrowserHostSync is probably not save.
Because FOnAfterCreated gets the browser as argument and can call "Browser.Host.CloseBrowser"
(I also don't know if CEF supports closing a browser during this event / TThread.Syncronize can be used to make threads wait)
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

martin_fr wrote: Mon Mar 22, 2021 6:48 pm Ok, I just realized, there is a problem.
Well, we can ignore that scenario. I just tested it. And CEF itself will crash, if you close the browser, while in the OnBrowserCreate event. At least in single threaded mode.

If I found the correct source, at least in cef_browser_host_create_browser_sync the "browser" object is accessed by CEF, after the call to OnAfterCreated. So it can not be closed at that time.

Cef4Delphi can not entirely protect from that, as the user can always call directly Browser.Host.CloseBrowser, bypassing any checks in Cef4Delphi.

As for Chromium.CloseBrowser:
If the current "if" condition at the top of doBrowserCreated is meant as any sort of protection, then "MultiBrowserMode" should not lead to the AddBrowser call, as that allows Chromium.CloseBrowser, which crashes.

======================

With MultiThreade event loop, I did not get a crash.
I do not know, if that is good luck, or design.
User avatar
salvadordf
Posts: 4040
Joined: Thu Feb 02, 2017 12:24 pm
Location: Spain
Contact:

Re: Cef4D on Mac with Fpc - Help needed

Post by salvadordf »

Sorry for the long delay. :oops:

I'm not sure if there's a confusion with the TChromiumCore.MultiBrowserMode property so I'll try to explain in more detail.

That property allows you to use one TChromiumCore component for several browsers. I only conceived it as an easy way to let CEF create new popup windows automatically as shown in the MiniBrowser demo.

Usually you would have to copy the code in the PopupBrowser2 demo to your application to handle the new popup windows using custom Lazarus/Delphi forms but as you can see in the demo, it can be complicated when GlobalCEFApp use the MultiThreadedMessageLoop mode.

If an application blocks new popup windows and new tabs, then it shouldn't use TChromiumCore.MultiBrowserMode.

Let's say that an application creates several browser tabs with a TChromium control for each tab like the TabbedBrowser2 demo.
In that situation it's recommended to use custom Lazarus/Delphi forms to handle popup windows because it can be tricky or undesirable to close the popup windows when the user closes the tab that created them.


I'll try to reply to your other messages as soon as I can.
martin_fr
Posts: 30
Joined: Wed Jan 27, 2021 5:56 pm

Re: Cef4D on Mac with Fpc - Help needed

Post by martin_fr »

No problem taking time.
I reached my main goal, adding support for Cocoa. So all else at your convenience.

All else are just observations I made on top. And that I believe might be useful, but you might have your opinion on that.

Anyway this thread has grown into to many different issues, so I extracted them into a new thread: https://www.briskbard.com/forum/viewtopic.php?f=8&t=1733

Sorry for the long delay.
IMHO you have been exceptionally fast with your responses.
I have seldom had responses, and inclusion of contribution going even anywhere near as fast.
In fact, in my experience response times to contributions are often in weeks, sometimes in months.
Post Reply