2015年1月6日 星期二

Get HTML/JSON from TWebBrowser FireMonkey (Delphi XE7)

Starting


FireMonkey starts from Delphi XE2, this new framework provides many powerful components and runtime library, and the most important point is, most of the components can be used cross-platform.

XE2 -> iOS fundmental features ready.
XE3-XE5 -> Android features were appended.
XE6 -> Almost features were ready, but minor performance issue remained.
XE7 -> Everything is ready, we still need some completion for couple components.

With mobile apps development, we need to connect the web server from time to time. Indy components play as a proper role for Delphi and C++ Builder, but the SSL feature is not perfect yet.

Hence, we still need TWebBrowser for user to interact with website, if the feedback or redirect path contains HTTPS URL, e.g., Facebook login, Google+ login.

However, there is no interfaces provided by TWebBrowser for retrieving the content of the page.

A->B, B->C, C->D

It's said in some movies, "Genius can get A to D without intermediate path, other cannot", I am not genius, so I have to find out A->B, B->C, C->D to achieve A->D.

The following is my thought:
A->D, A is the TWebBrowser, D is retrieveing content.

TWebBrowser cannot get content, but it can run Javascript without return value.
Javascript can get the content, but there is no way to send the content back to Delphi.
TWebBrowser can get the URL, which the browser wish to navigate.
Javascript can redirect the web browser to another specific URL.

So, the workaround is:
1. waiting for the target URL we want to get the content, e.g. the Google+ login result page.

http://www.moveinpocket.com/demo/GoogleLogin_API/index2.php (login page)
https://www.googleapis.com/oauth2/v1/userinfo?access_token= (login success page, the user information will be sent back with JSON format.)

2. if the login success page is done, the onDidFinishLoad event handler will handle the JSON Page:
if (Pos('https://www.googleapis.com/oauth2/v1/userinfo?access_token=',
        self.WebBrowser1.URL) = 1) then begin

            js := 'var markup = document.documentElement.innerText;' + #13 + #10
                + 'var newURL = "http://1.1.1.1/" + markup;' + #13 + #10 +
                'window.location = newURL;';
            self.WebBrowser1.EvaluateJavaScript(js);
    end;

3. Javascript redirect the browser to http://1.1.1.1/ (you can modify it to another http url), and append the JSON data (innerText in above codes) to the URL.

4. Let onShouldStartLoadURL of Browser to get "http://1.1.1.1/" url, then we got the content in Delphi!
if (Pos('http://1.1.1.1/', URL) = 1) then begin
        self.WebBrowser1.Height := 1;
        jsonStr := URL;
        Fetch(jsonStr, 'http://1.1.1.1/');

        jsonStr := TIdURI.URLDecode(jsonStr, IndyTextEncoding_UTF8);

        ShowMessage('got it');
        self.Memo1.Lines.Text := jsonStr;
        self.TabControl1.ActiveTab := self.TabItemResult;
    end;
I had search some work around with the following key words in Google, Stackoverflow, and it's pity that there is no any solution yet:
Delphi, FireMonkey, WebBrowser, get HTML, get JSON, get Content.

So, this work around might be the only way to get content from TWebBrowser, if you need the sample project, please download from here.

Best Regard, and Enjoy your Delphi.
Dennies Chang.

5 則留言:

  1. Excellent !
    Do you think it could ne possible to connect facebook using your method ? Actually it is not possible to connect facebook web page in embedded browser with firemonkey due to redirection :(

    回覆刪除
    回覆
    1. Hi, Conchon,

      With embed facebook web content, I think so, TWebBrowser will handle redirect automatically.

      If you wish the TWebBrowser handle facebook application, I recommand TRESTRequest, TRESTClient, TRESTResponse.

      Embarcadero provided several sample code, I tested the sample project, it works for Facebook application. Maybe you can try it first, and have some discussions if necessary?

      刪除
  2. onDidFinishLoad is never fired ...any idea?

    回覆刪除
    回覆
    1. onDidFinishLoad? I guess that's from Xcode NSFileConnection.

      2 possibilities for you reference:
      1. If delegate is assigned?
      2. May be the connection is failed, the connection was directed to connect:didFailWithError:

      Is that helpful?

      刪除
  3. unit Unit6;

    interface

    uses
    System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
    FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
    System.Net.URLClient, System.Net.HttpClient, System.Net.HttpClientComponent,
    FMX.StdCtrls, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo;

    type
    TForm6 = class(TForm)
    NetHTTPClient1: TNetHTTPClient;
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    private
    { Private declarations }
    public
    { Public declarations }
    end;

    var
    Form6: TForm6;

    implementation

    {$R *.fmx}

    function GetURL(const AURL: string): string;
    var
    HttpClient: THttpClient;
    HttpResponse: IHttpResponse;
    begin
    HttpClient := THTTPClient.Create;
    try
    HttpResponse := HttpClient.Get(AURL);
    Result := HttpResponse.ContentAsString();
    finally
    HttpClient.Free;
    end;
    end;

    procedure TForm6.Button1Click(Sender: TObject);
    begin
    memo1.Text:= GetURL('http://www.surutakibi.com/index.html');
    end;

    end.

    回覆刪除