11

I'm having some troubles with javascript error handling in WebBrowser on Delphi 2010.

I'm using WebBrowser with enabled silent property. Seems OK, but there is one issue on sites with buggy scripts: it seems like part of script after error doesn't executes. Results of some script slightly differs from IE.

Do you have any idea how this issue can be solved?

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
TipTop
  • 143
  • 1
  • 2
  • 9

2 Answers2

12

You can use the IOleCommandTarget and in its IOleCommandTarget.Exec method catch the OLECMDID_SHOWSCRIPTERROR command.

In the following example I've used the interposed class so if you put this code into your unit, only those web browsers on the form or those created in this unit dynamically will get this behavior.

uses
  SHDocVw, ActiveX;

type
  TWebBrowser = class(SHDocVw.TWebBrowser, IOleCommandTarget)
  private
    function QueryStatus(CmdGroup: PGUID; cCmds: Cardinal; prgCmds: POleCmd;
      CmdText: POleCmdText): HRESULT; stdcall;
    function Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD; 
      const vaIn: OleVariant; var vaOut: OleVariant): HRESULT; stdcall;
  end;

implementation

function TWebBrowser.QueryStatus(CmdGroup: PGUID; cCmds: Cardinal; 
  prgCmds: POleCmd; CmdText: POleCmdText): HRESULT; stdcall;
begin
  Result := S_OK;
end;

function TWebBrowser.Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD; 
  const vaIn: OleVariant; var vaOut: OleVariant): HRESULT; stdcall;
begin
  // presume that all commands can be executed; for list of available commands
  // see SHDocVw.pas unit, using this event you can suppress or create custom 
  // events for more than just script error dialogs, there are commands like 
  // undo, redo, refresh, open, save, print etc. etc.
  // be careful, because not all command results are meaningful, like the one
  // with script error message boxes, I would expect that if you return S_OK,
  // the error dialog will be displayed, but it's vice-versa
  Result := S_OK;

  // there's a script error in the currently executed script, so
  if nCmdID = OLECMDID_SHOWSCRIPTERROR then
  begin
    // if you return S_FALSE, the script error dialog is shown
    Result := S_FALSE;
    // if you return S_OK, the script error dialog is suppressed
    Result := S_OK;
  end;
end;
TLama
  • 75,147
  • 17
  • 214
  • 392
  • This method also supresses all javascript popups. – TipTop Apr 24 '12 at 11:47
  • Do you have any sample page which behaves this way ? See [`this`](http://support.microsoft.com/kb/261003) article. Are you sure there is no error before the popup is displayed ? IMHO it should suppress only errors, but I can take a look... – TLama Apr 24 '12 at 11:52
  • @TipTop, generally speaking, the code has nothing to do with JavaScript invoked popups. If you do have problem with the code, I think the default return value should not be S_OK, but OLECMDERR_E_NOTSUPPORTED. – stanleyxu2005 May 21 '12 at 14:45
  • @TLama, the default return value should be OLECMDERR_E_NOTSUPPORTED for the two methods. Trust me. – stanleyxu2005 May 21 '12 at 14:46
4

Here are my recommendation of implementation.

uses
  SHDocVw, ActiveX;

type
  TWebBrowser = class(SHDocVw.TWebBrowser, IOleCommandTarget)
  private
    function QueryStatus(CmdGroup: PGUID; cCmds: Cardinal; prgCmds: POleCmd;
      CmdText: POleCmdText): HRESULT; stdcall;
    function Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD; 
      const vaIn: OleVariant; var vaOut: OleVariant): HRESULT; stdcall;
  end;

implementation

function TWebBrowser.QueryStatus(CmdGroup: PGUID; cCmds: Cardinal; 
  prgCmds: POleCmd; CmdText: POleCmdText): HRESULT; stdcall;
begin
  // MSDN notes that a command target must implement this function; E_NOTIMPL is not a 
  // valid return value. Be careful to return S_OK, because we notice that context menu 
  // of Web page "Add to Favorites..." becomes disabled. Another MSDN document shows an
  // example with default return value OLECMDERR_E_NOTSUPPORTED.
  // http://msdn.microsoft.com/en-us/library/bb165923(v=vs.80).aspx
  Result := OLECMDERR_E_NOTSUPPORTED;
end;

function TWebBrowser.Exec(CmdGroup: PGUID; nCmdID, nCmdexecopt: DWORD; 
  const vaIn: OleVariant; var vaOut: OleVariant): HRESULT; stdcall;
var
  ShowDialog, InterpretScript: Boolean;
begin
  if CmdGroup = nil then
  begin
    Result := OLECMDERR_E_UNKNOWNGROUP;
    Exit;
  end;

  // MSDN notes that a command target must implement this function; E_NOTIMPL is not a 
  // valid return value. Be careful to return S_OK, because we notice some unhandled
  // commands behave unexpected with S_OK. We assumed that a return value 
  // OLECMDERR_E_NOTSUPPORTED means to use the default behavior.
  Result := OLECMDERR_E_NOTSUPPORTED;

  if IsEqualGUID(CmdGroup^, CGID_DocHostCommandHandler) then
  begin
    // there's a script error in the currently executed script, so
    if nCmdID = OLECMDID_SHOWSCRIPTERROR then
    begin
      ShowDialog := True;
      InterpretScript := False; 

      // Implements an event if you want, so that your application is able to choose the way of handling script errors at runtime.
      if Assigned(OnNotifyScriptError) then
        OnNotifyScriptError(Self, ShowDialog, InterpretScript);

      if ShowDialog then
        Result := S_FALSE
      else
        Result := S_OK;
      vaOut := InterpretScript; // Without setting the variable to true, further script execution will be cancelled.
    end;
  end;
end;
stanleyxu2005
  • 8,081
  • 14
  • 59
  • 94
  • "vaOut := InterpretScript;" At least this is a valuable hint. I read msdn many times, I agree with you, that these return values *should* be S_OK. But according to my experience in real application, I have to set them to OLECMDERR_E_NOTSUPPORTED, otherwise it behaves unexpectedly. – stanleyxu2005 May 21 '12 at 15:49
  • Please review your code and make sure you know what you say before you manifest to *compare my post with another one, I have something valuable.* Where did you found the `vaOut` value is boolean ? How do you know the result of the currently executed command will be boolean and will mean True to execute ? Next, you're mixing result values, I've told you before, the `IOleCommandTarget::QueryStatus` has no `OLECMDERR_E_NOTSUPPORTED` result value... Next, why are you testing event handler for pointer to pointer ? Just test `if Assigned(OnNotifyScriptError) then OnNotifyScriptError(...)` – TLama May 21 '12 at 19:11
  • ...take a look how VCL is written, it's the best source you can get. The line with `IsEqualGUID` I don't get at all. My personal conclusion, if you took this serious then try to read the documentation more carefully (if you got it from another, unofficial documentation, then leave it). I'm glad if someone review my post and tell me own opinion, but not this way. If you just needed to review your own code, then you might leave me a comment and I could help you with it e.g. through e-mail. – TLama May 21 '12 at 19:29
  • (1) nCmdID is not unique. different command group might have a same nCmdID. This is the reason, why we have to detect command group using IsEqualGUID() first. (2) vaOut is a OleVariant. it can be string, boolean, integer or whatever. (here have some examples: http://msdn.microsoft.com/en-us/library/aa752044(v=vs.85)) it depends on the command itself and unfortunately, the type and value of vaOut are usually not fully documented officially. I have already verify the command, vaOut stands for a boolean. – stanleyxu2005 May 22 '12 at 05:19