Search code examples
delphidevtoolstedgebrowser

TEdgeBrowser for Delphi 10.4.1 and later: How to trap F12 (OpenDevToolsWindow)?


I am using TEdgeBrowser in Delphi 10.4.1. It works very well.

The only nagging issue is, when TEdgeBrowser has focus, it grabs F12 and CTRL+SHIFT+C and presents the OpenDevToolsWindow. This is great, except I want to change some of the topmost properties of the Form before it loads (otherwise, the DevTools window will be behind the MainForm).

Is there any way to trap F12 from the parent MainForm? I have tried Application and MainForm key captures, but both fail to capture the TEdgeBrowser key events (when TEdgeBrowser has focus).

procedure TMainForm.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
  case Msg.Message of
    WM_KEYDOWN, WM_KEYUP:
      begin
      if Msg.WParam = VK_F11 then
         begin
         SetStatusLog(EID_KEYPRESS,'F11');
         Handled := true;
         end
      else if Msg.WParam = VK_F12 then
         begin
{ do something here and consider F12 handled, preventing F12 from going to TEdgeBrowser???}
         SetStatusLog(EID_KEYPRESS,'F12');
         Handled := true;
         end;
      end;
  end;
end;

Is there another way to tackle this?

Additionally, can I launch the OpenDevToolsWindow programmably?


Solution

  • I have used two ways to handle this. (1) You can call Set_AreBrowserAcceleratorKeysEnabled(0) to disable the browser's accelerator keys (but that might include disabling more than you want, and that is not really what you asked.) And it requires some additional work to get access to this interface as it is not included in the current TEdgeBrowser. Also, I read somewhere that the AcceleratorKey event still fires, even if you disable them in the EdgeBrowser so if you use that approach, you can process them. (2) Use the AddScriptToExecuteOnDocumentCreated to inject some Javascript that can prevent the default behavior (if desired) and send your app a message (which you'll pick up on OnWebMessageReceived) so you can process the event.

    Option 1:

    You'll need to define the following to get access to the interfaces you need as they were introduced after what TEdgeBrowser has:

    const
      IID_ICoreWebview2Settings2: TGUID = '{EE9A0F68-F46C-4E32-AC23-EF8CAC224D2A}'; //Introduced: SDK  1.0.864.35
      IID_ICoreWebview2Settings3: TGUID = '{FDB5AB74-AF33-4854-84F0-0A631DEB5EBA}'; //Introduced: SDK  1.0.864.35
    
    type
      ICoreWebView2Settings2 = interface(ICoreWebView2Settings)
        ['{EE9A0F68-F46C-4E32-AC23-EF8CAC224D2A}']
        function Get_UserAgent(out UserAgent: PWideChar): HResult; stdcall;
        function Set_UserAgent(UserAgent: PWideChar): HResult; stdcall;
      end;
    
      ICoreWebView2Settings3 = interface(ICoreWebView2Settings2)
        ['{FDB5AB74-AF33-4854-84F0-0A631DEB5EBA}']
        function Get_AreBrowserAcceleratorKeysEnabled(out AreBrowserAcceleratorKeysEnabled: Integer): HResult; stdcall;
        function Set_AreBrowserAcceleratorKeysEnabled(AreBrowserAcceleratorKeysEnabled: Integer): HResult; stdcall;
      end;
    

    Then in your OnCreateWebViewCompleted event you can do

    var
      Settings3: ICoreWebView2Settings3;
      HR: HRESULT;
    begin
      Sender.SettingsInterface.QueryInterface(IID_ICoreWebView2Settings3, Settings3);
      if Assigned(Settings3) then
      begin
        HR := Settings3.Set_AreBrowserAcceleratorKeysEnabled(0);
        if not SUCCEEDED(HR) then
          {Do something - Set_AreBrowserAcceleratorKeysEnabled failed};
      end
      else
        {Do something - ICoreWebView2Settings3 interface not found.};
      end;  
    

    Option 2:

    In your OnCreateWebViewCompleted event you can do the following

    const
      JavaScript =
        '  document.addEventListener(''keydown'', function(event){' + sLineBreak +
        '    if (event.code == "F12") {' + sLineBreak +
        '      Result = "#KEY_EVENT#" + event.code;' + sLineBreak +
        '      event.preventDefault();' + sLineBreak +
        '      window.chrome.webview.postMessage(Result);' + sLineBreak +
        '    };' + sLineBreak +
        '  });'; 
    
    {...}                                                                       
    begin
      Sender.DefaultInterface.AddScriptToExecuteOnDocumentCreated(JavaScript,
        Callback<HResult, PChar>.CreateAs<ICoreWebView2AddScriptToExecuteOnDocumentCreatedCompletedHandler>(
        function(ErrorCode: HResult; Id: PWideChar): HResult stdcall
        begin
          if not(Succeeded(ErrorCode)) then
            {Do something if this function failed.  It gets called later when a document id created.  Or you can pass nil for the Callback};
          Result := 1;
        end));
    

    Note, in Option 2, see TEdgeBrowser code as example for defining the Callback. It is defined in the implementation part of TEdgeBrowser. I just replicated it in my own form's unit implementation section.