[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 422
  • Last Modified:

Implementing Custom IDOCHOSTUIHANDLER causes 'showmodaldialog' boxes to stop working

Here's a tricky one, that I've spent countless hours trying to solve but have been unsuccessful so far.

I'm in the process of building a web browser application based on the 'TWebBrowser' Web Browser Control. As a part of this application I have implemented the IDOCHOSTUIHANDLER interface to allow me to remove the scrollbars from the TWebBrowser.

I have implemented the following code for scrollbars:

function TCustomBrowser.GetHostInfo(var pInfo: TDOCHOSTUIINFO): HRESULT;
begin
     pInfo.cbSize := SizeOf(pInfo);
     pInfo.dwFlags := 0;
     if not UIProperties.EnableScrollBars then
        pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_SCROLL_NO;
     if UIProperties.EnableFlatScrollBars then
        pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_FLAT_SCROLLBAR;
     if not UIProperties.Enable3DBorder then
        pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_NO3DBORDER;
     result := S_OK;
end;

And set the results of basically every other function in the IDOCHOSTUIHANDLER to S_FALSE. This works fine.


My implementation of TWebBrowser and the IDOCHOSTUIHANDLER appears to work well, with one exception. I've been able to specifically replicate with Yahoo Web Mail, although I'm sure it undoubtedly exists elsewhere:

PROBLEM: When I use my custom IDOCHOSTUIHANDLER interface like so:

TCustomBrowser = class (TWebBrowser, IDocHostUIHandler, IOleCommandTarget, IDispatch)

In Yahoo Web Mail and a user logs into Yahoo's webmail client and attempts to delete all their mail at once, Yahoo's Web Mail client attempts to pop up a dialog asking the user to confirm their action using the 'showmodaldialog' function.

With my custom IDocHostUIHandler in place, the call fails, the dialog box never appears with an 'Object Required' script error.

If I comment out my IDOCHOSTUIHANDLER implementation like so:

TCustomBrowser = class (TWebBrowser, {IDocHostUIHandler,} IOleCommandTarget, IDispatch)

and just use the web browser controls own default interface, the dialog appears fine.

My question is what am I doing wrong? I realise this may seem like an odd question because usually I've found people asking how to hide these dialogs!
0
moonrisesystems
Asked:
moonrisesystems
  • 10
  • 9
1 Solution
 
JaccoCommented:
Have you tried changing the implementation of EnableModeless? It says on msdn that it is also called when a modal dialog is shown.

Regards Jacco
0
 
moonrisesystemsAuthor Commented:
Jacco,

Youre right, I've confirmed through my own testing that this function (EnableModeless) is called everytime the showmodaldialog function is used.

I've attempted returning all of the three possible HRESULT values for this function (S_FALSE, S_OK, E_NOTIMPL) but the result is the same regardless. No dialog message and a script error is generated.

My best guess at this point is that I haven't implemented a method necessary to support these kinds of dialogs from another interface such as idochostshowui, etc.
0
 
moonrisesystemsAuthor Commented:
Another thing I've discovered:

Returning E_NOTIMPL causes a 'Not Implemented' Script Error to be generated

Returning S_FALSE or S_OK causes a 'Object Required' Script Error to be generated
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
JaccoCommented:
But did you try returning True and False for the var parameter as well?
0
 
moonrisesystemsAuthor Commented:
Yes, I've attempted to set fEnable to true and to false but the result is still the same script error 'Object Required'
0
 
JaccoCommented:
I have found the following on MSDN:

FilterData:
If the implementation of this method does not supply its own IDataObject, ppDORet should be set to NULL, even if the method fails or returns S_FALSE.

GetDropTarget:
If the host does not supply an alternative drop target, this method should return a failure code, such as E_NOTIMPL or E_FAIL.

GetExternal:
If the method implementation does not supply an IDispatch, ppDispatch should be set to NULL, even if the method fails or returns S_FALSE.

TranslateUrl:
If the implementation of this method does not supply a URL, ppchURLOut should be set to NULL, even if the method fails or returns S_FALSE.

GetOptionKetPath:
Even if this method is unimplemented, the parameter pchKey should be set to NULL.

Regards Jacco
0
 
moonrisesystemsAuthor Commented:
Thanks for all your help so far Jacco. I've tried the following:

FilterData:             ppDORet := nil;
GetDropTarget:      ppDropTarget := nil;
GetExternal:           ppDispatch := nil;
TranslateURL:         ppchURLOut := nil;
GetOptionsKeyPath: pchKey := nil;

From what I can gather, setting these values to nil in Delphi is the equivilent of nulling them because they are pointers anyways.
 
Makes no difference, the javascript error still appears and the confirmation dialog does not appear. The one thing I did notice was that when I varied the result value on 'GetExternal' the error message changed from 'Object Required' (for S_FALSE) to 'Not Implemented' (E_NOTIMPL). It's possible this is what's holding it up but I'm still lost.
0
 
JaccoCommented:
What I need to test is

- the declaration of IDocHostUIHandler, where can I find it?
- and a Yahoo Web Mail account

Is that correct. I want to try and replicate you error.

I hope that by simulating I might be able to find why the error occurs.

Jacco
0
 
moonrisesystemsAuthor Commented:
Jacco,

Yes that's correct.

With regards to an implementation of IDocHostUIHandler in Delphi, you can either use EmbeddedWeb (available here: http://www.euromind.com/iedelphi/embeddedwb.htm)

- or -

I can provide you with a generic source code sample available on the net here that is pretty much what I currently use. Let me know what works for you.

With the Yahoo mail, just to be absolutely clear to replicate this error:

1) Send yourself a test mail to this account
2) login and go to your inbox
3) click the checkbox at the top of the mail list (to select all messages in the inbox)
4) press Delete. Normally under IE you will see a dialog box pop up, but not with a custom idochostuihandler.
0
 
JaccoCommented:
It might take a while until I have to time set this up

Regards Jacco
0
 
moonrisesystemsAuthor Commented:
No worries Jacco, this isn't exactly a critical issue so take your time.
0
 
JaccoCommented:
Hi there,

I think I've got it. It seems like the java environment is not happy with the way TOleControl implements IDispatch. When returning E_NOTIMPL in GetIDsOfNames the script error is generated. I was able to change this behavior by overriding the four methods of IDispatch in TCustomBrowser. Make sure you have IDispatch mentioned in the TCustomBrowser class heading otherwise the added methods are not seen as overrides.

function TCustomBrowser.GetTypeInfoCount(out Count: Integer): HResult;
begin
  Result := inherited GetTypeInfoCount(Count);
end;

function TCustomBrowser.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
  Result := DISP_E_UNKNOWNNAME;
end;

function TCustomBrowser.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
begin
  Result := inherited GetTypeInfo(Index, LocaleID, TypeInfo);
end;

function TCustomBrowser.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,  ArgErr: Pointer): HResult;
begin
  Result := inherited Invoke(DispID, IID, LocaleID, Flags, Params, VarResult, ExcepInfo, ArgErr);
end;

It is probably enough to do just GetIDsOfNames and return DISP_E_UNKNOWNNAME but just in case other errors occur and need patching I added them here.

Regards Jacco
0
 
moonrisesystemsAuthor Commented:
Hey,

It looks like I still may be missing something on my end. I attempted to add the IDispatch methods into my browser implementation, but the same 'Object Required' error still presents itself. I'd be curious to learn more about your particular implementation of the IDOCHOSTUIHANDLER in the custom browser environment. It may be that there is something I have overlooked.

Three things of interest:

1) I'm not sure if this was missing on your part but those functions would not compile unless I added 'stdcall;' on the end of them.

2) If I step-through the program one line at a time, the only one of those functions that appears to be called is Invoke. The others don't ever seem to be called. I guess it could be possible that you can't trace through them step-by-step.

3) I am fairly convinced that the error occurs somewhere around when GetExternal, because when I step through the program, the last visible method that is called is GetExternal before the error appears.


function TCustomBrowser.GetExternal(out ppDispatch: IDispatch): HRESULT; stdcall;
begin
  Result := S_FALSE;
end;

Ultimately, the only method I actually need to call in IDOCHOSTUIHANDLER is GetHostInfo, the rest I could care less about. If there was a way to simply override the functions I don't need by calling their original methods with the keyword inherited I would, but I could not get this to work, example:

function TCustomBrowser.GetExternal(out ppDispatch: IDispatch): HRESULT; stdcall;
begin
  Result := inherited GetExternal(ppDispatch);
end;
0
 
moonrisesystemsAuthor Commented:
Wow, the mystery of this problem grows stranger!

I found another example where showmodaldialog is used: http://www.webreference.com/js/tutorial1/dialog.html

Admittedly this appears to be a much simpler implementation of a showmodaldialog, but most importantly, my browser successfully displays this dialog with no error! I'm presently looking to see if I can find further showmodaldialogs or if it is just this one on Yahoo that does not appear to work for whatever reason.
0
 
JaccoCommented:
I forgot to say I believe I have implemented

function TCustomBrowser.GetExternal(out ppDispatch: IDispatch): HRESULT; stdcall;
begin
  Result := Self;
  Result := S_OK;
end;

Regards Jacco

PS I will post the exact code when I get home :)
0
 
JaccoCommented:
I have the thing working with no error whatsoever...
0
 
JaccoCommented:
Here is the code of the test unit:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtrls, SHDocVw, ComObj, IeConst, ActiveX, StdCtrls, ExtCtrls;

type
  TCustomBrowser = class(TWebBrowser, IDocHostUIHandler {, IOleCommandTarget}, IDispatch)
  private
    function GetHostInfo(var pInfo: TDOCHOSTUIINFO): HRESULT; stdcall;
    function ShowContextMenu(const dwID: DWORD; const ppt: PPOINT; const pcmdtReserved: IUnknown; const pdispReserved: IDispatch): HRESULT; stdcall;
    function ShowUI(const dwID: DWORD; const pActiveObject: IOleInPlaceActiveObject; const pCommandTarget: IOleCommandTarget; const pFrame: IOleInPlaceFrame; const pDoc: IOleInPlaceUIWindow): HRESULT; stdcall;
    function HideUI: HRESULT; stdcall;
    function UpdateUI: HRESULT; stdcall;
    function EnableModeless(const fEnable: BOOL): HRESULT; stdcall;
    function OnDocWindowActivate(const fActivate: BOOL): HRESULT; stdcall;
    function OnFrameWindowActivate(const fActivate: BOOL): HRESULT; stdcall;
    function ResizeBorder(const prcBorder: PRECT; const pUIWindow: IOleInPlaceUIWindow; const fRameWindow: BOOL): HRESULT; stdcall;
    function TranslateAccelerator(const lpMsg: PMSG; const pguidCmdGroup: PGUID; const nCmdID: DWORD): HRESULT; stdcall;
    function GetOptionKeyPath(var pchKey: POLESTR; const dw: DWORD): HRESULT; stdcall;
    function GetDropTarget(const pDropTarget: IDropTarget; out ppDropTarget: IDropTarget): HRESULT; stdcall;
    function GetExternal(out ppDispatch: IDispatch): HRESULT; stdcall;
    function TranslateUrl(const dwTranslate: DWORD; const pchURLIn: POLESTR; var ppchURLOut: POLESTR): HRESULT; stdcall;
    function FilterDataObject(const pDO: IDataObject; out ppDORet: IDataObject): HRESULT; stdcall;
    { IDispatch }
    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
  end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    Memo1: TMemo;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    WWW: TCustomBrowser;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

function TCustomBrowser.EnableModeless(const fEnable: BOOL): HRESULT;
begin
  if fEnable then
    Form1.Memo1.Lines.Add('EnableModeless(True)')
  else
    Form1.Memo1.Lines.Add('EnableModeless(False)');
end;

function TCustomBrowser.FilterDataObject(const pDO: IDataObject; out ppDORet: IDataObject): HRESULT;
begin
  Form1.Memo1.Lines.Add('FilterDataObject');

end;

function TCustomBrowser.GetDropTarget(const pDropTarget: IDropTarget; out ppDropTarget: IDropTarget): HRESULT;
begin
  Form1.Memo1.Lines.Add('GetDropTarget');

end;

function TCustomBrowser.GetExternal(out ppDispatch: IDispatch): HRESULT;
begin
  Form1.Memo1.Lines.Add('GetExternal');
  ppDispatch := Self;
  Result := S_OK;
end;

function TCustomBrowser.GetHostInfo(var pInfo: TDOCHOSTUIINFO): HRESULT;
begin
  Form1.Memo1.Lines.Add('GetHostInfo');
  pInfo.cbSize := SizeOf(pInfo);
  pInfo.dwFlags := 0;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_SCROLL_NO;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_FLAT_SCROLLBAR;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_NO3DBORDER;
  result := S_OK;
end;


function TCustomBrowser.GetOptionKeyPath(var pchKey: POLESTR; const dw: DWORD): HRESULT;
begin
  Form1.Memo1.Lines.Add('GetOptionKeyPath');

end;

function TCustomBrowser.HideUI: HRESULT;
begin
  Form1.Memo1.Lines.Add('HideUI');

end;

function TCustomBrowser.OnDocWindowActivate(const fActivate: BOOL): HRESULT;
begin
  Form1.Memo1.Lines.Add('OnDocWindowActivate');

end;

function TCustomBrowser.OnFrameWindowActivate(const fActivate: BOOL): HRESULT;
begin
  Form1.Memo1.Lines.Add('OnFrameWindowActivate');

end;

function TCustomBrowser.ResizeBorder(const prcBorder: PRECT; const pUIWindow: IOleInPlaceUIWindow; const fRameWindow: BOOL): HRESULT;
begin
  Form1.Memo1.Lines.Add('ResizeBorder');

end;

function TCustomBrowser.ShowContextMenu(const dwID: DWORD; const ppt: PPOINT; const pcmdtReserved: IInterface; const pdispReserved: IDispatch): HRESULT;
begin
  Form1.Memo1.Lines.Add('ShowContextMenu');

end;

function TCustomBrowser.ShowUI(const dwID: DWORD; const pActiveObject: IOleInPlaceActiveObject; const pCommandTarget: IOleCommandTarget; const pFrame: IOleInPlaceFrame; const pDoc: IOleInPlaceUIWindow): HRESULT;
begin
  Form1.Memo1.Lines.Add('ShowUI');

end;

function TCustomBrowser.TranslateAccelerator(const lpMsg: PMSG; const pguidCmdGroup: PGUID; const nCmdID: DWORD): HRESULT;
begin
  Form1.Memo1.Lines.Add('TranslateAccelerator');

end;

function TCustomBrowser.TranslateUrl(const dwTranslate: DWORD; const pchURLIn: POLESTR; var ppchURLOut: POLESTR): HRESULT;
begin
  Form1.Memo1.Lines.Add('TranslateUrl');

end;

function TCustomBrowser.UpdateUI: HRESULT;
begin
  Form1.Memo1.Lines.Add('UpdateUI');

end;

function TCustomBrowser.GetTypeInfoCount(out Count: Integer): HResult;
begin
  Result := inherited GetTypeInfoCount(Count);
end;

function TCustomBrowser.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
  Result := DISP_E_UNKNOWNNAME;
end;

function TCustomBrowser.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
begin
  Result := inherited GetTypeInfo(Index, LocaleID, TypeInfo);
end;

function TCustomBrowser.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,  ArgErr: Pointer): HResult;
begin
  Result := inherited Invoke(DispID, IID, LocaleID, Flags, Params, VarResult, ExcepInfo, ArgErr);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  WWW := TCustomBrowser.CreateParented(Self.Handle);
  WWW.SetParent(Panel1);
  WWW.Align := alClient;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  WWW.Navigate('mail.yahoo.com');
end;

end.
0
 
moonrisesystemsAuthor Commented:
DING DING DING! We have a winner!

function TCustomBrowser.GetExternal(out ppDispatch: IDispatch): HRESULT;
begin
  ppDispatch := Self;
  Result := S_OK;
end;

After doing some thorough testing, I found that unless you do the following two steps:

1) implement IDispatch methods as shown above
2) Set ppDispatch to Self in 'GetExternal'

You will get a Javascript error of some description. Well done Jacco, with your help this problem is now solved! Congratulations on a well earned 500 points.



0
 
JaccoCommented:
Thanks! For the 2000 points if you graded A :-)

Happy to be of assistance.

Regards Jacco
0

Featured Post

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

  • 10
  • 9
Tackle projects and never again get stuck behind a technical roadblock.
Join Now