Skip over navigation

How to call Delphi code from scripts running in a TWebBrowser

Contents

Introduction

When writing programs that use the TWebBrowser control as part of the user interface I've sometimes needed my program to respond to user interaction with HTML displayed in the control. The official way to do this is to extend the web browser's external object, and this is the technique we will use in this article.

The external object is part of TWebBrowser's document object model. The object enables interaction with the environment surrounding the browser control – in this case our program.

We can extend the external object by adding methods to it that are implemented in Delphi rather than in JavaScript or VBScript. We do this by creating a COM automation object that exposes the required methods, then notifying the TWebBrowser control that the COM object extends the external object. The new external object methods can then be called from JavaScript or VBScript running in the TWebBrowser control, which results in our Delphi code executing.

The rest of this article examines how to use Delphi to create and manipulate the external object.

Overview

The solution divides neatly into three main activities:

  1. Extend the external object in Delphi by creating a COM automation object that exposes the required methods.
  2. Register the extended external object with the browser control so that the object's methods can be called from within the control. We do this by implementing the IDocHostUIHandler interface and enabling the TWebBrowser control to use our implementation.
  3. Call the methods of the extended external object from JavaScript running in the browser control.

The next sections discuss each of the above activities in turn. Finally a case study will be presented that puts the techniques we have learned into practise.

Implementing the external object

As already noted we extend the external object by creating a COM automation object – i.e. one that implements an interface that derives from IDispatch.

An easy way to do this is to derive the new class from TAutoIntfObject. This class implements the methods of IDispatch and so saves us from having to do this ourselves. However, TAutoIntfObject needs a type library to work with. Consequently we will use Delphi's Type Library editor to create a suitable type library that defines our new interface.

Once we have defined the required interface in the Type Library Editor we create the type library by pressing Ctrl+S. This will do three things:

  1. Depending on your version of Delphi it will either create the type library (.tlb) file directly or create an intermediate .ridl file that will be compiled to the required .tlb file when you next build the program.
  2. Create a Pascal unit containing, amongst other things, the interface definition. The unit will have the same name as the .tlb file but will end in _TLB.pas.
  3. Add a reference to the _TLB.pas unit to the project file.

Some versions of the type library editor will also include the type library in the program's resources by inserting a suitable $R compiler directive in the project's .dpr file. Other versions don't do this for you. If this is the case you need to add the line {$R *.tlb} to your .dpr file.

When creating the interface in the Type Library Editor we must abide by the following rules:

  1. Ensure the new interface is derived from IDispatch.
  2. Ensure all methods have a return value of HRESULT.
  3. Use only automation compatible parameter types.
  4. Ensure all [out] parameters are pointer types, i.e. they end with * (for example, BSTR *).
  5. Return any values from methods via a parameter that has an [out,retval] modifier.

Once we have our new type library and interface we create a new class that descends from TAutoIntfObject. Then we implement the methods of our interface in the class. This can be done by copying the method prototypes from the interface declaration in the *_TLB.pas file and pasting them into the class's declaration.

Note that Delphi creates the method prototypes using the safecall calling convention which means that any [out,retval] parameters become function results. For example, suppose we use the Type Library Editor to create an interface called IMyIntf that has two methods, Foo and Bar. Assume the method parameters are defined as in Table 1.

Method Parameters Type Modifiers
Foo Param1 long [in]
Result BSTR * [out,retval]
Bar Param1 BSTR [in]
Table 1

The *_TLB.pas file created by Delphi would contain the following interface definition shown in Listing 1, except that the interface's GUID will be different.

  1type
  2  IMyIntf = interface(IDispatch)
  3    ['{E1CDA762-584F-4E9E-9808-2312C689FA17}']
  4    function Foo(Param1: Integer): WideString; safecall;
  5    procedure Bar(const Param1: WideString); safecall;
  6  end;
Listing 1

We would therefore include the following methods in our class declaration:

  1type
  2  TMyClass = class(TAutoIntfObject,
  3    IMyIntf, IDispatch
  4  )
  5  private
  6    ...
  7  protected
  8    { IMyIntf methods }
  9    function Foo(Param1: Integer): WideString; safecall;
 10    procedure Bar(const Param1: WideString); safecall;
 11    ...
 12  end;
Listing 2

These methods would then be implemented as required.

Remember that we don't need to declare or implement anymethods of IDispatch since they are already implemented by TAutoIntfObject.

Now TAutoIntfObject's implementation of the IDispatch methods depends on having access to the type library that describes the methods of the interfaces implemented by descendent classes. This is achieved by passing an object, that implements ITypeLib as a parameter, to TAutoIntfObject's constructor. It is our job to create such an ITypeLib object that "knows about" our type library.

We do this by declaring a parameterless constructor for the derived class. In the constructor we call the LoadTypeLib API function, passing the name of our application as a parameter. LoadTypeLib accesses the type library information that is embedded in the application's resources and creates the required ITypeLib object based on this information. We then pass this ITypeLib object to the inherited constructor. Assuming our derived class is named TMyExternal, Listing 3 shows the constructor's implementation.

  1constructor TMyExternal.Create;
  2var
  3  TypeLib: ITypeLib;    // type library information
  4  ExeName: WideString;  // name of our program's exe file
  5begin
  6  // Get name of application
  7  ExeName := ParamStr(0);
  8  // Load type library from application's resources
  9  OleCheck(LoadTypeLib(PWideChar(ExeName), TypeLib));
 10  // Call inherited constructor
 11  inherited Create(TypeLib, IMyExternal);
 12  // ...
 13  // Do any other initialisation here
 14  // ...
 15end;
Listing 3

Registering the external object with TWebBrowser

Having implemented the COM object that extends the external object, how do we tell the web browser about it?

The answer is by creating a container object that hosts the web browser control. This object must implement the IDocHostUIHandler and IOleClientSite interfaces. IDocHostUIHandler has a GetExternal method that we must implement to return a reference to our custom external object.

IDocHostUIHandler has many other methods that we have to implement, but we can get away with stubbing them out. In the article "How to customise the TWebBrowser user interface" I discussed IDocHostUIHandler in detail and presented a do nothing implementation named TNulWBContainer [sic]. I won't repeat that presentation here, so please check out the Developing a reusable base class section of the article if you need to review how this is done.

Doing all the hard work in TNulWBContainer means that our implementation of IDocHostUIHandler and IOleClientSite can be quite simple if we descend our container class from TNulWBContainer. Listing 4 has the declaration of an example of such a class, which we will call TExternalContainer. Listing 5 shows its implementation.

  1type
  2  TExternalContainer = class(TNulWBContainer,
  3    IDocHostUIHandler, IOleClientSite)
  4  private
  5    fExternalObj: IDispatch;  // external object implementation
  6  protected
  7    { Re-implemented IDocHostUIHandler method }
  8    function GetExternal(out ppDispatch: IDispatch): HResult; stdcall;
  9  public
 10    constructor Create(const HostedBrowser: TWebBrowser);
 11  end;
Listing 4
  1constructor TExternalContainer.Create(
  2  const HostedBrowser: TWebBrowser);
  3begin
  4  inherited Create(HostedBrowser);
  5  fExternalObj := TMyExternal.Create;
  6end;
  7
  8function TExternalContainer.GetExternal(
  9  out ppDispatch: IDispatch): HResult;
 10begin
 11  ppDispatch := fExternalObj;
 12  Result := S_OK; 
 13end;
Listing 5

Notice that we create an instance of our external object extension in the constructor and store it in a field of type IDispatch. We then implement GetExternal to pass back a reference to the external object in ppDispatch and return S_OK to indicate we have provided the object.

We pass a reference to the web browser control we are hosting to the constructor. This reference is simply passed on to the inherited constructor where it is recorded and notified that our object is its container. See the implementation of TNulWBContainer.Create in article #18 for details of how this is done.

Calling into Delphi from JavaScript

Having implemented our external object extension and registered it with the browser control, it is now time to look at how we call the object's methods from JavaScript.

A similar approach is taken if you wish to use VBScript rather than JavaScript, but the precise details are, as they say, left as an exercise!

All we have to do to call into our Delphi code from JavaScript is to reference the relevant methods of the external object. For example to access external method Foo from JavaScript we would use the code presented in Listing 6:

  1external.Foo();
Listing 6

external object methods can be called anywhere that a JavaScript method call is valid – either from event handlers of HTML elements or from blocks of JavaScript code. For example, say we have implemented the interface shown in Listing 7 in Delphi, and have registered it as an extension of the external object:

  1type
  2  ITest = interface(IDispatch)
  3    ['{E1CDA762-584F-4E9E-9808-2312C689FA17}']
  4    procedure SetLabel(const Msg: WideString);
  5      safecall;
  6    function GetEMail(const Name: WideString): WideString;
  7      safecall;
  8  end;
Listing 7

Assume that SetLabel sets the caption of a TLabel to the given text and GetEMail looks up the name of the given person in a database and returns their email address.

We can ensure the label's caption is set when a user clicks a link by using HTML such as that presented in Listing 8.

  1<body>
  2  ...
  3  <a
  4    href="#"
  5    onclick="external.SetLabel('Hello Delphi');"
  6  >Click me</a>
  7  ...
  8</body>
Listing 8

We can also write someone's email address into the HTML document using a block of JavaScript similar to that given in Listing 9.

  1<body>
  2  ...
  3  <p>Fred's email address is:
  4    <script type="text/JavaScript">
  5      document.write( external.GetEMail("fred") );
  6    </script>
  7  </p>
  8  ...
  9</body>
Listing 9

As can be seen, once the hard work of setting up and registering the external object has been done, it is very easy to call the required methods.

That concludes our discussion of the techniques involved in calling Delphi code from JavaScript. In the next section we will work through a case study that draws all the strands together.

Case study

Overview

Our case study is a simple application that illustrates the techniques discussed in this article.

The program we will develop lists some of the programs available on the DelphiDabbler website. Clicking one of the program names will display a brief description (précis) of the program, in a box underneath the list of programs. When the mouse is moved over a program name, the URL of the its web page will be displayed in the status bar. The status bar will clear when there is no program name under the mouse. The précis of each program is stored in a data file that is read by our application.

Here is a screenshot of the completed program, compiled with Delphi 11.0 Alexandria as a 32 bit application and running on Windows 11:

Screenshot of case study program
Figure 1: Screenshot of case study program

We will develop the program in the following order:

  1. Design the main form.
  2. Define the required external object and create its type library / interface.
  3. Implement the external object.
  4. Implement the IDocHostUIHandler interface's GetExternal method.
  5. Register the IDocHostUIHandler implementation with the web browser control.
  6. Create the HTML file, containing the required JavaScript, that will be displayed in the browser control.

Designing the main form

To begin the project, start a new Delphi VCL GUI application and set up the main form as follows:

  • Drop a TStatusBar and set its SimplePanel and AutoHint properties to True.
  • Drop a TWebBrowser control and set its Align property to alClient.

That is all there is to the main form. SSave it with the default name, probably Unit1.pas.

Defining the external object

Let us consider the methods we need to add to the browser's external object. From the specification above we see that we need methods to perform the following actions:

  1. Get the précis of a specified program when a program name is clicked (GetPrecis method). We will use an id string to uniquely identify each program.
  2. Display the URL of a program's web page in the status bar when the mouse cursor is over a program name (ShowURL method). Again we will pass the program's id as a parameter.
  3. Clear the status bar when no program's name is under the cursor (HideURL method).

We can now create an interface that contains each of the required methods. Start Delphi's Type Library Editor and use it to create a new interface named IMyExternal. Make sure the new interface has IDispatch as it parent interface. Now add three methods using the information in Table 2, ensuring that each method has a return type of HRESULT.

Method Parameters Type Modifiers
GetPrecis ProgID
Result
BSTR
BSTR *
[in]
[out,retval]
ShowURL ProgID BSTR [in]
HideURL - - -
Table 2

Press Ctrl+S to save the type library and name it either Article22.ridlor Article22.tlb, depending on the version of Delhi in use – just accept the suggested file type in any Save dialogue box. Either way, Delphi will now create a type library file named Article22.tlb and a Pascal unit named Article22_TLB.pas. Opening Article22_TLB.pas will reveal the IMyExternal interface declaration shown in Listing 10. (Note that the GUID will be different):

  1type
  2  ...
  3  IMyExternal = interface(IDispatch)
  4    ['{4F995D09-CF9E-4042-993E-C71A8AED661E}']
  5    function GetPrecis(const ProgID: WideString): WideString;
  6      safecall;
  7    procedure ShowURL(const ProgID: WideString); safecall;
  8    procedure HideURL; safecall;
  9  end;
 10  ...
Listing 10

Article22_TLB.pas will also contain a dispinterface named IMyExternalDisp. You can ignore this since we won't be using it.

Implementing the external object

Now that we have created the IMyExternal interface, we implement it in a class named TMyExternal. We will add a new unit, UMyExternal.pas to store the class. Listing 11 has the class declaration.

  1type
  2  TMyExternal = class(TAutoIntfObject, IMyExternal, IDispatch)
  3  private
  4    fData: TStringList; // info from data file
  5    procedure ShowSBMsg(const Msg: string); // helper method
  6  protected
  7    { IMyExternal methods }
  8    function GetPrecis(const ProgID: WideString): WideString;
  9      safecall;
 10    procedure ShowURL(const ProgID: WideString); safecall;
 11    procedure HideURL; safecall;
 12  public
 13    constructor Create;
 14    destructor Destroy; override;
 15  end;
Listing 11

As expected, the methods of IMyExternal are specified in the class' protected section. We also have a private helper method, ShowSBMsg, that displays a given message in the status bar. This method is used by both ShowURL and HideURL as we will see in a moment. The fData string list is used to store the précis of the different programs. This field is accessed by GetPrecis.

Let us look at the implementation of the class. We will start with the constructor and destructor shown in Listing 12:

  1constructor TMyExternal.Create;
  2var
  3  TypeLib: ITypeLib;    // type library information
  4  ExeName: WideString;  // name of our program's exe file
  5begin
  6  // Get name of application
  7  ExeName := ParamStr(0);
  8  // Load type library from application's resources
  9  OleCheck(LoadTypeLib(PWideChar(ExeName), TypeLib));
 10  // Call inherited constructor
 11  inherited Create(TypeLib, IMyExternal);
 12  // Create and load string list from file
 13  fData := TStringList.Create;
 14  fData.LoadFromFile(ChangeFileExt(ExeName, '.dat'));
 15end;
 16
 17destructor TMyExternal.Destroy;
 18begin
 19  fData.Free;
 20  inherited;
 21end;
Listing 12

The first three executable lines in the constructor are boilerplate code that has been explained earlier in the article. Following the call to the inherited constructor we create the TStringList object that is to store the précis data. We then read the string list's contents from a data file named Article22.dat that is expected to be found in the same directory as the application. The format of the data file is described later. The destructor simply frees the string list.

Now move on to examine the implementation of the three IMyExternal methods shown in Listing 13:

  1function TMyExternal.GetPrecis(
  2  const ProgID: WideString): WideString;
  3begin
  4  Result := fData.Values[ProgId];
  5end;
  6
  7procedure TMyExternal.HideURL;
  8begin
  9  ShowSBMsg('');
 10end;
 11
 12procedure TMyExternal.ShowURL(const ProgID: WideString);
 13begin
 14  ShowSBMsg(
 15    'https://delphidabbler.com/software/' + ProgID
 16  );
 17end;
Listing 13

GetPrecis looks up the given ProgID in the Values property of fData and returns the value found there. The data file that was loaded into fData in the constructor contains a line for each program. Each line has the format ProgID=Precis. The TStringList.Values[] property is designed to work with strings in this format.

HideURL uses ShowSBMsg to display an empty string in the status bar, which has the effect of clearing any previous message.

ShowURL simply appends its ProgID parameter to a literal string to produce the URL of the program's web page. It then calls ShowSBMsg to display the URL in the status bar.

All that remains is to look at Listing 14, which shows the implementation of ShowSBMsg.

  1procedure TMyExternal.ShowSBMsg(const Msg: string);
  2var
  3  HintAct: THintAction;
  4begin
  5  HintAct := THintAction.Create(nil);
  6  try
  7    HintAct.Hint := Msg;
  8    HintAct.Execute;
  9  finally
 10    HintAct.Free;
 11  end;
 12end;
Listing 14

Hmm – no mention of the status bar! What's happening here is that we're creating an instance of the VCL's THintAction action class, storing the message we want to display in its Hint property then executing the action. A magical feature of THintAction is that it automatically displays its hint in any TStatusBar that has its AutoHint property set to True. This let's us decouple our external object implementation quite nicely from the program's form.

Finally, we need to add a uses clause, containing the following units in order for the code to compile: Classes, ComObj, SysUtils, ActiveX, StdActns and Article22_TLB.

A note about unit names

All unit names in this example are unqualified. Depending on which version of Delphi you are using you may need to qualify the unit names with the required unit scope name. Unit scopes can vary between versions of Delphi, so it is left to the reader to determine the required name qualificatons.

If you need help doing this you could try the DelphiDabbler Unit2NS program.

Implementing IDocHostUIHandler

As already noted, we are re-using code from an earlier article for our declaration of IDocHostUIHandler and for the do-nothing implementation of the interface, TNulWBContainer So, to begin with, we must add the IntfDocHostUIHandler.pas and UNulContainer.pas units, developed in that article, to our project.

Unit source code

Units containing IDocHostUIHandler & TNulWBContainer are included with this article's source code.

We now create our custom container class, TExternalContainer, by descending from TNulWBContainer and overriding the GetExternal method to get the functionality we need. We will use exactly the same code as we developed in listings 4 & 5.

We will create a new unit, UExternalContainer.pas for TExternalContainer and add a uses clause that references the following units: ActiveX, SHDocVw, UNulContainer, IntfDocHostUIHandler and UMyExternal.

Registering the external object

Our final piece of Delphi code registers our TExternalContainer object as a client site (container) for the browser control. This is done in the main form simply by instantiating a TExternalContainer object and passing a reference to the browser control to its constructor. Recall that TExternalContainer's inherited constructor automatically registers the object as a client site of the contained web browser control.

We will use the form's OnShow event handler to create TExternalContainer. We will also use this event handler to load the required HTML file, as Listing 15 shows:

  1procedure TForm1.FormShow(Sender: TObject);
  2begin
  3  fContainer := TExternalContainer.Create(WebBrowser1);
  4  WebBrowser1.Navigate(
  5    ExtractFilePath(ParamStr(0)) + 'Article22.html'
  6  );
  7end;
Listing 15

Notice that we have stored a reference to the container object in a field named fContainer. Add such a field, with type TExternalContainer to the form's declaration.

Use FormShow and not FormCreate

We have to create TExternalContainer in the form's OnShow event handler instead of OnCreate (which is where we usually create objects) because the contained TWebBrowser will raise an exception if we create the container object in OnCreate. Delaying construction until the OnShow event handler gets called solves this problem.

Why? I don't really know, but I'm guessing that TWebBrowser requires the host form window to be created before it can interact with any container object.

Having created the container object we must also ensure it gets freed. This is done in the form's OnHide event handler as Listing 16 illustrates:

  1procedure TForm1.FormHide(Sender: TObject);
  2begin
  3  fContainer.Free;
  4end;
Listing 16

The last step is to add UExternalContainer to the main form's uses clause.

Creating the HTML file

We now need to create the HTML file that we loaded in Listing 15. This file is named Article22.html and listing 17 shows the file in full:

  1<?xml version="1.0"?>
  2
  3<!DOCTYPE html
  4  PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  5    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  6
  7<html xmlns="http://www.w3.org/1999/xhtml"
  8  xml:lang="en" lang="en">
  9  <head>
 10    <title>DelphiDabbler Articles</title>
 11    <style type="text/css">
 12      body {font-family: Tahoma; font-size: 10pt;}
 13      h1 {font-size: 12pt;}
 14      #precis {border: 1px solid silver; padding: 4px;}
 15    </style>
 16    <script type="text/javascript">
 17      function ShowPrecis(progID) {
 18        precisObj = document.getElementById("precis");
 19        progObj = document.getElementById(progID);
 20        precisObj.innerHTML = progObj.innerHTML.bold()
 21          + '\<br />'
 22          + external.GetPrecis(progID);
 23      }
 24    </script>
 25  </head>
 26  <body>
 27    <h1>DelphiDabbler Program Descriptions</h1>
 28    <ul>
 29      <li>
 30        <a
 31          href="javascript:void(0);"
 32          id="codesnip"
 33          onclick="ShowPrecis('codesnip');"
 34          onmouseover="external.ShowURL('codesnip');"
 35          onmouseout="external.HideURL()";
 36        >CodeSnip Database Viewer</a>
 37      </li>
 38      <li>
 39        <a
 40          href="javascript:void(0);"
 41          id="htmlres"
 42          onclick="ShowPrecis('htmlres');"
 43          onmouseover="external.ShowURL('htmlres');"
 44          onmouseout="external.HideURL()";
 45        >HTML Resource Compiler</a>
 46      </li>
 47      <li>
 48        <a
 49          href="javascript:void(0);"
 50          id="unit2ns"
 51          onclick="ShowPrecis('unit2ns');"
 52          onmouseover="external.ShowURL('unit2ns');"
 53          onmouseout="external.HideURL()";
 54        >Unit2NS</a>
 55      </li>
 56      <li>
 57        <a
 58          href="javascript:void(0);"
 59          id="bdiff"
 60          onclick="ShowPrecis('bdiff');"
 61          onmouseover="external.ShowURL('bdiff');"
 62          onmouseout="external.HideURL()";
 63        >BDiff / BPatch Utilities</a>
 64      </li>
 65    </ul>
 66    <div id="precis">
 67      Click a program name to see its description here.
 68    </div>
 69  </body>
 70</html>
Listing 17

Looking at the body section of the file we see that it contains a list of four program names, each defined as links (<a> tags) that reference no URL.

Do-nothing links

To ensure that a link does nothing when clicked we set the href attribute to call the JavaScript void() function, like this: href="javascript:void(0);". It's also possible to simply use href="#".

Every link has a unique id attribute that identifies the program to which it refers.

The a-links' onclick event handlers call the ShowPrecis JavaScript routine, passing in the id of the relevant program as a parameter. ShowPrecis is defined in the HTML head section. The function first finds the <div> tag with the id of "precis" and then finds the a-link element associated with the program id. The HTML enclosed by the "precis" <div> tag is then replaced by HTML comprising of the program name in bold, a line break and the actual précis of the program. The précis is returned by the external.GetPrecis method, which executes TMyExternal.GetPrecis in the Delphi code.

Returning to the a-link tags, note that the onmouseover events directly call external.ShowURL with the id of the required program while the onmouseout events call external.HideURL. These JavaScript methods execute methods of the same name in TMyExternal, which in turn show and hide the program's URL in the main window status bar.

The only other item of note in the HTML file is that the <head> section contains an embedded style sheet that styles the <body>, <h1> and <div id="precis"> elements.

Correct location of HTML file

It is important that Article22.html can be found by the compiled program when it is run. By default this is in the same directory as the executable file.

If you want to store the HTML file in a separate location you can either place a copy with the executable file or modify the source code to change the expected location of the file (see Listing 15 line 5).

Data file

The final step is to create the data file, named Article22.dat, that we load in TMyExternal.Create. The file needs to be in the form Key=Value, where Key is the id of one of the listed items of software and Value is the text of a description of the software.

You can use any content you want, providing all the software ids are used as keys.

Avoid HTML reserved characters

When I said "use any content you want", I didn't quite mean it! You need to make sure that the Value items contain only characters that are safe to use in HTML. This is because the case study code doesn't do any checking on the text. Production code should do this for security reasons.

Unsafe characters include > (use the &gt; character entity instead), < (use &lt;), & (use &amp;) and " (use &quot;).

Listing 18 shows the content of the file provided in the case study's source code:

  1codesnip=Offline viewer for routines from CodeSnip database. Displays source of each unit and can test compile with any supported installed version of Delphi and Free Pascal.
  2htmlres=Compiles HTML and associated files into RT_HTML resources in 32 bit resource files suitable for use with Internet Explorer's res:// protocol or with TWebBrowser.
  3unit2ns=Maintains one or more 'mappings' of Delphi Pascal units to the namespaces to which they may belong. Copies the fully qualified namespace / unit name to the clipboard for pasting into Pascal code.
  4bdiff=BDiff computes differences between two binary files and outputs either a human readable file of a binary patch file. BPatch uses the binary patch files produced by BDiff to patch files.
Listing 18

Correct location of data file

It is important that Article22.dat can be found by the compiled program when it is run. By default this is in the same directory as the executable file.

If you want to store the data file in a separate location you can either place a copy with the executable file or modify the source code to change the expected location of the file (see Listing 12, line 14).

Source code

A demo program to accompany this article can be found in the delphidabbler/article-demos Git repository on GitHub.

You can view the code in the article-22 sub-directory. Alternatively download a zip file containing all the demos by going to the repository's landing page and clicking the Clone or download button and selecting Download ZIP.

See the demo's README.md file for details.

This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.

The demo is open source. See the demo's LICENSE.md file for licensing details.

Delph 2007 problems

I've had a report that the demo code does not compile with Delphi 2007 unless you rename the unit Article22_TLB.pas to Article22TLB.pas and change references to it accordingly.

Summary

In this article we have investigated how to call into Delphi code from JavaScript running in a TWebBrowser control.

We began by learning about the browser's external object and looked at how we can extend it using a COM automation object that is registered with the browser control. We reused code from a previous article that enables us to perform this registration.

Next we examined how the methods of the external object are called from JavaScript.

Finally a simple case study program was developed that exercised the code we had developed and demonstrated Delphi code being called from within the web browser control. The source code of the case study was made available for download.

References

Various resources from the former Microsoft® Developer Network Library were used in researching this article. The articles were:

  • "About the Browser - Extending the Dynamic HTML Object Model'"
  • "WebBrowser Customization"
  • "External Object"

Unfortunately, these articles are no longer available at the URLs where they were originally found.

The article also depends heavily on information provided in the companion article: "How to customise the TWebBrowser user interface"

Feedback

I hope you found this article useful.

If you have any observations, comments, or have found any errors there are two places you can report them.

  1. For anything to do with the article content, but not the downloadable demo code, please use this website's Issues page on GitHub. Make sure you mention that the issue relates to "article #22".
  2. For bugs in the demo code see the article-demo project's README.md file for details of how to report them.