In my iOS app, I have 2 WKWebView controls to simulate parent/child relationship of 2 tab/window in our web app so that we can reuse all those javascripts. Problem is that there is no generic communication channel between these 2 WKWebViews.
Web Architecture:
- The first/master window loads our internal javascript functions with links opening in second/child window.
- The second/child window loads third party web content.
- The third party content communicates with our system using javascript form source window (using window.opener / window.parent API bridge).
Now, in the native app, this is what I'm doing to reuse existing javascripts and simulate web archtecture -
a. Below js call from the webpage in 1st WKWebView -
window.open("http://localhost/child.html", "myWindow", "width=200,height=100");
b. The WKUIDelegate intercepts this call (a) and opens 2nd WKWebView -
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
// Here, window.open js calls are handled by loading the request in another/new web view
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
WebViewController *webViewController = [mainStoryboard instantiateViewControllerWithIdentifier:#"WebViewController"];
webViewController.loadURLString = navigationAction.request.URL.absoluteString;
// Show controller without interfering the current flow of API call, thus dispatch after a fraction of second
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.navigationController pushViewController:webViewController animated:YES];
});
return nil;
}
c. In web/computer browser, the child window of web app is able to access source js functions using window.opener and window.parent
How to achieve the same functionality in WKWebViews? In other words, js functions in 2nd WKWebView could call js functions from parent/source WKWebView?
You definitely should not return nil from webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:, but an instance of your WKWebView.
Unfortunately, I have no way to verify this right now. But as far as I remember, the trick is also to not ignore WKWebViewConfiguration argument from webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures: delegate method, but create a second WKWebView instance, passing this argument as one of the initWithFrame:configuration: parameters.
Also, according Apple documentation, looks like you don't need to load the request yourself if you're returning correct web view instance. WebKit should handle this for you.
Overall your code should look similar to:
- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
WKWebView *childWebView = [[WKWebView alloc] initWithFrame:CGFrameZero configuration:configuration];
WebViewController *webViewController = [[WebViewController alloc] initWithWebView: childWebView];
// looks like this is no longer required, but I can't check it now :(
// [childWebView loadRequest:navigationAction.request];
// Show controller without interfering the current flow of API call, thus dispatch after a fraction of second
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.navigationController pushViewController:webViewController animated:YES];
});
return childWebView;
}
A simpler way to accomplish your task will be to have one WKWebView, and use HTML to separate header and detail. The simplest solution is to have one HTML document with the header and all of the details, but if you need to add details dynamically that can also be done use the DOM. Reusing Javascript for both the header and detail will be easiest with this approach.
Related
Is there any chance to access the dom elements of a "x-ms-webview" ? For my cordova application, I want to use a custom login page. That's why I need to load a webview in the background, access the login fields and "click" the submit button via javascript.
For iOS and the UIWebview this working (I could not believe) really smooth and nice. Is there any chance to realize this for windows 10 as well ? This is what I have so far (not really much):
function onWebviewLoadedLoginPage(ev) {
webview.removeEventListener("MSWebViewDOMContentLoaded", onWebviewLoadedPage);
// --> Here I need to access the dom elements of the loaded webview !?
}
var webview = document.createElement("x-ms-webview");
document.body.appendChild(webview);
webview.addEventListener("MSWebViewDOMContentLoaded", onWebviewLoadedPage);
webview.navigate(url);
In objective-c I can set the login credentials directly to the dom elements quite easy:
NSString* statement = [NSString stringWithFormat:#"document.getElementById('%#').value = '%#'", self->usernameInputFieldId, self->username];
[webView stringByEvaluatingJavaScriptFromString:statement];
Thanks in advance !!!
I can see that you need to execute javascript inside the webview once the content is loaded. In that case, you can use invokeScriptAsync method of the webview element. invokeScriptAsyc accepts two parameters a function's name and that function's parameters. However, It can only call a javascript function that is written inside the webview. However, eval() is used to execute javascript code that is written in form of string. Hence to execute your javascript code your function should be:
function onWebviewLoadedLoginPage(ev) {
webview.removeEventListener("MSWebViewDOMContentLoaded", onWebviewLoadedPage);
// --> Here I need to access the dom elements of the loaded webview !?
var javascriptcode = "document.getElementByID('#myelement')"; // Your javascript code in the form of string
var injectedJavascript = webview.invokeScriptAsync('eval', javascriptcode);
injectedJavascript.start();
}
I am using Awesomium library for UI of my C++ application ("plain" C++, no .NET, precompiled headers etc.).
Everything works fine, I am able to send events from C++ application to JavaScript in Awesomium and vice versa. UI responds to clicks etc. thanks to webCore->Update().
The problem is, sometimes errors in JavaScript occurs. And I would like to see them like in e.g. Edge/Chrome/Firefox (where you can press F12 to see console, debugger etc.).
I know that it's possible to use remote debugger for Awesomium with appropriate WebConfig so I've tried:
Awesomium::WebConfig webConfig;
webConfig.remote_debugging_host = Awesomium::WSLit("127.0.0.1");
webConfig.remote_debugging_port = 9222;
//create the WebCore singleton with configuration
webCore = Awesomium::WebCore::Initialize(webConfig);
//create a new WebView instance with a certain width and height
view = webCore->CreateWebView(width, height, 0, Awesomium::kWebViewType_Window);
view->set_parent_window(*windowHandle->getHWND());
view->set_view_listener(this);
//create a global js object named 'app'
Awesomium::JSValue result = view->CreateGlobalJavascriptObject(Awesomium::WSLit("app"));
//bind our method dispatcher to the WebView
view->set_js_method_handler(this);
if (result.IsObject()) {
//bind our custom method to it
Awesomium::JSObject& appObject = result.ToObject();
appObject.SetCustomMethod(Awesomium::WSLit("sendEvent"), false);
}
Now in Chrome at http://localhost:9222/# I see my session:
I am able to e.g. select UI elements inside my C++ application with debugger.
But I cannot see anything in my debugger's console tab in Chrome (I do use console.log("Hello!") inside website that is loaded into Awesomium):
p.s. The debugger window is not 100% height (as you see on screen). Can I fix it somehow?
p.s.2. Google Chrome is not my browser of choice. Can I use another one for debugging Awesomium UI?
JavaScript error/debug messages are not visible in the remote debugger of Awesomium (v1.7.5.1).
The only way I found was by listening to the OnAddConsoleMessage() method in the WebViewListener::Load class.
You have to make your own subclass and register it to your webview.
#include <Awesomium/WebViewListener.h>
class MyViewListender : public WebViewListener::View {
// ... All the overridden WebViewListener::View methods go here
void OnAddConsoleMessage(Awesomium::WebView* caller,
const Awesomium::WebString& message,
int line_number,
const Awesomium::WebString& source)
{
cout << "Console: " << message << endl;
}
};
Register it:
MyViewListender* my_view_listener = new MyViewListender();
view->set_view_listener(my_view_listener);
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
I have a Cocoa app with an embedded WebView. I've set up my delegate to respond to a number of custom Objective-C functions that can be called from JavaScript. For example, in JavaScript I will be able to call something like console.log()
I set the delegate as follows:
- (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame {
[self.webView.windowScriptObject setValue:self forKey:#"console"];
}
Now when I navigate to another page and then use the back button (via WebView goBack:), the above method is not called and I can't set the console object.
However, the JavaScript object still exists (typeof(console) returns object), but calling any function on it does not have any effect.
First of all, does anybody know why didClearWindowObject: is not called when I invoke goBack:? Any suggestions how I can debug the JavaScript object?
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.