Page 1 of 2

Update with bug fixes #18 and #26

Posted: Mon Apr 24, 2017 11:03 am
by salvadordf
I just updated CEF4Delphi and the two pending bugs are now fixed.

Bug #18 - Random crashes using custom schemes : Removed the callback that caused the AV. It's not needed if you set the results inside that function.
I also added the possibility to register the factory in a command menu or in the DPR file.

Bug #26 - Dropdown control appearing in wrong place after form moved : Added the call to NotifyMoveOrResizeStarted in WM_MOVE and WM_MOVING (cefclient uses both events). Thanks to brianjford!!! :D

Re: Update with bug fixes #18 and #26

Posted: Tue Apr 25, 2017 2:23 pm
by salvadordf
Hi :

I'll test your project with MDI forms and I'll let you know the results.

Thanks!

Re: Update with bug fixes #18 and #26

Posted: Wed Apr 26, 2017 2:29 pm
by salvadordf
When you close a window that has a TChromium but your application is still running you have to follow the steps described at the CEF 3 sources.

Read the comments in this file /includes/capi/cef_browser_capi.h
Before the "cef_browser_host_t::close_browser" function

Code: Select all

  ///
  // Request that the browser close. The JavaScript 'onbeforeunload' event will
  // be fired. If |force_close| is false (0) the event handler, if any, will be
  // allowed to prompt the user and the user can optionally cancel the close. If
  // |force_close| is true (1) the prompt will not be displayed and the close
  // will proceed. Results in a call to cef_life_span_handler_t::do_close() if
  // the event handler allows the close or if |force_close| is true (1). See
  // cef_life_span_handler_t::do_close() documentation for additional usage
  // information.
  ///
Read the comments in this file /includes/capi/cef_life_span_handler_capi.h
Just before the "cef_life_span_handler_t::do_close" function

Code: Select all

  ///
  // Called when a browser has recieved a request to close. This may result
  // directly from a call to cef_browser_host_t::*close_browser() or indirectly
  // if the browser is parented to a top-level window created by CEF and the
  // user attempts to close that window (by clicking the 'X', for example). The
  // do_close() function will be called after the JavaScript 'onunload' event
  // has been fired.
  //
  // An application should handle top-level owner window close notifications by
  // calling cef_browser_host_t::try_close_browser() or
  // cef_browser_host_t::CloseBrowser(false (0)) instead of allowing the window
  // to close immediately (see the examples below). This gives CEF an
  // opportunity to process the 'onbeforeunload' event and optionally cancel the
  // close before do_close() is called.
  //
  // When windowed rendering is enabled CEF will internally create a window or
  // view to host the browser. In that case returning false (0) from do_close()
  // will send the standard close notification to the browser's top-level owner
  // window (e.g. WM_CLOSE on Windows, performClose: on OS X, "delete_event" on
  // Linux or cef_window_delegate_t::can_close() callback from Views). If the
  // browser's host window/view has already been destroyed (via view hierarchy
  // tear-down, for example) then do_close() will not be called for that browser
  // since is no longer possible to cancel the close.
  //
  // When windowed rendering is disabled returning false (0) from do_close()
  // will cause the browser object to be destroyed immediately.
  //
  // If the browser's top-level owner window requires a non-standard close
  // notification then send that notification from do_close() and return true
  // (1).
  //
  // The cef_life_span_handler_t::on_before_close() function will be called
  // after do_close() (if do_close() is called) and immediately before the
  // browser object is destroyed. The application should only exit after
  // on_before_close() has been called for all existing browsers.
  //
  // The below examples describe what should happen during window close when the
  // browser is parented to an application-provided top-level window.
  //
  // Example 1: Using cef_browser_host_t::try_close_browser(). This is
  // recommended for clients using standard close handling and windows created
  // on the browser process UI thread. 
  // 1.  User clicks the window close button which sends a close notification to
  //     the application's top-level window.
  // 2.  Application's top-level window receives the close notification and
  //     calls TryCloseBrowser() (which internally calls CloseBrowser(false)).
  //     TryCloseBrowser() returns false so the client cancels the window close.
  // 3.  JavaScript 'onbeforeunload' handler executes and shows the close
  //     confirmation dialog (which can be overridden via
  //     CefJSDialogHandler::OnBeforeUnloadDialog()).
  // 4.  User approves the close. 
  // 5.  JavaScript 'onunload' handler executes. 
  // 6.  CEF sends a close notification to the application's top-level window
  //     (because DoClose() returned false by default).
  // 7.  Application's top-level window receives the close notification and
  //     calls TryCloseBrowser(). TryCloseBrowser() returns true so the client
  //     allows the window close.
  // 8.  Application's top-level window is destroyed. 9.  Application's
  // on_before_close() handler is called and the browser object
  //     is destroyed.
  // 10. Application exits by calling cef_quit_message_loop() if no other
  // browsers
  //     exist.
  //
  // Example 2: Using cef_browser_host_t::CloseBrowser(false (0)) and
  // implementing the do_close() callback. This is recommended for clients using
  // non-standard close handling or windows that were not created on the browser
  // process UI thread. 
  // 1.  User clicks the window close button which sends a close notification to
  //     the application's top-level window.
  // 2.  Application's top-level window receives the close notification and:
  //     A. Calls CefBrowserHost::CloseBrowser(false).
  //     B. Cancels the window close.
  // 3.  JavaScript 'onbeforeunload' handler executes and shows the close
  //     confirmation dialog (which can be overridden via
  //     CefJSDialogHandler::OnBeforeUnloadDialog()).
  // 4.  User approves the close. 
  // 5.  JavaScript 'onunload' handler executes. 
  // 6.  Application's do_close() handler is called. Application will:
  //     A. Set a flag to indicate that the next close attempt will be allowed.
  //     B. Return false.
  // 7.  CEF sends an close notification to the application's top-level window.
  // 8.  Application's top-level window receives the close notification and
  //     allows the window to close based on the flag from #6B.
  // 9.  Application's top-level window is destroyed. 10. Application's
  // on_before_close() handler is called and the browser object is destroyed.
  // 11. Application exits by calling cef_quit_message_loop() if no other
  // browsers exist.
  ///
In CEF4Delphi you should call TChromium.CloseBrowser(False) and then wait for the TChormium.OnClose event.

If I understand the comments correctly, we should wait for the TChormium.OnBeforeClose event that comes after TChormium.OnClose but TChormium.OnBeforeClose is not always triggered.

Even when you follow the instructions you can get an access violation after the TChormium.OnBeforeClose event, specially in web pages with lots of Flash.

Some people in the offical CEF3 forum with the same problem just added a small timer after the TChormium.OnClose. When the time is up, you can destroy the window by sending a WM_CLOSE.

Re: Update with bug fixes #18 and #26

Posted: Fri Apr 28, 2017 2:02 pm
by hvassbotn
We're having problems to get a stable implementation of this.

The CEF code comments are not crystal clear and it remains to be determined what the correct / best solution for this is.

Our application has two modes:
* MDI mode, with standard MDI child windows inside a main form MDI frame window
* Floating mode, with each child window "floating" on the desktop

The CEF browser window is implemented as a child window, along with other non-browser child windows.

We first tried using OnCloseQuery in the TCEFBrowserForm window. That (more or less) works in the floating mode, but in MDI mode, if you try to close the main window, the CanClose := False in the CEF window code, like this:

Code: Select all

procedure TCEFBrowserForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  inherited;
  if CanClose and not FChromiumClosed then
  begin
    FChromiumClosed := True;
    Chromium.CloseBrowser(False);
    CanClose := False;
  end
  else
    CanClose := CanClose and FChromiumClosed;
end;

procedure TCEFBrowserForm.ChromiumClose(Sender: TObject; const browser: ICefBrowser; out Result: Boolean);
begin
  inherited;
  Result := True;
  Close;
end;
stops the process of closing other windows and the application itself - standard TForm logic:

Code: Select all

function TCustomForm.CloseQuery: Boolean;
var
  I: Integer;
begin
  if FormStyle = fsMDIForm then
  begin
    Result := False;
    for I := 0 to MDIChildCount - 1 do
      if not MDIChildren[I].CloseQuery then Exit;
  end;
  Result := True;
  if Assigned(FOnCloseQuery) then FOnCloseQuery(Self, Result);
end;
So that does not work. Our next idea was to hook OnClose instead and change the Action from caFree to caHide - to delay the actual closing of the window, but that causes crashes. Have tried variants of:

Code: Select all

procedure TCEFBrowserForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  inherited;
  if not FChromiumClosed and (Action = caFree) then
  begin
    FChromiumClosed := True;
    Chromium.CloseBrowser(True);
    Action := caHide;
  end;
end;

procedure TCEFBrowserForm.ChromiumClose(Sender: TObject; const browser: ICefBrowser; out Result: Boolean);
begin
  inherited;
  Result := True;
//  Result := False;
  FChromiumClosed := True;
  Release; //??
//  Close;
end;
Often it never gets to ChromiumClose at all - and the hidden MDI forms are forcibly freed in the TApplication.DoneComponents code.

Then some message handling code crashes.

I also noticed the advice to call cef_quit_message_loop. It does not look like CEF4Delphi calls this anywhere, so I added it to our main application:

Code: Select all

procedure TInfrontMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if Assigned(cef_quit_message_loop) then
    cef_quit_message_loop;
But that didn't help.

Starting to feel stuck here.

Re: Update with bug fixes #18 and #26

Posted: Fri Apr 28, 2017 2:51 pm
by salvadordf
I'll take a look at this over the weekend.

Re: Update with bug fixes #18 and #26

Posted: Sun Apr 30, 2017 6:00 pm
by salvadordf
I'll create a MDI demo for the next version of CEF4Delphi.

Re: Update with bug fixes #18 and #26

Posted: Tue May 02, 2017 7:53 am
by hvassbotn
Thanks, great.

Re: Update with bug fixes #18 and #26

Posted: Wed May 03, 2017 3:17 pm
by hvassbotn
Looking at the MDIBrowser project now. Thanks for coming up with something so quickly!

I'm testing it, and am seeing some issues:

Steps:
- Open three child windows, leave them at the default google search page
- Close one of the windows - it is very slow to close here, ca 4 seconds
- Try to close the main window - it only closes one of the child windows, the other window stays running

Can you reproduce these issues? :)

Re: Update with bug fixes #18 and #26

Posted: Thu May 04, 2017 7:51 am
by salvadordf
hvassbotn wrote: Wed May 03, 2017 3:17 pm - Close one of the windows - it is very slow to close here, ca 4 seconds
Depending on what you are loading you can skip the 'about:blank' loading step or reduce the Timer.Interval.
hvassbotn wrote: Wed May 03, 2017 3:17 pm - Try to close the main window - it only closes one of the child windows, the other window stays running
I'll take a look as soon as I have time.

Re: Update with bug fixes #18 and #26

Posted: Thu Aug 10, 2017 1:02 pm
by hvassbotn
This part work much better now, thanks! :-)

Starting with the MDI example project, I had some minor issues in the order of events etc. After a few small tweaks, it works fine here now.

However, we're experiencing another issue with POST requests, creating a new thread for that.

/Hallvard