3

How to programatically launch IE (iexplore.exe) and navigate in the current running instance (by opening a new tab, or replacing the current URL) Rather than creating a new instance.

I have searched for a command line switch, and tried using InternetExplorer.Application, to no avail.

Here is a pseudo of what I need (IE6-IE9 would be nice):

ShellExecute(Handle, 'open', 'iexplore.exe',
    '"http://google.com" -single_instance',
    nil, SW_RESTORE);

Here is some code to demonstrate my attempts. code in Delphi (partly based on How to get IHTMLDocument2 from a HWND):

implementation

uses ShellApi, ComObj, ActiveX, SHDocVw, MSHTML;        

function GetIEFromHWND(WHandle: HWND; var IE: IWebbrowser2): Boolean;
type
  TObjectFromLResult = function(LRESULT: lResult; const IID: TIID;
    wParam: WPARAM; out pObject): HRESULT; stdcall;
var
  hInst: HMODULE;
  lRes: Cardinal;
  Msg: UINT;
  pDoc: IHTMLDocument2;
  ObjectFromLresult: TObjectFromLresult;
begin
  Result := False;
  hInst := LoadLibrary('oleacc.dll');
  if hInst <> 0 then
  try
    @ObjectFromLresult := GetProcAddress(hInst, 'ObjectFromLresult');
    if @ObjectFromLresult <> nil then
    begin
      Msg := RegisterWindowMessage('WM_HTML_GETOBJECT');
      if SendMessageTimeOut(WHandle, Msg, 0, 0, SMTO_ABORTIFHUNG, 1000, lRes) <> 0 then
        if ObjectFromLresult(lRes, IHTMLDocument2, 0, pDoc) = S_OK then
        begin
          (pDoc.parentWindow as IServiceprovider).QueryService(
            IWebbrowserApp, IWebbrowser2, IE);
          Result := IE <> nil;
        end;
      end;
  finally
    FreeLibrary(hInst);
  end;
end;

function GetActiveIEServerWindow(const Activate: Boolean=True): HWND;
var
  Wnd, WndChild: HWND;
begin
  Result := 0;
  Wnd := FindWindow('IEFrame', nil); // top level IE
  if Wnd <> 0 then
  begin
    WndChild := FindWindowEx(Wnd, 0, 'Shell DocObject View', nil);
    if WndChild <> 0 then
    begin
      WndChild := FindWindowEx(WndChild, 0, 'Internet Explorer_Server', nil);
      if WndChild <> 0 then
      begin
        Result := WndChild;
        if Activate then
        begin
          if IsIconic(Wnd) then
            ShowWindow(Wnd, SW_RESTORE)
          else
            SetForegroundWindow(Wnd);
        end;
      end;
    end;
  end;
end;

// Method 1
procedure TForm1.Button1Click(Sender: TObject);
const
  navOpenInNewTab = $800;
var
  IEServerWnd: HWND;
  IE: IWebBrowser2;
begin
  IEServerWnd := GetActiveIEServerWindow;
  if (IEServerWnd <> 0) and GetIEFromHWnd(IEServerWnd, IE) then
  begin
    // *** this opens the Default browser, e.g Google Chrome 
    // *** if IE is the Default browser, an empty new window is opened.
    OleVariant(IE).Navigate('http://www.yahoo.com', Longint(navOpenInNewTab));
  end
  else
  begin
    ShellExecute(Handle, 'open', 'iexplore.exe',
    '"http://google.com"',
    nil, SW_RESTORE);
  end;
end;

procedure InternetExplorerNavigate(URL: WideString);
const
  navOpenInNewTab = $800;
var
  IE: OleVariant;
begin
  try
    // *** this always fails (security constraints?)
    IE := GetActiveOleObject('InternetExplorer.Application');
  except
    IE := CreateOleObject('InternetExplorer.Application');
  end;
  IE.Visible := True;
  IE.Navigate(URL, Longint(navOpenInNewTab));
end;

// Method 2
procedure TForm1.Button2Click(Sender: TObject);
begin
  InternetExplorerNavigate('http://google.com');
end;
kobik
  • 21,001
  • 4
  • 61
  • 121
  • Shouldn't this be down to the users configuration? If I've set it to open in new windows, then I expect new windows. If I set it to load all tabs in one window, I'd expect that too. – Deanna Apr 16 '12 at 14:20

1 Answers1

4

Your method '1' actually works. The problem, at least here, is that 'Shell DocObject View' window is not an immediate child of the top-level ie window. Here with IE8, 'Internet Explorer_Server' window is a child of 'Shell DocObject View', which is a child of 'TabWindowClass', which is a child of 'Frame Tab'. If you can confirm 'FindWindowEx' returns 0 in method 1, that's why it fails. The below is the code modified to use EnumChildWindows:

function EnumChilds(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
const
  Server = 'Internet Explorer_Server';
var
  ClassName: array[0..24] of Char;
begin
  GetClassName(hwnd, ClassName, Length(ClassName));
  Result := ClassName <> Server;
  if not Result then
    PLongWord(lParam)^ := hwnd;
end;

function GetActiveIEServerWindow(const Activate: Boolean=True): HWND;
var
  Wnd, WndChild: HWND;
begin
  Result := 0;
  Wnd := FindWindow('IEFrame', nil); // top level IE
  if Wnd <> 0 then
  begin

//    WndChild := FindWindowEx(Wnd, 0, 'Shell DocObject View', nil);
//    if WndChild <> 0 then
//    begin
//      WndChild := FindWindowEx(WndChild, 0, 'Internet Explorer_Server', nil);

    WndChild := 0;
    EnumChildWindows(Wnd, @EnumChilds, LongWord(@WndChild));

    if WndChild <> 0 then
    begin
      Result := WndChild;
      if Activate then
      begin
        if IsIconic(Wnd) then
          ShowWindow(Wnd, SW_RESTORE)
        else
          SetForegroundWindow(Wnd);
      end;
    end;
//    end;
  end;
end;

As for method '2', I've seen a few links on the web that state IE does not support returning a reference for its active object, but I don't have any official reference for that.

roalz
  • 2,699
  • 3
  • 25
  • 42
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • Hi, and thanks for the answer. I'm testing with IE6/XP. so the child window might be different here (It is not the issue). what is your default browser BTW? – kobik Apr 14 '12 at 15:07
  • But ie6 does not have tabs, $800 makes no sense to ie6. I'm using ıe8 as mentioned in the answer. – Sertac Akyuz Apr 14 '12 at 15:44
  • I know, I was hoping IE6 will ignore that (I have also tried to pass 0 as flag), and since I acquire the active `IHTMLDocument2` I should be able to `Navigate` no?. – kobik Apr 14 '12 at 17:20
  • 1
    @kobik - Seems reasonable. I tested the code as I posted in a WM with XPSP2 and Ie6.0.2900.2180(SP2), the existing ie window navigated to the link without launching a new instance, and the flag caused no trouble at all. You're right about the window hieararchy BTW. – Sertac Akyuz Apr 15 '12 at 02:31
  • Well, I juts realized my IE6 installation is messed up. Thanks for the confirmation, and `EnumChildWindows` is really the way to go. +1 and accepted. – kobik Apr 15 '12 at 07:06
  • BTW, I think that `WndChild` could be declared locally inside the function and pass it as reference in the `EnumChildWindows` LPARAM. – kobik Apr 15 '12 at 07:19
  • @kobik - Thanks!.. For some reason the callback cannot be nested with the 64-bit Delphi compiler... – Sertac Akyuz Apr 15 '12 at 14:07
  • Thanks @Sertac. Looks perfect now :) – kobik Apr 15 '12 at 14:51
  • Comment to @self (followup): [Why can't I nest winapi callbacks as local functions in 64 bit Delphi compiler](http://stackoverflow.com/questions/10162749/why-cant-i-nest-winapi-callbacks-as-local-functions-in-64-bit-delphi-compiler) – kobik Apr 16 '12 at 12:03