(* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is GeckoComponents for Delphi.
 *
 * The Initial Developer of the Original Code is Takanori Ito.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** *)
unit GeckoChromeWindow;

{$MACRO on}

{$IFDEF Windows}
  {$DEFINE extdecl:=stdcall}
{$ELSE Windows}
  {$DEFINE extdecl:=cdecl}
{$ENDIF}

{$IFNDEF FPC_HAS_CONSTREF}
  {$DEFINE constref:=const}
{$ENDIF}

{$IFDEF LCLCocoa}
  {$MODESWITCH ObjectiveC1}
{$ENDIF}

interface

uses
  {$IFNDEF LCL} Windows, Messages, {$ELSE} LclIntf, LResources, {$ENDIF}
  SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, CallbackInterfaces, nsXPCOM, nsTypes, nsXPCOM_std19
  {$IFDEF LCLCarbon}, CarbonPrivate {$ENDIF}
  {$IFDEF LCLGtk2}, gtk2 {$ENDIF}
  {$IFDEF LCLCocoa}, CocoaAll, CocoaUtils, CocoaPrivate {$ENDIF};

type
  //In all currently supported platforms the native window handle is a pointer
  //size handle. In Linux64 THANDLE can not be used because by default it is 32
  //bits due file descriptors which are 32 bits even in 64 bit platform.
  //Win32   WindowHandle 32 bits THANDLE 32 bits
  //Win64   WindowHandle 64 bits THANDLE 64 bits
  //Linux32 WindowHandle 32 bits THANDLE 32 bits
  //Linux64 WindowHandle 64 bits THANDLE 32 bits
  nativeWindow = PtrUInt;

  TGeckoChromeForm = class(TForm,
                           IGeckoCreateWindowTarget,
                           nsIWebBrowserChrome,
                           nsIEmbeddingSiteWindow,
                           nsIWebProgressListener,
                           nsIInterfaceRequestor_std19,
                           nsIWeakReference,
                           nsISupportsWeakReference)
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormResize(Sender: TObject);
  private
    { Private 錾 }
    FWebBrowser: nsIWebBrowser;
    FChromeFlags: Longword;

    // nsIWebBrowserChrome
    procedure SetStatus(statusType: PRUint32; const status: PWideChar); safecall;
    function GetWebBrowser(): nsIWebBrowser; safecall;
    procedure SetWebBrowser(aWebBrowser: nsIWebBrowser); safecall;
    function GetChromeFlags: PRUint32; safecall;
    procedure SetChromeFlags(aChromeFlags: PRUint32); safecall;
    procedure DestroyBrowserWindow(); safecall;
    procedure SizeBrowserTo(aCX: PRInt32; aCY: PRInt32); safecall;
    procedure ShowAsModal(); safecall;
    function IsWindowModal(): PRBool; safecall;
    procedure ExitModalEventLoop(aStatus: nsresult); safecall;
    // nsIEmbeddingSiteWindow
    procedure SetDimensions(flags: PRUint32; x, y, cx, cy: PRInt32); safecall;
    procedure GetDimensions(flags: Longword; out x, y, cx, cy: PRInt32); safecall;
    procedure SetFocus; reintroduce; safecall;
    function GetVisibility(): PRBool; safecall;
    procedure SetVisibility(Value: PRBool); safecall;
    function GetTitle(): PWideChar; safecall;
    procedure SetTitle(const Value: PWideChar); safecall;
    function GetSiteWindow: Pointer; safecall;
    // nsIWebProgressListener
    procedure OnStateChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; aStateFlags: PRUint32; aStatus: nsresult); safecall;
    procedure OnProgressChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; aCurSelfProgress: PRInt32; aMaxSelfProgress: PRInt32; aCurTotalProgress: PRInt32; aMaxTotalProgress: PRInt32); safecall;
    procedure OnLocationChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; location: nsIURI); safecall;
    procedure OnStatusChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; aStatus: nsresult; const aMessage: PWideChar); safecall;
    procedure OnSecurityChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; state: PRUint32); safecall;
    // nsIInterfaceRequestor
    function NS_GetInterface(constref uuid: TGUID; out Intf): nsresult; extdecl;
    function nsIInterfaceRequestor_std19.GetInterface = NS_GetInterface;
    // for nsIWeakReference
    procedure QueryReferent(constref IID: TGUID; out Obj); safecall;
    // for nsISupportsWeakReference
    function GetWeakReference(): nsIWeakReference; safecall;

    function GetNativeWindow : nativeWindow;  //FPC port: added this.
    procedure InitWebBrowser;
    procedure UpdateChrome;
    procedure ContentFinishedLoading;
  public
    { Public 錾 }
    function SafeCallException(Obj: TObject; Addr: Pointer): HResult; override;

    constructor CreateWithChromeFlags(AOwner: TComponent; aChromeFlags: Longword);

    // IGeckoCreateWindowTarget
    function DoCreateChromeWindow(chromeFlags: Longword): nsIWebBrowserChrome;
    function GetWebBrowserChrome: nsIWebBrowserChrome;
    property WebBrowser : nsIWebBrowser read FWebBrowser; //FPC port: added this.
  end;

var
  GeckoChromeForm: TGeckoChromeForm;

implementation

{$IFNDEF LCL}
{$R *.dfm}
{$ENDIF}

uses
  nsXPCOMGlue, nsError, BrowserSupports;

{$PUSH}
{$HINTS OFF}
procedure UseParameter(var X);
begin
end;
{$POP}

constructor TGeckoChromeForm.CreateWithChromeFlags(AOwner: TComponent; AChromeFlags: Longword);
begin
  inherited Create(AOwner);
  FChromeFlags := aChromeFlags;
  UpdateChrome;
end;

procedure TGeckoChromeForm.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  Action := caFree;
end;

function TGeckoChromeForm.GetNativeWindow : nativeWindow;
{$IFDEF LCLCocoa}
var
  ARect : NSRect;
  AView : NSView;
{$ENDIF}
begin
   {$IFDEF MSWINDOWS}Result := Handle;{$ENDIF}

   {$IFDEF LCLCarbon}Result := THANDLE(TCarbonWindow(Handle).Window);{$ENDIF}
    //Carbon doesn't work but leave in so package compiles in Carbon IDE.

//   {$IFDEF LCLCocoa}Result := Pointer(TCocoaForm(Handle).MainWindowView.superview);{$ENDIF}
      //Old PasCocoa-based widgetset.

//NSLog(NSStringUtf8(FloatToStr(NSView(TCocoaWindow(Handle).contentView).frame.size.width)));
   {$IFDEF LCLCocoa}Result := THANDLE(TCocoaWindow(Handle).contentView);{$ENDIF}
    //New ObjC-based Cocoa widgetset.

(*
    //Does adding a view work better than using window's view (below)? No, it doesn't.
   {$IFDEF LCLCocoa}
    ARect := NSView(TCocoaWindow(Handle).contentView).visibleRect;
    ARect.size.width := ARect.size.width - 30;
    ARect.size.height := ARect.size. height - 30;
    ARect.origin.x := 15;
    ARect.origin.y := 15;
    AView := NSView.alloc.initWithFrame(ARect);
    NSView(TCocoaWindow(Handle).contentView).addSubView(AView);
    Result := THANDLE(AView);
   {$ENDIF}
*)

   {$IFDEF LCLGtk}Result := Handle;{$ENDIF}  //Is Handle same as GTK Window?

   {$IFDEF LCLGtk2}
   Result := nativeWindow(PGtkWindow(GeckoChromeForm.Handle)^.bin.child);
   {$ENDIF}  //Is Handle same as GTK Window?
end;

procedure TGeckoChromeForm.InitWebBrowser;
var
  base: nsIBaseWindow;
begin
  NS_CreateInstance(NS_WEBBROWSER_CID, nsIWebBrowser, FWebBrowser);

  FWebBrowser.ContainerWindow := Self;

  base := FWebBrowser as nsIBaseWindow;
  base.InitWindow(GetNativeWindow, nil, 0, 0, ClientWidth, ClientHeight);
  base.Create;
  FWebBrowser.AddWebBrowserListener(Self, nsIWebProgressListener);
  base.SetVisibility(True);
end;

procedure TGeckoChromeForm.UpdateChrome;
begin
  {if (FChromeFlags and CHROME_WINDOW_BORDERS)<>0 then
    if (FChromeFlags and CHROME_WINDOW_RESIZE)<>0 then
      BorderStyle := bsSizeable
    else
      if (FChromeFlags and CHROME_OPENAS_DIALOG)<>0 then
        BorderStyle := bsDialog
      else
        BorderStyle := bsSingle
  else
    BorderStyle := bsNone;}
  BorderStyle := bsSizeable;

  {
  if (FChromeFlags and CHROME_WINDOW_CLOSE)<>0 then
    BorderIcons := BorderIcons + [biClose]
  else
    BorderIcons := BorderIcons - [biClose];
  }

  if (FChromeFlags and CHROME_SCROLLBARS)<>0 then
    AutoScroll := True
  else
    AutoScroll := False;

  {
  if (FChromeFlags and CHROME_TITLEBAR)<>0 then
    BorderIcons := BorderIcons + [biSystemMenu]
  else
    BorderIcons := BorderIcons - [biSystemMenu];
  }

end;

function TGeckoChromeForm.DoCreateChromeWindow(chromeFlags: Longword): nsIWebBrowserChrome;
begin
  UseParameter(chromeFlags);
  Result := nil;
end;

function TGeckoChromeForm.GetWebBrowserChrome: nsIWebBrowserChrome;
begin
  Result := Self;
end;

procedure TGeckoChromeForm.SetStatus(statusType: Longword; const status: PWideChar);
begin
  UseParameter(statusType);
end;

function TGeckoChromeForm.GetWebBrowser: nsIWebBrowser;
begin
  Result := FWebBrowser as nsIWebBrowser;
end;

procedure TGeckoChromeForm.SetWebBrowser(aWebBrowser: nsIWebBrowser);
begin
  UseParameter(aWebBrowser);
end;

function TGeckoChromeForm.GetChromeFlags: PRUint32;
begin
  Result := FChromeFlags;
end;

procedure TGeckoChromeForm.SetChromeFlags(aChromeFlags: Longword);
begin
  FChromeFlags := aChromeFlags;
  UpdateChrome;
end;

procedure TGeckoChromeForm.DestroyBrowserWindow;
begin
  Close;
end;

procedure TGeckoChromeForm.SizeBrowserTo(aCX, aCY: Integer);
var
  dx, dy: Integer;
begin
  dx := Width - ClientWidth;
  dy := Height - ClientHeight;
  SetBounds(Left, Top, aCX+dx, aCY+dy);
end;

procedure TGeckoChromeForm.ShowAsModal;
begin
  Visible := False;
  ShowModal;
end;

function TGeckoChromeForm.IsWindowModal: PRBool;
begin
  Result := False;
end;

procedure TGeckoChromeForm.ExitModalEventLoop(aStatus: nsresult); safecall;
begin
  UseParameter(aStatus);
  ModalResult := 1;
end;

procedure TGeckoChromeForm.SetDimensions(flags: Longword; x, y, cx, cy: Longint);
const
  FLAGS_POSITION   = NS_IEMBEDDINGSITEWINDOW_DIM_FLAGS_POSITION;
  FLAGS_SIZE_INNER = ns_IEmbeddingSiteWindow_DIM_FLAGS_SIZE_INNER;
  FLAGS_SIZE_OUTER = ns_IEmbeddingSiteWindow_DIM_FLAGS_SIZE_OUTER;
var
  dx, dy: Integer;
begin
  dx := Width - ClientWidth;
  dy := Height - ClientHeight;

  if (flags and FLAGS_POSITION)<>0 then
  begin
    if (flags and FLAGS_SIZE_INNER)<>0 then
    begin
      Bounds(x, y, cx+dx, cy+dy);
    end else
    if (flags and FLAGS_SIZE_OUTER)<>0 then
    begin
      Bounds(x, y, cx, cy);
    end else
    begin
      Bounds(x, y, Width, Height);
    end;
  end else
  if (flags and FLAGS_SIZE_INNER)<>0 then
  begin
    Bounds(Left, Top, cx+dx, cy+dy);
  end else
  if (flags and FLAGS_SIZE_OUTER)<>0 then
  begin
    Bounds(Left, Top, cx, cy);
  end;
end;

procedure TGeckoChromeForm.GetDimensions(flags: Longword; out x, y, cx, cy: Longint);
const
  FLAGS_POSITION   = ns_IEmbeddingSiteWindow_DIM_FLAGS_POSITION;
  FLAGS_SIZE_INNER = ns_IEmbeddingSiteWindow_DIM_FLAGS_SIZE_INNER;
  FLAGS_SIZE_OUTER = ns_IEmbeddingSiteWindow_DIM_FLAGS_SIZE_OUTER;
begin
  if (flags and FLAGS_POSITION)<>0 then
  begin
    x := Left;
    y := Top;
  end;

  if (flags and FLAGS_SIZE_INNER)<>0 then
  begin
    cx := ClientWidth;
    cy := ClientHeight;
  end else
  if (flags and FLAGS_SIZE_OUTER)<>0 then
  begin
    cx := Width;
    cy := Height;
  end;
end;

procedure TGeckoChromeForm.SetFocus();
begin
end;

function TGeckoChromeForm.GetVisibility: PRBool;
begin
  Result := True;
end;

procedure TGeckoChromeForm.SetVisibility(Value: LongBool);
begin
  UseParameter(Value);
  Visible := Value;
end;

function TGeckoChromeForm.GetTitle: PWideChar;
begin
  Result := nil;
end;

procedure TGeckoChromeForm.SetTitle(const Value: PWideChar);
begin
  Caption := WideString(Value);
end;

function TGeckoChromeForm.GetSiteWindow: Pointer;
begin
//Known "not safe" conversion.
{$PUSH}
{$HINTS OFF}
  Result := Pointer(GetNativeWindow);
{$POP}
end;

procedure TGeckoChromeForm.OnStateChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; aStateFlags: PRUint32; aStatus: nsresult);
begin
  UseParameter(aWebProgress);
  UseParameter(aRequest);
  UseParameter(aStatus);
  if ((aStateFlags and NS_IWEBPROGRESSLISTENER_STATE_STOP)<>0) and
     ((aStateFlags and NS_IWEBPROGRESSLISTENER_STATE_IS_DOCUMENT)<>0) then
  begin
    ContentFinishedLoading();
  end;
end;

procedure TGeckoChromeForm.OnProgressChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; aCurSelfProgress: PRInt32; aMaxSelfProgress: PRInt32; aCurTotalProgress: PRInt32; aMaxTotalProgress: PRInt32);
begin
  UseParameter(aWebProgress);
  UseParameter(aRequest);
  UseParameter(aCurSelfProgress);
  UseParameter(aMaxSelfProgress);
  UseParameter(aCurTotalProgress);
  UseParameter(aMaxTotalProgress);
end;

procedure TGeckoChromeForm.OnLocationChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; location: nsIURI);
begin
  UseParameter(aWebProgress);
  UseParameter(aRequest);
  UseParameter(location);
end;

procedure TGeckoChromeForm.OnStatusChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; aStatus: nsresult; const aMessage: PWideChar);
begin
  UseParameter(aWebProgress);
  UseParameter(aRequest);
  UseParameter(aStatus);
end;

procedure TGeckoChromeForm.OnSecurityChange(aWebProgress: nsIWebProgress; aRequest: nsIRequest; state: PRUint32);
begin
  UseParameter(aWebProgress);
  UseParameter(aRequest);
  UseParameter(state);
end;

function TGeckoChromeForm.NS_GetInterface(constref uuid: TGUID; out Intf): nsresult;
var
  domwin: nsIDOMWindow;
begin
  if IsEqualGUID(uuid, nsIDOMWindow) then
  begin
    if Assigned(FWebBrowser) then
    begin
      domwin := FWebBrowser.ContentDOMWindow;
      Result := domwin.QueryInterface(uuid, Intf);
    end else
      Result := NS_ERROR_NOT_INITIALIZED;
  end else
  begin
// FPC port: Result is PRUInt32, but QueryInterface returns Longint,
//  so cast to nsresult to prevent range check error.
    try
    Result := nsresult(QueryInterface(uuid, Intf));
    except
      Result:=0;
      Integer(Intf):=0;
    end;
  end;
end;

procedure TGeckoChromeForm.QueryReferent(constref IID: TGUID; out Obj);
var
  rv: nsresult;
begin
  rv := QueryInterface(IID, Obj);
  if NS_FAILED(rv) then
    raise EIntfCastError.Create('QueryReferent');
end;

function TGeckoChromeForm.GetWeakReference: nsIWeakReference;
begin
  Result := Self as nsIWeakReference;
end;

procedure TGeckoChromeForm.FormCreate(Sender: TObject);
begin
  InitWebBrowser;
end;

procedure TGeckoChromeForm.FormResize(Sender: TObject);
var
  baseWin: nsIBaseWindow;
begin
  baseWin:=FWebBrowser as nsIBaseWindow;
  baseWin.SetPositionAndSize(0, 0, ClientWidth, ClientHeight, True);
  baseWin.SetVisibility(True);
end;

procedure TGeckoChromeForm.ContentFinishedLoading;
var
  contentWin: nsIDOMWindow;
  baseWin: nsIBaseWindow;
begin
  contentWin := FWebBrowser.ContentDOMWindow;
  try
    //Will try to resize the form to the size of the HTML page, but if the HTML
    //does not have a width specified (UNRESTRICTED) it will raise an exception
    //and badly resize the HTML content.
    contentWin.SizeToContent;
  except
    //Workaround
    baseWin:=FWebBrowser as nsIBaseWindow;
    //Forces reflow...
    baseWin.SetPositionAndSize(0,0,ClientWidth, ClientHeight+1, false);
    baseWin.SetPositionAndSize(0,0,ClientWidth, ClientHeight, true);
  end;
  Visible:=true;
end;

{$IFDEF LCL}
const
  E_FAIL = HRESULT($80004005);
{$ENDIF}

function TGeckoChromeForm.SafeCallException(Obj: TObject; Addr: Pointer): HResult;
begin
  UseParameter(Addr);
  if Obj is EIntfCastError then
    Result := E_NOINTERFACE
  else
    Result := E_FAIL;
end;

initialization
{$IFDEF LCL}
{$I GeckoChromeWindow.lrs}
{$ENDIF}

end.