I've implemented a GetResourceHandler
call in Delphi 2007 using the latest Delphi Chromium Embedded wrapper (which uses libcef 3.1750.1738, I believe compiled by the DCEF3 creator). I based my handler on some python code posted on the magpcss site (with some changes).
All is working well, except for a couple of things. First off, "blob:" urls don't work and I'm scratching my head as to how to get them to. I'm not even that knowledgeable about them in general so that's part of the problem.
Second, some cross-origin stuff pops up when using my resource handler that otherwise doesn't. This is in my tests against https://maps.google.com/. I get:
GET blob:https%3A//map.google.com/93e08d0a-b8b3-4c60-8a82-71d424b0893c 404 (Not Found) rs=ACT90oHHUBc59MzTbg1AU48l7w-F8f1yuA:1107
GET blob:https%3A//map.google.com/4c89a387-5958-4c19-ae9c-0640007db009 404 (Not Found) rs=ACT90oHHUBc59MzTbg1AU48l7w-F8f1yuA:1107
GET blob:https%3A//map.google.com/bd839949-a77b-43a1-9f47-1cb841f858a9 404 (Not Found) /maps/_/js/k=maps.m.en.oD0-LnUwxmc.O/m=sy437,sy444,sy446,sy486,sy494,wrc,sy438,sy436,vw,sy139,sy220…:134
XMLHttpRequest cannot load https://mt0.google.com/vt?pb=!1m8!4m7!2u15!5m2!1x450831026!2x3361961734!6m2!1x451029165!2x3362284457!2m1!1e0!3m1!5e1105!4e5!18m1!1b1. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://map.google.com' is therefore not allowed access. /maps/@45.0930104,-93.28442,15z:1
XMLHttpRequest cannot load https://mt0.google.com/vt/pb=!1m8!3m7!1m2!1u2020096!2u3014144!2m2!1u1024!2u768!3i15!2m3!1e0!2sm!3i290!3m2!2sen!5e1105!4e4!11m2!1e2!2b1!20m1!1b1. No 'Access-Control-Allow- Origin' header is present on the requested resource. Origin 'https://map.google.com' is therefore not allowed access. /maps/@45.0930104,-93.28442,15z:1
Yet neither of those problems happen when omitting my resource handler and using CEF without it. I've found I can get around the CORS problems by disabling web security. That hardly seems ideal and, again, isn't needed when using CEF without my resource handler.
Here's my resource handler code:
unit WebInterceptHandler;
interface
uses
ceflib, Classes;
type
TWebInterceptHandler = class(TCefResourceHandlerOwn)
protected
FDataStream: TMemoryStream;
FResponseHeadersReadyCallback: ICefCallback;
FOffsetRead: NativeUInt;
FResponse: ICefResponse;
function ProcessRequest(const Request: ICefRequest;
const Callback: ICefCallback): Boolean; override;
procedure GetResponseHeaders(const Response: ICefResponse;
out ResponseLength: Int64; out RedirectUrl: ustring); override;
function ReadResponse(const DataOut: Pointer; BytesToRead: Integer;
var BytesRead: Integer; const Callback: ICefCallback): Boolean; override;
public
constructor Create(const Browser: ICefBrowser; const Frame: ICefFrame;
const SchemeName: ustring; const Request: ICefRequest); override;
destructor Destroy; override;
end;
implementation
uses windows, sysutils;
type
TWebInterceptHandlerClient = class(TCefUrlRequestClientOwn)
private
FResourceHandler: TWebInterceptHandler;
protected
procedure OnDownloadData(const Request: ICefUrlRequest; Data: Pointer;
DataLength: NativeUInt); override;
procedure OnRequestComplete(const Request: ICefUrlRequest); override;
end;
{ TWebInterceptHandlerClient }
procedure TWebInterceptHandlerClient.OnDownloadData(const Request: ICefUrlRequest;
Data: Pointer; DataLength: NativeUInt);
begin
inherited;
FResourceHandler.FDataStream.Write(Data^, DataLength);
//OutputDebugStringW(PWideChar(Request.GetRequest.Url));
end;
procedure TWebInterceptHandlerClient.OnRequestComplete(const Request: ICefUrlRequest);
begin
inherited;
FResourceHandler.FResponse := Request.GetResponse;
if FResourceHandler.FResponseHeadersReadyCallback <> nil then
FResourceHandler.FResponseHeadersReadyCallback.Cont;
end;
{ TWebInterceptHandler }
constructor TWebInterceptHandler.Create(const Browser: ICefBrowser;
const Frame: ICefFrame; const SchemeName: ustring;
const Request: ICefRequest);
begin
inherited;
FDataStream := TMemoryStream.Create;
end;
//function HTTPDecode(const AStr: ustring): rbstring;
//var
// Sp, Rp, Cp: PAnsiChar;
// src: rbstring;
//begin
// src := rbstring(AStr);
// SetLength(Result, Length(src));
// Sp := PAnsiChar(src);
// Rp := PAnsiChar(Result);
// while Sp^ <> #0 do
// begin
// case Sp^ of
// '+': Rp^ := ' ';
// '%': begin
// Inc(Sp);
// if Sp^ = '%' then
// Rp^ := '%'
// else
// begin
// Cp := Sp;
// Inc(Sp);
// if (Cp^ <> #0) and (Sp^ <> #0) then
// Rp^ := AnsiChar(StrToInt('$' + Char(Cp^) + Char(Sp^)))
// else
// begin
// Result := '';
// Exit;
// end;
// end;
// end;
// else
// Rp^ := Sp^;
// end;
// Inc(Rp);
// Inc(Sp);
// end;
// SetLength(Result, Rp - PAnsiChar(Result));
//end;
destructor TWebInterceptHandler.Destroy;
begin
FDataStream.Free;
inherited;
end;
function TWebInterceptHandler.ProcessRequest(const Request: ICefRequest;
const Callback: ICefCallback): Boolean;
var
wrc: TWebInterceptHandlerClient;
// url: ustring;
// i: Integer;
// headerMap: ICefStringMultimap;
begin
//headerMap := TCefStringMultimapOwn.Create;
//OutputDebugStringW(PWideChar(Request.Url));
//Request.GetHeaderMap(headerMap);
//if headerMap.Size <> 0 then
// for i := 0 to headerMap.Size - 1 do
// OutputDebugStringW(PWideChar(headerMap.Key[i] + ': ' + headerMap.Value[i]));
//if Pos('blob:', WideLowerCase(Request.Url)) = 1 then
// request.Url := HTTPDecode(Copy(Request.Url, 1, Length(Request.Url) - Length('blob:')));
FOffsetRead := 0;
FResponseHeadersReadyCallback := Callback;
wrc := TWebInterceptHandlerClient.Create;
wrc.FResourceHandler := Self;
TCefUrlRequestRef.New(request, wrc);
Result := True;
end;
procedure TWebInterceptHandler.GetResponseHeaders(const Response: ICefResponse;
out ResponseLength: Int64; out RedirectUrl: ustring);
var
headerMap: ICefStringMultimap;
begin
headerMap := TCefStringMultimapOwn.Create;
Response.Status := FResponse.Status;
Response.StatusText := FResponse.StatusText;
Response.MimeType := FResponse.MimeType;
FResponse.GetHeaderMap(headerMap);
if headerMap.Size <> 0 then
FResponse.SetHeaderMap(headerMap);
ResponseLength := FDataStream.Size;
end;
function TWebInterceptHandler.ReadResponse(const DataOut: Pointer; BytesToRead:
Integer; var BytesRead: Integer; const Callback: ICefCallback): Boolean;
begin
if FOffsetRead < FDataStream.Size then
begin
BytesRead := BytesToRead;
Move(Pointer(NativeUInt(FDataStream.Memory) + FOffsetRead)^, DataOut^,
BytesRead);
Inc(FOffsetRead, BytesRead);
Result := True;
end
else
Result := False;
end;
end.
Which I'm calling from a test app:
object MainForm: TMainForm
Left = 0
Top = 0
Caption = 'MainForm'
ClientHeight = 716
ClientWidth = 752
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object URLBox: TEdit
Left = 0
Top = 0
Width = 752
Height = 21
Align = alTop
TabOrder = 0
Text = 'https://map.google.com/'
OnKeyPress = URLBoxKeyPress
end
object Panel1: TPanel
Left = 0
Top = 21
Width = 752
Height = 41
Align = alTop
BevelOuter = bvNone
TabOrder = 1
ExplicitLeft = 296
ExplicitTop = 360
ExplicitWidth = 185
object Button1: TButton
Left = 8
Top = 8
Width = 120
Height = 25
Caption = 'Show Dev Tools'
TabOrder = 0
OnClick = Button1Click
end
object UseResourceHandlerBox: TCheckBox
Left = 152
Top = 12
Width = 233
Height = 17
Caption = 'Use Resource Handler'
TabOrder = 1
end
end
end
unit MainUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, cefgui, ceflib, cefvcl, StdCtrls, ExtCtrls;
type
TMainForm = class(TForm)
URLBox: TEdit;
Panel1: TPanel;
Button1: TButton;
UseResourceHandlerBox: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure URLBoxKeyPress(Sender: TObject; var Key: Char);
procedure Button1Click(Sender: TObject);
private
browser: TChromium;
procedure BrowserGetResourceHandler(Sender: TObject;
const Browser: ICefBrowser; const Frame: ICefFrame;
const Request: ICefRequest; out Result: ICefResourceHandler);
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
uses
WebInterceptHandler;
procedure TMainForm.URLBoxKeyPress(Sender: TObject; var Key: Char);
begin
if Key = #13 then
begin
if UseResourceHandlerBox.Checked then
browser.OnGetResourceHandler := BrowserGetResourceHandler
else
browser.OnGetResourceHandler := nil;
browser.Load(URLBox.Text);
end;
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
browser.ShowDevTools;
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
browser := TChromium.Create(Self);
browser.Align := alClient;
browser.Parent := Self;
//browser.Options.WebSecurity := STATE_DISABLED;
end;
procedure TMainForm.BrowserGetResourceHandler(Sender: TObject; const Browser:
ICefBrowser; const Frame: ICefFrame; const Request: ICefRequest;
out Result: ICefResourceHandler);
begin
Result := TWebInterceptHandler.Create(Browser, Frame, 'webintercept', Request);
end;
end.
program DCEF3WebIntercept;
uses
ceflib,
Forms,
MainUnit in 'MainUnit.pas' {MainForm},
WebInterceptHandler in 'WebInterceptHandler.pas';
{$R *.res}
begin
CefCache := 'cache';
CefSingleProcess := False;
if not CefLoadLibDefault then
Exit;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
Along with any help on those problems, any suggestions for improvements to this code are welcome.
Also, in case anyone is wondering, I'm planning on later modifying this code so I can alter the response. Right now it's just a proof-of-concept of GetResourceHandler.
A blob url is a special url that refers to data that your browser currently has in memory, for the current page. Looks to me that in BrowserGetResourceHandler() you should detect if url starts with "blob:" and return NULL in such case and let Chromium handle fetching that url internally. If it still doesn't work, it may be a limitation in CEF and bug should be reported.
The CORS security problem seems to be caused by missing headers in request/response. Do some debugging: