Unfortunetly, I using a platform where WebView is not available, so, I can't make use of the simplicity of JavascriptInterface to interact from my webpage with my app.
Is there a complete (straight forward) example out there explaining how to Interact a page with my Android app using Geckoview?
I tried steps on this page (and others):
https://firefox-source-docs.mozilla.org/mobile/android/geckoview/consumer/web-extensions.html
Frankly speaking, I never saw a page hiding so many details like that.
Html page as simple as this, hosted (lets say) in "http://example.com/x.html":
<html>
<script>
function appToPage(s)
{
log('received:' + s);
}
function pageToApp(s)
{
// do something to send s to app
log('sent:' + s);
}
function log(s)
{
var x = document.getElementById('x');
x.innerHTML += '<br>' + s;
}
var i = 0;
</script>
<body>
<input type=button onclick="pageToApp('helloFromPage' + (i++))" value="SEND">
<div id="x"></div>
</body>
</html>
<script>
log('started');
</script>
Android side:
public class MainActivity extends AppCompatActivity
{
protected void onCreate(Bundle savedInstanceState)
{
...
//all the stuff needed for GeckoView and extensions
geckoSession.loadUri("http://example.com/x.html");
...
}
// when some user press some button in the browser
public void onSendButtonClick()
{
// do somenthing to call appToPage("helloFromApp");
}
// this should be called when a message arrives
public void onMessageFromPage(String s) // or whatever object as parameter (JSON, for example)
{
Log.d("msgFromPage", s)
}
...
}
I'm also using Geckoview from a month ago. To interact with your Website content you have to use a web extension.
There are two methods:
messaging
port messaging
I'm sharing an example link: https://searchfox.org/mozilla-central/source/mobile/android/examples
Related
With WebVeiw2 you can send a message to a web app running in it using WebView2Ctrl?.CoreWebView2?.PostWebMessageAsJson(message).
Is there a way of doing this in CefSharp
Create a class (I used JavascriptCallbackMessenger) to Set and Run the callbacks.
public class JavascriptCallbackMessenger
{
private IJavascriptCallback _callback;
public void SetCallBack(IJavascriptCallback callback)
{
_callback = callback;
}
public void RunCallback(string message)
{
if (_callback != null && _callback.CanExecute)
{
_callback.ExecuteAsync(message);
}
}
}
Create an instance of JavascriptCallbackMessenger and register it with the CefSharp control
CefSharpCtrl.JavascriptObjectRepository.Register(JavascriptCallbackMessengerName, _messenger, true, BindingOptions.DefaultBinder);
Set the callback in Javascript as follows (I'm not a JS developer, but this was my solution).
(async function() {
const cefSharp = (window as any).CefSharp;
await cefSharp.BindObjectAsync(JavascriptCallbackMessengerName);
window.javascriptCallbackMessenger.setCallBack(function(message: string)
{
console.log("messageHandler: " + message);
})
})();
I was using typescript, so I had to extend the Window with the newly created variable.
declare global {
interface Window { javascriptCallbackMessenger: any; }
}
Apologies, but the formatting seems to be a bit "off"!
I have an ajax request done with angular, its very basic, here is the code:
$http.get(url,data).success(function(data){
//Do Stuff with data
});
This works fine and the data is all good.
But when i inspect the request on the network tab in Chrome, and i right click on the request and click Open in new tab I get the following error:
<ExceptionMessage>
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
Here is the api controller code:
[AcceptVerbs("GET")]
public object SearchRetailers(long userId, string term)
{
List<Tuple<int,string>> res = userService.SearchRetailers(userId, term);
return res.Take(20).Select(x => new
{
label = x.Item2 + " - " + x.Item1,
value = x.Item1
});
}
The code works fine and does not throw any exceptions.
i think this might have something to do with the request headers, i just want it to work when opened in the browser regularly.
Its important to note I am using Umbraco so the apicontroller inherits UmbracoApiController class.
Also, i don't see any apiwebconfig file anywhere so i cant implement other solutions i found, and my global asax also looks significantly different than in other questions i encountered, here is how it looks:
<%# Application Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %>
<script RunAt="server">
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
private bool IsWebApiRequest()
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.Contains("/api/");
}
</script>
Using Geb 0.13.1 here. I'm not trying to integrate it with any kind of testing framework (yet), just trying to get a feel for its DSL. I just want Geb to launch a Firefox browser, point it to Google's homepage and type in 'gmail' in the search address (HTML <input/> with an id of 'q'):
class GoogleHomepage extends Page {
static url = 'http://google.com'
static at = {
title == 'Google'
}
static content = {
search {
$('input#q')
}
}
}
class MyGebDriver {
static void main(String[] args) {
FirefoxDriver firefoxDriver = new FirefoxDriver()
Browser browser = new Browser(driver: firefoxDriver)
Browser.drive(browser) {
to(GoogleHomepage)
search.value('gmail')
quit()
}
}
}
When I run this, Firefox opens up and goes to Google, but seems to choke when finding the search ('q' element) bar to set a value for:
Exception in thread "main" geb.error.RequiredPageContentNotPresent: The required page
content 'sandbox.geb.GoogleHomepage -> search: geb.navigator.EmptyNavigator' is not
present
Any ideas as to what's going on here?
You're getting an EmptyNavigator because you're looking for an input with an id of q $('input#q') try $('input[name=q]') instead.
Your GoogleHomepage looks a bit goofy to me.
What if you made it look more like this which follows the geb dsl better.
class GoogleHomepage extends Page {
url = 'http://google.com'
static at = {
waitFor() {
title == 'Google'
content
}
}
static content = {
search(required: true) { $('input#lst-ib') }
}
}
I am creating one phonegap application using Libstreaming for Video and Audio streaming. I have login and Home screen. On Home screen, there is a button "Start Streaming", on click of this button It launches the camera to start streaming which sends audio and video streams to WOWZA media server. The camera preview has one surface view on which video is getting played.
The camera preview has one Back button over SurfaceView to back on Home screen. My issue is that, On click of back button Camera preview should be destroyed and It should redirect to Home screen (Navigation from Android SurfaceView to HTML page).
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/surface_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:background="#android:color/black" >
<!-- Below surface view is used to to send rtsp audio+video stream to wowza server -->
<net.majorkernelpanic.streaming.gl.SurfaceView
android:id="#+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<!-- button to halt the camera preview and go back on to the main screen -->
<Button
android:id="#+id/btnGoBack"
android:layout_width="60dp"
android:layout_height="40dp"
android:visibility="visible"
android:text="Back"
android:layout_alignTop="#+id/surface_view"
android:layout_alignParentLeft="true"
android:textSize="14dp"
/></RelativeLayout>
I have done it myself. It looks big but quite simple. All you need to do is, define a click method of back button (which is on the SurfaceView) in your activity class (Not MainActivity as follows-
public class LiveStreamingActivity extends Activity implements RtspClient.Callback, Session.Callback, SurfaceHolder.Callback {
private static SurfaceView mSurfaceView;
private Button btnGoBack;
private SurfaceHolder mHolder;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
if (!LibsChecker.checkVitamioLibs(this))
return;
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
btnGoBack = (Button) findViewById(R.id.btnGoBack);
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(this);
/* click listener of back button */
btnGoBack.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
try {
MainActivity.isFirstTime = true;
/* on click of back button finish the current activity
* which will destroy the surfaceview and will go back on MainActivity
* by referring the Android Activity lifecycle, OnResume() method of MainActivity
* would be called where you can call javascript function directly.
*/
finish();// callback lifecycle: "onPause", "onStop", and at last "onDestroy"
} catch (Exception e) {
e.printStackTrace();
}
}
});
}}
MainActivity.java:
You have to declare a field ("isFirstTime = false") to put a logic for calling javascript function in OnResume() method. (For better understanding please refer Android activity lifecycle).
public class MainActivity extends CordovaActivity {
public static boolean isFirstTime = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loadUrl(launchUrl);
}
#Override public void onResume(){
super.onResume();
System.out.println("onResume main activity");
if(isFirstTime){System.out.println("Inside onResume");
loadUrl("javascript:launchProfile()");
}
}
#Override public void onPause(){
super.onPause();
System.out.println("onPause main activity");
isFirstTime = false;
}}
Javascript Function:
I am using AngularJS. So I have to get the $location object to inject the path of profilePage.
function launchProfile(){
var e = document.getElementById('loginScreen');/*'loginScreen' is login form id */
var $injector = angular.element(e).injector();
var $location = $injector.get('$location');
$location.path("/profilePage");}
app.js (Angularjs routing):
vcApp = angular.module('VCMobApp', ['ngRoute']);vcApp.config(['$routeProvider', function ($routeProvider){
$routeProvider.when('/', {templateUrl: 'screens/login.html', controller: 'loginPageCntrl' });
$routeProvider.when('/profilePage', {templateUrl: 'screens/profilePage.html', controller: 'profilePageCntrl'});
$routeProvider.otherwise({ redirectTo: '/' });}]);
In case you are not using AngularJS, then directly you can call your html page using window.location.href='../profilePage.html' inside launchProfile() function itself (skip angularjs code then).
I'm implementing an API via a web service, using Nancy.
I'd like to have a /help or /docs page that programmatically lists all of the available routes, so that I can provide API users with automatically generated/updated documentation.
Any ideas on how to accomplish this? (Inside a route handler, "this.routes" gives access to a collection of defined routes - but only on the current NancyModule. I'd need a programmatic way to list all registered routes, not just ones in the current module)
Not exactly what you need, but there is also a built in dashboard panel in Nancy. To enable it do:
public class CustomBootstrapper : DefaultNancyBootstrapper
{
protected override DiagnosticsConfiguration DiagnosticsConfiguration
{
get { return new DiagnosticsConfiguration { Password = #"secret"}; }
}
}
And then you can access it on {yournancyapp}/_nancy
https://github.com/NancyFx/Nancy/wiki/Diagnostics
You can do it by taking a dependency on the IRouteCacheProvider and calling GetCache - we actually do this in one of our demos in the main repo:
https://github.com/NancyFx/Nancy/blob/master/src/Nancy.Demo.Hosting.Aspnet/MainModule.cs#L13
Example of how to use IRouteCacheProvider like #grumpydev mentioned in this answer:
// within your module
public class IndexModule : NancyModule
{
// add dependency to IRouteCacheProvider
public IndexModule(Nancy.Routing.IRouteCacheProvider rc)
{
routeCache = rc;
Get["/"] = GetIndex;
}
private Nancy.Routing.IRouteCacheProvider routeCache;
private dynamic GetIndex(dynamic arg)
{
var response = new IndexModel();
// get the cached routes
var cache = routeCache.GetCache();
response.Routes = cache.Values.SelectMany(t => t.Select(t1 => t1.Item2));
return response;
}
}
public class IndexModel
{
public IEnumerable<Nancy.Routing.RouteDescription> Routes { get; set; }
}
You can get the routing information like Path and Method from the list of Nancy.Routing.RouteDescription. For example with this view:
<!DOCTYPE html>
<html>
<body>
<p>Available routes:</p>
<table>
<thead><tr><th>URL</th><th>Method</th></tr></thead>
<tbody>
#Each.Routes
<tr><td>#Current.Path</td><td>#Current.Method</td></tr>
#EndEach
</tbody>
</table>
</body>
</html>