I am creating a Silverlight application which will be heavily javascripted against.
To enable JS interaction, I have created the following SL class:
[ScriptableType]
public class JavaScriptProxy
{
private string _version;
// provided for testing SL-JS integration
[ScriptableMember]
public void SmokeTest() { HtmlPage.Window.Alert("Hello world!"); }
}
And loaded it on the constructor of the main SL application:
public App()
{
this.Startup += this.onStartup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;
InitializeComponent();
// register javascript bridge proxy
// (must register in constructor so proxy is available immediatly)
HtmlPage.RegisterScriptableObject("JsProxy", new JavaScriptProxy());
}
However, as this is a Javascript-heavy app, it must be loadable via javascript itself.
I.e. something alongs:
// called on body.onLoad
function init() {
var proxy;
var el = document.getElementById("target_canvas");
Silverlight.createObject(..., el, "agApp" ..., {
onLoad: function() {
proxy = agApp.Content.JsProxy;
// ***this line is ok***
proxy.SmokeTest();
}
});
// ***this line fails (of course)***
proxy.SmokeTest();
}
However, this raise the error because agApp.Content.JsProxy is not available fully until the Silverlight onLoad event is fired, thus the JsProxy field is unavailable.
How can I enable access to the JsProxy class immediately as I create the Silverlight instance? Some thing alongs while(_mutex); is probably a bad idea.
I had to this because there will be another layer of abstraction building on the creation of Silverlight app instances, so that function must synchronously load all SL contents in one go.
This is due to Silverlight and JavaScript are operating on seperate threads. Even though you've requested the browser to load the said Silverlight control, it doesn't wait around for Silverlight to finish loading before it proceeds to the next line.
You can only access the JS Proxy after Silverlight has instantiated it so you can either wait for the OnLoad event to fire (but this will only fire after the entire Silverlight.xap is fully loaded) that or after you RegisterScriptableObject fire a JavaScript method called onYourJSProxyNameLoaded() which will put you back inline with the workflow you desire.
HTH.
-
Scott Barnes / Rich Platforms Product Manager / Microsoft.
Related
I´m using code splitting in GWT to reduce the size of the initial JavaScript.
While my application initializes, I want to prefetch the other (bigger) part of my code as explained in the docs (www.gwtproject.org/doc/latest/DevGuideCodeSplitting.html).
private void doSth(final boolean prefetch) {
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable caught) {
Log.error("Loading the code failed!");
}
public void onSuccess() {
if(prefetch)
return; //do nothing. just a prefetch
//here is the loaded code
}
});
}
But I cannot recognize a performance improvement. As I analyzed the browser logs, I recognized, that the request for loading the JavaScript is not marked as XHR. Does GWT load the code of a split point synchronously?
The performance improvement is in the initial downloaded code, assuming nothing else references that code. If anything else does the work of //here is the loaded code, then there will be either very little or no code to break out into a separately downloaded JS file.
This feature can be disabled in several ways, including by using dev mode or setting a compiler flag to skip this process. In this case, yes, the split point runs synchronously, since it makes no sense to wait. Additionally, after the file has been loaded once, it does not need to be loaded the next time the code is invoked within the same page load.
If your server is set to cache correctly, then after the first visit the savings is even smaller since there is no download to do - you only save the time taken to parse that code into the browser's JS VM.
But beyond that, we're going to need more information.
Here's a quick demo showing how the split point can be written with a little more meat to it, and letting you use your browser to notice how the split point code was brought in separately.
public class SampleEntryPoint implements EntryPoint {
public void onModuleLoad() {
Label label = new Label("Hello, World!");
label.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
GWT.runAsync(new RunAsyncCallback() {
public void onFailure(Throwable var1) {/*ignore*/}
public void onSuccess() {
Window.alert("Clicked, and loaded in split point!");
}
});
}
});
RootPanel.get().add(label);
}
}
Code and sample:
https://viola.colinalworth.com/proj/755e224e7f48a047703d44eb6903d926/project/client/SampleEntryPoint.java
Standalone sample:
https://viola.colinalworth.com:444/compiled/755e224e7f48a047703d44eb6903f76c/
When you load this page, the nocache file loads, as does the initial download (as seen through Chrome's inspector's Network tab):
Then, when you click the Label widget, the onClick fires which triggers the runAsync and downloads the extra split point (plus the "leftover" fragment):
After those two new entries have been added to your Network tab, you see the alert message appear. Subsequent clicks do not result in this slight delay, nor do they force this extra JS to download again.
Also note that these are not loaded as AJAX/XHR calls, but as a script tag to be added to the page. Clicking on the details in the Initiator column (not pictured) leads to this (formatted for readability):
function fb(a) {
var b, c, d;
d = (bb(), window);
b = d.document;
c = b.createElement('script');
(!!a.a || a.b) && cb(c, a.a, a.b);
eb(c, a.c);
(b.head || b.getElementsByTagName('head')[0]).appendChild(c);
return c
}
Getting through the obfuscated code, we see that a <script> tag is created, and appended to the <head> of the page.
Digging in deeper, we can find that the AsyncFragmentLoader.LoadingStrategy interface describes how to go get this fragment, and that com/google/gwt/core/AsyncFragmentLoader.gwt.xml wires this by default to XhrLoadingStrategy. However, both the xs and xsiframe linkers change this, to CrossSiteLoadingStrategy and ScriptTagLoadingStrategy respectively. And as of recent versions of GWT (you didn't specify, so I'm assuming you are using a recent version), the xsiframe linker is the default. From Core.gwt.xml:
<add-linker name="xsiframe" />
We can customize this by switching to the old linker, or just replacing the strategy. Note that switching the an XHR strategy will prevent cross-domain loading from working correctly (such as SuperDevMode), so be careful with this.
Much as AsyncFragmentLoader.gwt.xml wired the interface to XhrLoadingStrategy, and CrossSiteIframeLinker.gwt.xml changed it to ScriptTagLoadingStrategy, we can change it back. We create a rule that replaces LoadingStrategy with XhrLoadingStrategy, and list it after our GWT inherits statements in our .gwt.xml file:
<replace-with class="com.google.gwt.core.client.impl.XhrLoadingStrategy">
<when-type-is class="com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadingStrategy" />
</replace-with>
This is what the old default used to rely on as part of the std linker (com.google.gwt.core.linker.IFrameLinker), though this is no longer encouraged and may be removed in a later release.
I have a similar problem to the person in this post; I'm trying to extend the cefsimple.exe app included with the chromium embedded framework binaries to include a V8 handler. I implemented the OnContextCreated() method and made sure to extend RenderProcessHandler in the SimpleHandler class. I'm trying to implement a simple window bound variable called test_string; here's what my code looks like;
void SimpleHandler::OnContextCreated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context)
{
CefRefPtr<CefV8Value> object = context->GetGlobal();
object->SetValue("test_string", CefV8Value::CreateString("this is a test"), V8_PROPERTY_ATTRIBUTE_NONE);
}
But the program never arrives at any breakpoints I add within the method, and the variable is undefined on any webpages I load within the app. I saw that one of the solutions in the other thread is to enable the settings.single_process flag, which i've done, but my code still doesn't reach the breakpoint.
To be clear, I'm accessing the variable on pages with window.test_string.
Make sure that you are sending that CefApp to CefExecuteProcess.
CefRefPtr<SimpleApp> app(new SimpleApp);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, app, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
Found this solution here
Have you read through the General Usage guide? Some key points below
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-cefapp
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-processes
The single_process mode is not supported so I've never used it. In general I'd avoid it. The multi process architecture means you need to attach the debugger to the process. The Chromium guide is relevant to CEF in this instance.
https://www.chromium.org/developers/how-tos/debugging-on-windows#TOC-Attaching-to-the-renderer
you need to ensure your App is derived from CefRenderProcessHandler
not SimpleHandler!!!
class SimpleApp : public CefApp
, public CefRenderProcessHandler
{
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) OVERRIDE;
valdemar-rudolfovich says you need to pass instance of SimpleApp in
CefExecuteProcess
First a bit of background
I'm working on a Web application, that will be running within a WebBrowser, within a WPF application.
This is a temporary necessity while we're gradually moving functionality to the web app. As long as that's not finished, the WPF client is still needed. Ultimately the WPF client will phase out completely.
Now to the issue at hand
When the user closes the client (webpage), the webbrowser should catch that event and also close the window it is a child to.
I found this link describing what I would need: WebBrowser and javascript window.close()
Alas, I don't think the answer described there would still work, as it's not possible to even do a window.close(), because I'm not the one opening the window I'm running on. Browsers have (rightfully) tightened their security since then.
The question
Is there a way to trigger a Window close from the client, that bubbles up to the WPF?
Thanks.
I have used a WebBrowser control to call methods in a WPF application from the JavaScript before using WebBrowser.InvokeScript and WebBrowser.ObjectForScripting
See this MSDN article How to: Implement Two-Way Communication Between DHTML Code and Client Application Code
Also see this CodeProject article which looks like it might solve your problem
Call a C# Method From JavaScript Hosted in a WebBrowser
[ComVisible(true)]
public class ScriptManager
{
// Variable to store the form of type Form1.
private Window _window;
// Constructor.
public ScriptManager(Window window)
{
// Save the form so it can be referenced later.
_window = window;
}
// This method can be called from JavaScript.
public void MethodToCallFromScript()
{
// Call a method on the form.
_window.Close();
}
}
from code behind of Window:
webBrowser1.ObjectForScripting = new ScriptManager(this);
That worked, thanks!
I did the following:
[ComVisible(true)]
public class ScriptManager
{
protected Window Window { get; set; }
public ScriptManager(Window window)
{
this.Window = window;
}
public void CloseWindow()
{
this.Window.Close();
}
}
And in my Window (Loaded Event):
// Build browser
this.Browser = new WebBrowser();
this.Browser.Navigate(this.GetUri());
this.Browser.ObjectForScripting = new ScriptManager(this);
The client Javascript then does:
$scope.Close = function() {
window.external.CloseWindow();
}
Since the way you call javascript on a WebView is through loadUrl("javascript: ... "); The keyboard cannot stay open.
The loadUrl() method calls loadUrlImpl() , which calls a method called clearHelpers() which then calls clearTextEntry(), which then calls hideSoftKeyboard() and then we become oh so lonely as the keyboard goes away.
As far as I can see all of those are private and cannot be overridden.
Has anyone found a workaround for this? Is there a way to force the keyboard to stay open or to call the javascript directly without going through loadUrl()?
Is there anyway to override the WebView in a way to prevent (the private method) clearTextEntry() from being called?
Update
KitKat added a public method for invoking javascript directly: evaluateJavascript()
For older apis, you could try a solution like below, but if I had to do this again I'd look at just building an compatibility method that on KitKat uses the above method and on older devices, uses reflection to drill down to a inner private method: BrowserFrame.stringByEvaluatingJavaScriptFromString()
Then you could call javascript directly without having to deal with loadUrl and adding "javascript: " to the script.
Old Answer
As requested by Alok Kulkarni, I'll give a rough overview of a possible workaround I thought of for this. I haven't actually tried it but in theory it should work. This code is going to be rough and is just to serve as an example.
Instead of sending the calls down through loadUrl(), you queue your javascript calls and then have javascript pull them down. Some thing like:
private final Object LOCK = new Object();
private StringBuilder mPendingJS;
public void execJS(String js) {
synchronized(LOCK) {
if (mPendingJS == null) {
mPendingJS = new StringBuilder();
mPendingJS.append("javascript: ");
}
mPendingJS
.append(js)
.append("; ");
}
}
Instead of calling loadUrl() call that method. (For making this simple I used a synchronized block, but this might be better suited to a different route. Since javascript runs on its own thread, this will need to be thread safe in some way or another).
Then your WebView would have an interface like this:
public class JSInterface {
public String getPendingJS() {
synchronized(LOCK) {
String pendingCommands = mPendingJS.toString();
mPendingJS.setLength(0);
mPendingJS.append("javascript: ");
return pendingCommands;
}
}
}
That returns a String with the pending commands and clears them so they don't get returned again.
You would add it to the WebView like this:
mWebView.addJavascriptInterface(new JSInterface(), "JSInterface");
Then in your javascript you would set some interval in which to flush the pending commands. On each interval it would call JSInterface.getPendingJS() which would return a String of all of the pending commands and then you could execute them.
You could further improve this by adding a check in the execJS method to see if a EditText field exists in the WebView and is in focus. If there is one, then you would use this queueing method, but if there wasn't one in focus then you could just call loadUrl() like normal. That way it only uses this workaround when it actually needs to.
Regarding older APIs (pre 19), I used a similar method to the excepted answer, but slightly different.
First, I keep track of if the keyboard is displayed by using javascript in the webview roughly like so:
document.addEventListener( "focus", function(e){
var el = e.target;
reportKeyboardDisplayedToJava( isInputElement( el ) );
}, true);
document.addEventListener( "blur", function(e){
reportKeyboardDisplayedToJava( false );
}, true);
If the keyboard is displayed, and a js injection is attempted by the Android Java layer – I “defer” that injection. I add it to a string list, allow the user to finish up their input, and then upon the keyboard disappearing, I detect that and execute the backlog of injections.
I could implement cottonBallPaws's idea to use the internals of WebView with reflection, and got it to work for my 4.2 device. There are gracious fallbacks for Android versions older than KitKat.
The code is written in Xamarin, but it should be easily adaptable to native Java code.
/// <summary>
/// Executes a JavaScript on an Android WebView. This method offers fallbacks for older
/// Android versions, to avoid closing of the soft keyboard when executing JavaScript.
/// </summary>
/// <param name="webView">The WebView to run the JavaScript.</param>
/// <param name="script">The JavaScript code.</param>
private static void ExecuteJavaScript(Android.Webkit.WebView webView, string script)
{
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Kitkat)
{
// Best way for Android level 19 and above
webView.EvaluateJavascript(script, null);
}
else
{
try
{
// Try to do with reflection
CompatExecuteJavaScript(webView, script);
}
catch (Exception)
{
// Fallback to old way, which closes any open soft keyboard
webView.LoadUrl("javascript:" + script);
}
}
}
private static void CompatExecuteJavaScript(Android.Webkit.WebView androidWebView, string script)
{
Java.Lang.Class webViewClass = Java.Lang.Class.FromType(typeof(Android.Webkit.WebView));
Java.Lang.Reflect.Field providerField = webViewClass.GetDeclaredField("mProvider");
providerField.Accessible = true;
Java.Lang.Object webViewProvider = providerField.Get(androidWebView);
Java.Lang.Reflect.Field webViewCoreField = webViewProvider.Class.GetDeclaredField("mWebViewCore");
webViewCoreField.Accessible = true;
Java.Lang.Object mWebViewCore = webViewCoreField.Get(webViewProvider);
Java.Lang.Reflect.Method sendMessageMethod = mWebViewCore.Class.GetDeclaredMethod(
"sendMessage", Java.Lang.Class.FromType(typeof(Message)));
sendMessageMethod.Accessible = true;
Java.Lang.String javaScript = new Java.Lang.String(script);
Message javaScriptCodeMsg = Message.Obtain(null, 194, javaScript);
sendMessageMethod.Invoke(mWebViewCore, javaScriptCodeMsg);
}
I am working on a project where I have a lot of interaction between JavaScript and managed code. In fact, I need the JS application to interface with the Silverlight application in the page right from the beginning. So I need the Silverlight application to load before the JS code gets executed.
But while doing so, most of the times I get the error that the object was not found as the Silverlight application has not yet been loaded. So I need the Silverlight to load before the JS executes.
Is there a way I can put a stop on the JS application until the Silverlight loads and then start executing?
First, you need to add an event handler to the Loaded event of your app, from that event handler you can call the javascript function like this:
using System.Windows.Browser;
namespace SilverlightApplication3
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
HtmlPage.Window.CreateInstance("SomeFunction", new string[] { "parameter1", "parameter2" });
}
}
}
Note that you need the System.Windows.Browser namespace to use HtmlPage