TWebBrowser and URL - delphi

I'm working on a software to verify the problem of porting it from D5 to XE5. In D5, TWebBrowser.BeforeNavigate2 was call each time the user click on the submit button of a displayed form. In XE5, it's not the case. I figured out it is because the URL for the submit contain http:/aDirectory/ExecToBeCall.exe. If I add an extra / after: the event is fire.
Under D5 the URL is change for:
http ://localhost/aDirectory/ExecToBeCall.exe (space added to break the link in the post)
That behavior of TWebBrowser under D5 to fire anyway and change the URL is important for the software and I cannot change the HTML (about 2000 files) to contain 2. It allowed us to know if the submit was made inside Delphi or from a outside Browser. I tried other and newer events of TWebBrowser and none are fire.
How can I be informed of a problematic URL, check it and change it into a localhost URL? A small and clean method would be preferable.
Thanks for your help and suggestions

TWebBrowser is just a thin wrapper around Internet Explorer's ActiveX object, so it is IE itself, not TWebBrowser, that is behaving differently.
http:/aDirectory/ExecToBeCall.exe is actually a valid URL. Since the : is not followed by //, there is no authority portion in the URL, and thus no explicit hostname. localhost is used as an implicit hostname, and the path is /aDirectory/ExecToBeCall.exe. That is what the URL is being changed to in D5, which is correct behavior. Changing the URL to http://aDirectory/ExecToBeCall.exe is incorrect, as that creates an authority portion of the URL and thus the hostname is explicitly set to aDirectory and the path is set to /ExecToBeCall.exe, which is not what you want.
Why the URL is not changing in XE5, I have no clue. Sounds like a bug in whatever version of IE is being used in that version of TWebBrowser.
In any case, it is IE that is triggering the event, so if it is not triggering for a URL it does not like, there is nothing you can do about that, short of using the browser's DOM interfaces to handle the onsubmit event of the HTML webform directly.

If you want to redirect unexpected url instead of navigating to it, you can start with TEmbeddedWB project or you can DIY by extending TWebBrowser class with IDocHostUIHandler, which has an interesting method TranslateURL.
function TAdvWebBrowser.TranslateURL(const dwTranslate: DWORD; const pchURLIn: POLESTR; var ppchURLOut: POLESTR): HRESULT;
var
Url: string;
BufferSize: Integer;
begin
Url := PChar(pchURLIn);
if GetSafeUrlFor(Url) then
begin
ppchURLOut := CoTaskMemAlloc(BufferSize);
CopyMemory(ppchURLOut, PChar(Url), BufferSize);
// redirects to new location
Result := S_OK;
end
else
// no redirection
Result := S_FALSE;
end;
// You can change the function to add more complex redirection rules
function GetSafeUrlFor(var Url: string): Boolean;
begin
Result := Url.EndsWithText('.exe');
if Result then
Url := 'http://localhost/';
end;

Related

twebbrowser download in popupwindow

Looking to get my delphi app to log into a website, navigate to a page, and automatically download certain files, the solution at How do I keep an embedded browser from prompting where to save a downloaded file?, helped a great deal with the file download.
The final problem is the last step on navigating opens in a popup window, there are plenty of solutions out there to capture popup windows by implementing TWebBrowser.NewWindow2 but none of these events seem to work with the above code, something to do with how twebbrowser.invokeevent in the above code works maybe?
If I use invokeveent and the dispID of 273(newwindow3) to call a function I can twebbwowser.navigate() a second webbrowser to the url of the popupwindow.
My problem is the popup window has basicly one line of javascript "document.print(parent.parent.opener.thefunction())" the second twebbrowser has no reference to its parent so this fails.
I can see two possible solutions, get the TWebBrowser.NewWindow2 or 3 to trigger, fix the code sample bellow, LVarArray[0] {const IDispatch}, is null for some reason.
procedure TWebBrowser.InvokeEvent(ADispID: TDispID; var AParams: TDispParams);
// DispID 250 is the BeforeNavigate2 dispinterface and to the FFileSource here
// is stored the URL parameter (for cases, when the IDownloadManager::Download
// won't redirect the URL and pass empty string to the pszRedir)
//showmessage('test');
var
ArgCount : Integer;
LVarArray : Array of OleVariant;
LIndex : Integer;
begin
inherited;
ArgCount := AParams.cArgs;
SetLength(LVarArray, ArgCount);
for LIndex := Low(LVarArray) to High(LVarArray) do
LVarArray[High(LVarArray)-LIndex] := OleVariant(TDispParams(AParams).rgvarg^[LIndex]);
case ADispID of
250: FFileSource := OleVariant(AParams.rgvarg^[5]);
273: DoNewWindow3(Self,
LVarArray[0] {const IDispatch},
WordBool((TVarData(LVarArray[1]).VPointer)^) {var WordBool},
LVarArray[2] {const OleVariant},
LVarArray[3] {const OleVariant},
LVarArray[4] {const OleVariant});
end;
end;
I'm not going to answer your question directly because I think you've asked the wrong question. You are trying to download files over the internet without any GUI being shown to the user. As such, an embedded browser is simply the wrong solution.
Rather than trying to suppress popup dialogs, use a tool that never shows popup dialogs. What I believe you should be doing is downloading the files using direct HTTP download. There are many different ways to achieve that. For example, an extremely convenient method, available out of the box with Delphi, is to use Indy. I believe that the component you need is TIdHttp.

Using a cookie with Indy

I'm trying to get data from a site using the Indy components. (This is in Delphi 7 but happy to use anything that works.)
If you go into a normal browser and put in the path:
http://inventory.data.xyz.com/provide_data.aspx?ID=41100&Mixed=no?fc=true&lang=en
it makes you tick a disclaimer before redirecting you to the actual site. This creates a cookie, which if I look at it in Firefox is like this:
http://inventory.data.xyz.com
Name: ASP.NET_SessionId
Content: vm4l0w033cdng5mevz5bkzzq
Path: /
Send For: Any type of connection
Expires: At end of session
I can't get through the disclaimer part using programming but I thought if I manually sign the disclaimer, I can then enter the details of the cookie into my code and connect directly to the data page. I have tried to do this with the code below but it only returns the html for the disclaimer page which tends to imply it's not using the cookie data I've given it. What am I doing wrong?
procedure TfmMain.GetWebpageData;
var
http: TIdHTTP;
cookie: TIdCookieManager;
sResponse: String;
begin
try
http := TIdHTTP.Create(nil);
http.AllowCookies := True;
http.HandleRedirects := True;
cookie := TIdCookieManager.Create(nil);
cookie.AddCookie('ASP.NET_SessionId=vm4l0w033cdng5mevz5bkzzq', 'inventory.data.xyz.com');
http.CookieManager := cookie;
sResponse := http.Get('http://inventory.data.xyz.com/provide_data.aspx?ID=41100&Mixed=no?fc=true&lang=en');
ShowMessage(sResponse); // returns text of disclaimer
except
end;
end;
Since you have not provided a real URL, I can only speculate, but chances are that either the cookie value you are providing to TIdCookieManager is wrong or outdated by the time TIdHTTP.Get() tries to use it, or more likely TIdCookieManager.AddCookie() is rejecting the cookie outright (if the TIdCookieManager.OnNewCookie event is not triggered, then the cookie was not accepted).

A way to redirect keypresses from TWebBrowser to the ParentForm

First question. Help format it, if needed, please.
Context
I have a TWebBrowser in the main form that is used to behave like it was a printer.
So I load some HTML text in it as user do some commands in the real printer...
I want the user to be able to click and select text from the WebBrowser.
Problem
When the user clicks in the WebBrowser some of the shortcuts registered from the actions don't work anymore. For example, there is an action with shortcut F7. If the user clicks in the WebBrowser and presses F7, it does not invoke my shortcut.
I know that this is by design of the WebBrowser.
So, I thought: I want to send every key combination back to the form.
The question is: How?
If it was another control, I could use a perform(WM_KeyDown,...) in the OnKeyDown event.
Alternatives or suggestions would be appreciated too. I am very tired these past 2 days, so I could be missing something.
Derivate TWebBrowser with an implementation of IDocHostUIHandler or use the famous EmbeddedWB
Implement the interface with an event OnTranslateAccelerator called in TranslateAccelerator
Set the event on your brwoser instance
Detect your key(s) like this:
function TBrowserPageIE.DoTranslateAccelerator(const lpMsg: PMSG; const pguidCmdGroup: PGUID; const nCmdID: DWORD): HRESULT;
begin
result := S_FALSE;
if lpMsg.message = WM_KEYDOWN then begin
if lpMsg.wParam = VK_F7 then begin
// do something here...
result := S_OK;
end;
end;
end;
An option that I tested and worked is to trap the key_code on HTML/javascript and then send that to the form using changing the document title. I will let it here hoping that help someone...
You will need add the javascript to trap the keys in the Header of the HTML page like this:
<script = ''javascript''>
function keypresed() {
var tecla=window.event.keyCode;
document.title = "Command"+tecla;
event.keyCode=0;
event.returnValue=false;
}
document.onkeydown=keypresed;
</script>
Then in Webbrowser you use the onTitleChangeEvent to use the key.
var
s:string;
begin
if Copy(Text,0,7) = 'Command' then
begin
//get the key...
s:= Copy(Text,8,Length(Text));
// if before the webbrowser get the focus edit1 was the focused control, you will need remove that focus first...
dummy.setfocus;
edit1.setfocus;
//perform keydown
keybd_event(StrToInt(s), 1,0,0)
end;
end;
Well, this can be used to perform any other custom command. :)

Detect TWebBrowser refresh event in Delphi 2009

I am using a TWebBrowser component which I use to load XML documents into which are linked to a XSL file.
I have a default page I display when no XML document is loaded. However, if the user deletes the XML file whilst it is open in the browser and then refreshes I get the standard resource could not be found error. What I would like to do instead is if the page cannot be loaded, check that the file exists, and if it doesn't just load the default page again.
I have tried using the OnNavigateError and OnBeforeNavigate2 events however they does not seem to trigger on a refresh.
Any idea's?
There is an onRefresh event which is exposed by the TWebBrowser replacement TEmbeddedWB. This version also exposes many other features which are otherwise hidden by the TWebBrowser component.
This is a bit of a cludge but it works in my tests, using the standard TWebBrowser component.
What I did was override the F5 key in the form's OnKeyUp event. By setting the form's KeyPreview property to True, you can call your own refresh. Seeing as the TWebBrowser.Refresh method doesn't appear to call any of the navigation events (as you said in your question), I call the TWebBrowser.Navigate event myself, which does fire the events.
You need to store the URL, which I imagine you're already doing. But if not, then in the BeforeNavigate2 event, you are given the URL as a parameter. So store this in a variable or on-screen control. Then, when F5 is pressed (or the Refresh button if you have put one on screen), simpy navigate to that URL again. The OnBeforeNavigate2, OnNavigateComplete2 and OnDocumentComplete events are all fired again, allowing you the opportunity to perform your test and put your placeholder page up instead of IEs default error page.
procedure TForm1.WebBrowser1BeforeNavigate2(Sender: TObject;
const pDisp: IDispatch; var URL, Flags, TargetFrameName, PostData,
Headers: OleVariant; var Cancel: WordBool);
begin
Edit1.Text := URL;
end;
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if (Key = VK_F5) then
begin
WebBrowser1.Navigate(Edit1.Text);
end;
end;

TIdHTTP in Indy 10

I used to use Indy back in the Delphi 6 days, and I am playing with Indy 10 now. What I want to do is incredibly simple, but I don't see a simple way of doing it, so I must be missing something.
What I want to do is something like this:
Here is the actual code I am using:
procedure TForm1.btnGetURLClick(Sender: TObject);
begin
moHeader.Lines.Clear;
moBody.Lines.Clear;
try
moBody.text := IdHttp1.Get(edURL.text);
finally
end;
end;
When the request is complete, the http_result should contain the HTML from the URL specified. This doesn't seem to work however, so I get the feeling I should perhaps be using the IOHandler property or the OnWork event of the component - however the usage doesn't seem obvious to me, and I couldn't find any working examples with google. I am sure this is something that has been done before, so any help would be appreciated.
Additional Information:
In the spirit of being more specific, I want to know:
1. Am I doing this right to begin with (or did I miss something?).
2. If so, why might it not be working.
3. It is always possible that there is a bug in the combination of compiler/os/Indy I am using. (Although it should be working).
I should mention, I always get a popup "Connection Closed Gracefully". This seems to be an exception, and it could be interfering with the result of the function. I attempted to trap this with a TRY...FINALLY, but it doesn't work. Probably because Indy is triggering the exception in the background after the Get method runs I suppose.
Finally, here is a screencast of the program running to clear up any confusion:
http://screencast.com/t/NDMzNTQ5
I expect the HTML to fill the second memo box.
i think you have the TIdHTTP.HandleRedirects property set to false, if you get the error "HTTP/1.1 302 Found" you can try this
var
http_result:string;
Begin
IdHTTP1.HandleRedirects:=True;
http_result := IdHTTP1.Get('http://www.google.com');
End;
Another option, would be to use synapse. This is all that is needed to retrieve a webpage using this library:
uses
...,HTTPSEND;
var
Result : TStrings;
if HTTPGetText('http://www.google.com',Result) then
// do something with result
Synapse is a lightweight TCPIP library. The library is being actively maintained and the current version runs fine in Delphi 2009/2010. It is NOT a component based framework, so it is very easy to use with other threading techniques (OmniThreadLibrary or AsyncCalls for example).
You have to set the property HandleRedirects to true.
There's no need for a form, using GExperts components to code I got this:
var
IdHTTP: TIdHTTP;
IdHTTP := TIdHTTP.Create(Self);
with IdHTTP do
begin
Name := 'IdHTTP';
AllowCookies := True;
HandleRedirects := True;
HTTPOptions := [hoForceEncodeParams];
end;
Just paste this in your unit, it should be all you need.
Iirc if the website redirects, you also need to override some handler (onredirect or so). But this was also the case in indy9 iirc.
This question has lingered open for quite some time, so I am closing it out. My solution was to just use Synapse, as one of the posters suggested. It works on windows/Linux/Mac OS with minimal modifications, and works fine in libraries/threads.

Resources