Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Insert & Execute Javascript code in HTML page? #19

Closed
LeMoussel opened this issue Dec 19, 2015 · 12 comments
Closed

Insert & Execute Javascript code in HTML page? #19

LeMoussel opened this issue Dec 19, 2015 · 12 comments

Comments

@LeMoussel
Copy link

Hi,

Is it possible to pass Javascript code back and execute JavaScript function/method from C#?

For example :

ScriptingService javascriptService = new ScriptingService();
IConfiguration config = Configuration.Default.WithDefaultLoader(setup => setup.IsResourceLoadingEnabled = true).With(javascriptService).WithCss();

string scriptJS  = @"<script> 
                         function getTitle() { return document.title; }
                    </script>";

IDocument ashDocument = await BrowsingContext.New(config).OpenAsync("http://html5test.com");

// How to insert scriptJS  & excute getTitle() function?

// Perhaps to execute getTitle() function ....
JsValue jsGetTitle = javascriptService.Engine.GetJint(ashDocument).GetValue("getTitle");
string documentTitle = jsGetTitle.Invoke().AsString();
Console.WriteLine("Document Title: {0}", documentTitle);
@FlorianRappl
Copy link
Contributor

Potentially, the following file will cover your desired use-case:
https://github.com/AngleSharp/AngleSharp.Scripting/blob/master/AngleSharp.Scripting.JavaScript.Tests/InteractionTests.cs

@LeMoussel
Copy link
Author

Unfortunately no.
All test method don't load external HTML page and insert Javascript code.

@FlorianRappl
Copy link
Contributor

I mean, how would you do in pure JS? You would

  • either invoke eval or,
  • append a new script element, which references an external source.

Does none of these methods work for you?

@LeMoussel
Copy link
Author

I would

  1. Load HTML page (eg http://httpbin.org/)
  2. Append a new script element with some JS functions to the loaded page (eg: scriptJS)
  3. Call / Execute JS functions (eg: getTitle()) append to the loaded page from C#

@FlorianRappl
Copy link
Contributor

If the example looks like above (where you know the script's content) I would highly recommend using eval. As you already have access to Jint (responsible for handling the specific IDocument) that is also most elegant.

If you need to load the script from some external source then the way you describe is certainly the best one. Here AngleSharp does all the steps for you.

So yes, that sounds good to me.

One thing to be aware is the asynchronous execution. You probably won't have access to the result right away. This is something that will definitely improve in the near future, but right now its not optimal.

@LeMoussel
Copy link
Author

I do this

var jsGetTitle = javascriptService.Engine.GetJint(ashDocument).Execute("document.Title;").GetCompletionValue().AsString();

But I got exception document is not defined.

@FlorianRappl
Copy link
Contributor

Is window defined? Also you probably mean document.title as Title is the C# variant, but title is the JavaScript name.

@LeMoussel
Copy link
Author

I'm confused. OK for document.title
window is not define. With

javascriptService.Engine.GetJint(ashDocument).Execute("if (typeof window === 'undefined'){console.log('object: window is not available...');}");

I got object: window is not available...

@FlorianRappl
Copy link
Contributor

The thing is that you execute that stuff directly from Jint, without providing the right execution layer. The way JavaScript engines are built there are is a stack of so called contexts. They all provide a base context, which contains elementary JavaScript objects, such as String, Number, Math, ... Then there would be a layer populated by the current instance of the Window interface.

AngleSharp provides this window for you. Therefore I can recommend two things:

  • Obtain a stored (global) value: Use GetJint from the engine and Execute from Jint.
  • Execute some script (in the DOM context): Use Evaluate from the engine and supply some scripting options.

This should be made more convenient I guess. Perhaps some more methods may be useful here.

@FlorianRappl FlorianRappl added this to the v0.5 milestone Dec 20, 2015
@LeMoussel
Copy link
Author

some progress :)
No error with Evaluate like this

javascriptService.Engine.Evaluate("document.title;", new ScriptOptions() { Context = ashDocument.DefaultView, Document = ashDocument });

But Evaluate has no return type (void). Like you say this should be made more convenient.
Maybe some Evaluate methods like Execute may be useful here.
eg :

javascriptService.Engine.Evaluate("document.title;", new ScriptOptions() { Context = ashDocument.DefaultView, Document = ashDocument }).AsString();

@LeMoussel
Copy link
Author

POC

In EngineInstance.cs, modiy RunScriptmethod like this

        public Jint.Native.JsValue RunScript(String source)
        {
            _engine.EnterExecutionContext(Lexicals, Variables, _window);
            Jint.Native.JsValue jsResult = _engine.Execute(source).GetCompletionValue();
            _engine.LeaveExecutionContext();
            return jsResult;
        }

In JavaScriptEngine.cs, Add Execute method

        public Jint.Native.JsValue Execute(String source, ScriptOptions options)
        {
            var objectContext = options.Context;
            var instance = default(EngineInstance);

            if (_contexts.TryGetValue(objectContext, out instance) == false)
                _contexts.Add(objectContext, instance = new EngineInstance(objectContext, _external));

            return instance.RunScript(source);
        }

Test

string jsGetTitle = javascriptService.Engine.Execute("document.title;", new ScriptOptions() { Context = ashDocument.DefaultView, Document = ashDocument }).AsString();

@FlorianRappl
Copy link
Contributor

Nope, this won't happen.

Jint will always let you access the last return value on the stack. Therefore, there is no need to make such changes. Instead, an extension method is more useful.

The coupling to Jint should be as minimal as possible. Right now its already on the edge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants