Adding a script element to an existing TWebBrowser document - javascript

Prompted by a couple of SO qs this weekend, I decided to see if I could work out how
to add some javascript in an Html element to a document loaded in a TWebBrowser. Doing so,
I've run into an oddity which maybe someone can explain.
Here's my Html document
<html>
<body>
Something
<br>
<div id="forscript">some more text</div>
</body>
</html>
and my javascript element I want to add to it
<script type="text/javascript" defer="false">{alert('hello');}</script>
which I pass as the Script argument to AddScript() below.
This is my code (doc2 is an IHtmlDocument2 obtained from the WebBrowser):
procedure TForm1.AddScript(const Script : String);
var
Element : IHtmlElement;
Doc3 : IHtmlDocument3;
begin
Doc2.QueryInterface(IHtmlDocument3, Doc3);
Element := Doc3.GetElementByID('forscript');
Element.innerHTML := Element.innerHTML + Script;
end;
This works fine, but ...
The reason for the Element.innerHTML on the RHS of the assignment is simply that I found that
if the RHS contains only the Script js, the js does't execute. My question is, why not, and is there a simpler alternative, like somehow creating an IHtmlScriptElement in code and inserting it in the DOM? Obviously my simple-minded work-around is simply to prepend the text the
Element already contains, but I have a slightly hard time believing that people who actually know what they're doing would find that necessary.
FWIW #1: I tried using Element.insertAdjacentHtml to add the script but got the same behaviour as I've just described, in terms of needing to insert something in addition to the script to get the script to be executed, so I'm wondering whether it's something to do with how the DOM is processed after a change is made to it.
FWIW #2: I've used the Element.innerHtml route because TWebBrowser/MSHTML.Pas have resisted my
attempts to create an IHtmlScriptElement in code; AFAICS, attempting to use any of the
CohtmlXXXElement routines in MSHTML.Pas provoke a "Class not registered" exception, which seems to confirm a comment I came across somewhere by #kobik of this parish.
(Btw, using D7 + IE11 on Win7 64-bit)

Here' a complete example how to use IHtmlScriptElement.
Html is loaded at application startup.
The code under Button1Click adds the javascript to the DOM and executes it:
unit u_frm_SO27091639;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, OleCtrls, SHDocVw, MsHtml, ActiveX;
type
TForm1 = class(TForm)
WebBrowser1: TWebBrowser;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure LoadDocFromString(Webbrowser : TWebbrowser);
var
Strm : TStringStream;
Adapter : TStreamAdapter;
begin
WebBrowser.Navigate('about:blank');
// warning - don't use this trick in production code, use the `OnDocumentComplete` event
while WebBrowser.ReadyState <> READYSTATE_INTERACTIVE do
Application.ProcessMessages;
// end of warning
Strm := TStringStream.Create;
try
Strm.WriteString('<html><body>Something<br></body></html>');
Strm.Seek(0, 0);
Adapter := TStreamAdapter.Create(Strm);
(WebBrowser.Document as IPersistStreamInit).Load(Adapter) ;
finally
Strm.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Doc2 : IHtmlDocument2;
Script : IHtmlDOMNode;
HTMLWindow: IHTMLWindow2;
begin
Doc2 := Webbrowser1.Document as IHtmlDocument2;
if Assigned(Doc2.body) then
begin
Script := Doc2.createElement('script') as IHTMLDOMNode;
(Script as IHTMLScriptElement).text := 'function helloWorld() { alert("hello world!") }';
(Doc2.body as IHtmlDomNode).appendChild(Script);
HTMLWindow := Doc2.parentWindow;
if Assigned(HTMLWindow) then
HTMLWindow.execScript('helloWorld()', 'JavaScript')
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
LoadDocFromString(Webbrowser1);
end;
end.

Related

VBA & Selenium | Access iframe within HTML containing #document

I am trying to access the HTML within two iframes using Selenium Basic in VBA, as IE has been blocked on our machines, and Python, etc. are not available to us.
Previously I could access the html with this:
Dim IE As InternetExplorerMedium
Set IE = New InternetExplorerMedium
' actual website excluded as it is a work hosted website which requires login, etc.
website = "..."
IE.navigate (website)
Dim IEDocument As HTMLDocument
Set IEDocument = IE.document.getElementById(id1).contentDocument.getElementById(id2).contentDocument
From there I would have access to all the HTML elements which I could work with.
Now I am trying the following with Selenium Basic:
Set cd = New Selenium.ChromeDriver
website = "..."
cd.Start baseUrl:=website
cd.Get "/"
Dim af1 As Selenium.WebElement, af2 As Selenium.WebElement
Set af1 = cd.FindElementById("CRMApplicationFrame")
Set af2 = af1.FindElementById("WorkAreaFrame1")
It works up to the last line, as it is able to set af to the "CRMApplicationFrame" id; however, I am unable to get inside of it.
I think the solution lies in executing a bit of JavaScript, similar to as in this video:
https://www.youtube.com/watch?v=phYGCGXGtEw
Although I don't have a #ShadowDOM line, I do have a #document line.
Based on and trying to adapt the video I have tried the following:
Set af2 = cd.ExecuteScript(Script:="return arguments[0].contentDocument", arguments:=af1 )
However, that did not work.
I also tested:
Dim af1 As Selenium.WebElement
Set af1 = cd.FindElementById("CRMApplicationFrame")
call cd.SwitchToFrame (af1)
Debug.Print cd.PageSource
However, the SwitchToFrame line won't execute, with a 438 error: Object doesn't support this property or method.
Any advice or guidance on how I could succeed would be highly appreciated!
Replace:
call cd.SwitchToFrame (af1)
with:
cd.SwitchToFrame "CRMApplicationFrame"
You can find a relevant detailed discussion in Selenium VBA Excel - problem clicking a link within an iframe

Insert a script at the top of HEAD tag in PageMod (Firefox Add-on SDK)

I want to insert a script in Firefox Add-on SDK content script added via page-mod. So, when user goes to page XXX, he gets a page with the inserted script. But, when trying to insert this at the top of HEAD, I got an error that the 1st Node child is not an object.
My content script code with which I insert the <script> is:
script='<script>var d=new Date(2012,2,24); window.Date=function () {return d;}</script>';
var head = document.getElementsByTagName("head")[0];
head.insertBefore(script, head.firstChild);
What's with it?
You have not provided a Minimal, Complete, and Verifiable Example. Thus, this answer is based just on the code you have provided.
Issue with how you are using insertBefore():
The first issue I noticed is that you are providing a string as the first argument to insertBefore() when that argument needs to be a node object.
However, you state the error you are getting is "1st Node child is not an object". This does not match what I would expect from an error based on the code provided. When I put your code in a page-mod content script, the error I got was:
console.error: testpagemodinsertBefore:
Object
- _errorType = TypeError
- message = Argument 1 of Node.insertBefore is not an object.
- fileName = resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/loader/sandbox.js -> resource://testpagemodinsertbefore/data/contentScript.js
- lineNumber = 26
- stack = #resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/loader/sandbox.js -> resource://testpagemodinsertbefore/data/contentScript.js:26:1|
- name = TypeError
This is the error which I would expect from the code in the question. Thus, you have either not posted the code which is producing the error you have stated, or you have inaccurately reported the error which you saw in the console. In the future, please be exact in what you are reporting. Being more verbose is better than leaving things out.
Inserting HTML text:
Your code:
script='<script>var d=new Date(2012,2,24); window.Date=function () {return d;}</script>';
defines some HTML text which you are trying to insert into the DOM.
If you want to insert HTML text instead of inserting nodes, one way for you to do so is to use insertAdjacentHTML(). One way you could do this in your code is:
head.insertAdjacentHTML('afterbegin',script);
This works well for most HTML. But, does not result in the contents of the <script> tag being executed/evaluated. This is for security reasons. It is much easier for an attacker to get arbitrary text inserted into the DOM than it is for them to insert actual nodes/elements. The HTML specification specifies that a tag inserted inserted as text will not be executed.
Inserting a <script> tag that is evaluated and executed:
We can do this by creating a <script> element containing the script text desired, then inserting that element into the DOM.
For your code that would look something like:
//Get the first <head> element
let head = document.getElementsByTagName("head")[0];
//The script text desired
let scriptText='var d=new Date(2012,2,24); window.Date=function () {return d;};';
//Add an alert for testing (\n is not needed. But, is more readable when inspecting DOM.)
scriptText = 'window.alert("In inserted script");\n' + scriptText;
//Create a <script> element
let scriptEl = document.createElement( 'script' );
//That is JavaScript
scriptEl.type = 'text/javascript';
//Add the script text
scriptEl.textContent = scriptText;
//Insert it as the firstChild of <head>
head.insertBefore( scriptEl, head.firstChild );
Further reading:
You appear to be desire to interact with page scripts using your content script. I suggest that you read the MDN page "Interacting with page scripts".

Javascript Nashorn scripting mode: how to write to file

I'm writing a shell script to be run with jjs -scripting under Java 8. However, a key requirement is that I need to be able to write to a number of files. (So I can't just print() to stdout and redirect.)
There's handily readFully to read a file, but I don't see any writeFully, which seems odd to me.
I thought probably I could just $EXEC an echo command, but I can't get that to work:
jjs> x='some string'
some string
jjs> $EXEC("echo '"+x+"' >test.out");
some string >test.out
So my next thought is that I have to load up and call the appropriate Java classes, but that seems like it's over-complicating a simple function. What am I missing?
The best I could figure out was to do it via the Java FileWriter class. For example, I had an array of links I needed written to a file:
var FileWriter=Java.type("java.io.FileWriter");
var olinkfile = caldir+"/"+year+"_links.html";
var fw = new FileWriter(olinkfile);
fw.write(links.join("\n"));
fw.write("\n");
fw.close(); // forgetting to close it results in a truncated file
Although it would have been nice for JJS to provide a function to do this directly from JavaScript without having to instantiate the FileWriter class manually, this really isn't too much code. And once you've done it once it seems almost obvious.

Is there a way to AutoFormat (Javascript) code in TestComplete?

So similar to ALt-Shift-F in Netbeans, is there a to do this right in the ide in TestComplete? Not sure if this is possible or if anyone can think of a workaround to autoFormat without leaving the TestComplete window.
I'm trying to get the below solution to work with http://jsbeautifier.org/ for javascript / Jscript code in TestComplete.
Thanks
Great question!
There is no built-in function for that. So, we should not expect any solution to be 100% convenient - it is just not a simple task to modify the current script editor contents (if at all possible). So, whatever you do, it will still be some kind of compromise.
In general, the task is three-fold:
Get the current unit code.
Format the code.
Put the code back to the unit.
According to my understanding, items 1 and 3 can be accomplished only by creating a TestComplete plug-in - accessing editors for project nodes is not an easy thing.
UPDATE: silly me! There is a way to access the script editor code - I've updated the below part.
What will help us avoid switching to a different app, are the Script Extensions:
We create a custom Checkpoint in the form of a Script Extension, and install it to TestComplete. As a result, we get a button on the toolbar that we can click to invoke our code.
In the design time action, we call some code that reads the editor contents, then uses external code formatting functionality, and replaces the editor contents with the formatted code.
It would extremely interesting to see the implementations other TestComplete users can suggest! As a start, I am posting a solution that includes using an external web site to format VBScript code (http://www.vbindent.com/). I know that the starter of the post is probably using JScript, but I have not found a JScript formatter yet.
My solution is a simple Script Extension. I can't post a file here, so I will post the code of the two Script Extension files:
Description file:
<!-- Description.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<ScriptExtensionGroup>
<Category Name="Checkpoints">
<ScriptExtension Name="VBScript Code Indent" Author="SmartBear Software" Version="0.1" HomePage="smartbear.com">
<Script Name="VBIndent.js">
<DesignTimeAction Name="Indent Current VBScript Unit" Routine="DesignTimeExecute"/>
</Script>
<Description>
Indents VBScript code in the currently active unit.
</Description>
</ScriptExtension>
</Category>
</ScriptExtensionGroup>
Code file:
// VBIndent.js
function DesignTimeExecute()
{
if (CodeEditor.IsEditorActive)
{
var newCode = IndentVBSCode_Through_VBIndent(CodeEditor.Text);
if (null == newCode)
return;
CodeEditor.Text = newCode;
}
}
function IndentVBSCode_Through_VBIndent(codeToIndent)
{
var URL_VBIndent = "http://www.vbindent.com/?indent";
var httpObj = Sys.OleObject("MSXML2.XMLHTTP");
httpObj.open("POST", URL_VBIndent, false);
httpObj.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
httpObj.send("thecode=" + escape(codeToIndent));
var responseText = httpObj.responseText;
// Extract the indented code from the response
var rx = /<textarea name=\"thecode\".*?>((.*\n)*?)<\/textarea>/;
matches = rx.exec(responseText);
if (null == matches)
{
return null;
}
codeIndented = matches[1];
return codeIndented;
}
After you create these files, and put them to something like "\Bin\Extensions\ScriptExtensions\VBIndent", and click "File | Install Script Extensions | Reload", you will see a new "Indent Current VBScript Unit" item in the custom checkpoints drop-down button on the Tools toolbar. Clicking the element will format the VBScript code in the currently active editor.
So, this is to give a clear idea of what a solution can look like. Better suggestions are welcome! Share your thoughts!
FYI
I've done. Based on your posts.
JSFormat.tcx
https://drive.google.com/uc?export=download&id=0B1x_73bHRc2Jcm8wbTJ2dUpZQTQ
To install the extension copy attached file JSFormat.tcx to C:\Program Files (x86)\SmartBear\TestComplete 10\Bin\Extensions\ScriptExtensions
To use view next image:
https://drive.google.com/uc?export=download&id=0B1x_73bHRc2Jc3RuLXFpTnlCSnc
Regards

How can I write a simple JScript input/output program?

I'm planning on using JavaScript to enter an informatics competition (BIO) tomorrow. However, I can't rely on the examiner having a browser with a decent JavaScript engine, so I was hoping to use Microsoft's JScript instead.
However, the documentation is, quite frankly, crap. Can someone post some example code that reads in a line of text, calls foo(string) on it, and echos the output to the command line?
Similarly, how do I actually run it? Will wscript.exe PATH_TO_JS_FILE do the trick?
If you're using the command-line, I'd execute the script using CSCRIPT.EXE.
ie: CSCRIPT.EXE myscript.js
This is because WScript.Echo from WSCRIPT will create a dialog box and from CSCRIPT outputs a line to the console. Run this in a command window (CMD).
Reading a line from console into variable:
var x = WScript.StdIn.ReadLine();
Where StdIn is a TextStream object. There is also an StdOut which can be used in place of WScript.Echo()...
Writing the output of foo(x) to console: (must run under CSCRIPT)
WScript.Echo(foo(x));
You can use the WScript object to determine which engine you are running under, there's a question/answer for that (VBScript, but uses the same objects under JScript) here.
If you want to be sure your program only runs in the command line, you may use the WScript.StdIn and WScript.StdOut objects/properties:
var myString = WScript.StdIn.ReadLine();
WScript.StdOut.WriteLine(myString);
and run it with cscript.exe. But if you want it to be a GUI program, it is a bit more difficult considering that JScript doesn't have a native InputBox function like VBScript. However, as described here we may use Windows Script Host (WSH). Create a .wsf file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<job id="testJScriptInputBox">
<script language="VBScript">
<![CDATA[
Function WSHInputBox(Message, Title, Value)
WSHInputBox = InputBox(Message, Title, Value)
End Function
]]>
</script>
<script language="JScript">
<![CDATA[
var vbOKOnly = 0; // Constants for Popup
var vbInformation = 64;
var title = "InputBox function for JScript";
var prompt = "Enter a string: ";
var WshShell = WScript.CreateObject("WScript.Shell");
var result = WSHInputBox(prompt, title, "New York");
if (result != null)
{ // Cancel wasn't clicked, so get input.
var intDoIt = WshShell.Popup(result,
0,
"Result",
vbOKOnly + vbInformation);
}
else
{ // Cancel button was clicked.
var intDoIt = WshShell.Popup("Sorry, no input",
0,
"Result",
vbOKOnly + vbInformation);
}
]]>
</script>
</job>
and run it with either cscript.exe or wscript.exe. Alternatively, you could also use HTML Application (HTA) to create more elaborate GUIs.

Categories

Resources