Determine if a WebView is invisible using only JS - javascript

I have an Android test app with a webView like so:
<WebView
android:alpha="0"
android:id="#+id/myWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
My Main activity loads a page that I have JS content running. JS is enabled using:
webSettings.setJavaScriptEnabled(true);
There is a button on my app that toggles the alpha value from 0 to 1 (show / hide webView).
Is there any "creative" way of detecting the change on the JS side?
Things I tried:
Checking requestAnimationFrame frame rate changes.
Visibility API
Update:
Clarification, I'm looking for a JS Only solution, the actual JS code is an SDK used inside a WebView environment.
I have no control over the Android app, full control over the WebView content.

You could subclass android's WebView, override its setAlpha method and add some logic to let the webpage know the current alpha value.
Here is a simplified example:
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
public class MyWebView extends WebView {
public MyWebView(Context context) {
super(context);
init();
}
public MyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public MyWebView(Context context,
AttributeSet attrs,
int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
#SuppressLint("SetJavaScriptEnabled")
private void init() {
getSettings().setJavaScriptEnabled(true);
}
#Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
propagateAlphaToWebPage(alpha);
}
private void propagateAlphaToWebPage(float alpha) {
// setWebViewAlpha is a JS function that should be defined in your html's js
String jssnippet = "setWebViewAlpha("+alpha+");";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
evaluateJavascript(jssnippet, null);
} else {
loadUrl("javascript:"+jssnippet);
}
}
}
Other related questions you may find helpful:
Android WebView - detect whether a JS function is defined
Android Calling JavaScript functions in WebView
Declaring a custom android UI element using XML

Below is complete code which will tell to JavaScript if WebView is visible or not.
Steps:
Create a layout containing a ToggleButton and WebView.
Load your html using htmlWebView.loadUrl("file:///android_asset/index.html"); and related code
Create a function onToggleButtonPressed() which will be called onClick of toggle button
In onToggleButtonPressed() show/hide WebView and at the same time pass the status to JavaScript using htmlWebView.evaluateJavascript() method. Pass visibilityStatusFromAndroid() to JavaScript
Get the status in JavaScript from visibilityStatusFromAndroid() function
Android Layout xml code:
<ToggleButton
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#color/red"
android:onClick="onToggleButtonPressed"
android:text="Button1"
android:textOn="Show"
android:textOff="Hide"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="148dp"
tools:layout_editor_absoluteY="0dp" />
<WebView
android:id="#+id/webView"
android:layout_width="368dp"
android:layout_height="447dp"
android:layout_alignParentStart="true"
android:layout_below="#+id/button"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="56dp"
android:layout_alignParentLeft="true" />
Java Code:
WebView htmlWebView; // put it outside onCreate() to make it accessible in onToggleButtonPressed()
htmlWebView = (WebView)findViewById(R.id.webView);
htmlWebView.setWebViewClient(new WebViewClient());
htmlWebView.loadUrl("file:///android_asset/index.html");
WebSettings webSetting = htmlWebView.getSettings();
webSetting.setJavaScriptEnabled(true);
public void onToggleButtonPressed(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
boolean on = ((ToggleButton) view).isChecked();
String visibility;
if (on) {
htmlWebView.setVisibility(View.GONE);
visibility = "NOT VISIBLE";
htmlWebView.evaluateJavascript("javascript: " +
"visibilityStatusFromAndroid(\"" + visibility + "\")", null);
} else {
htmlWebView.setVisibility(View.VISIBLE);
visibility = "VISIBLE NOW";
htmlWebView.evaluateJavascript("javascript: " +
"visibilityStatusFromAndroid(\"" + visibility + "\")", null);
}
}
}
JavaScript file:
function visibilityStatusFromAndroid(message) {
if (message === "NOT VISIBLE") {
console.log("webview is not visible");
// do your stuff here
} else if (message === "VISIBLE NOW") {
console.log("webview is visible now");
// do your stuff here
}
}
That's it. You are DONE!
Here is Video of working code.
Update:
There is one option that you can use to detect visibility only using JavaScript is document.hasFocus() but this will not work if there is any EditText on the screen and user will focus that EditText.
Just in case below is the code you can use in for document.hasFocus():
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>TEST</title>
<style>
#message { font-weight: bold; }
</style>
<script>
setInterval( checkPageFocus, 200 ); // you can change interval time to check more frequently
function checkPageFocus() {
var info = document.getElementById("message");
if ( document.hasFocus() ) {
info.innerHTML = "The document is VISIBLE.";
} else {
info.innerHTML = "The document is INVISIBLE.";
}
}
</script>
</head>
<body>
<h1>JavaScript hasFocus example</h1>
<div id="message">Waiting for user action</div>
</body>
</html>

There is (currently) no JS-only solution.
The content of the WebView isn't supposed to be able to get information about its environment.
The only exception is information which was volunteered by the environment itself.
This would contradict basic sandboxing and could lead to security issues.
As there is no standard API that would give you the information you want you would have to change the environment yourself to expose it to the WebView.
If you can't change the environment what you are requesting is (theoretically) impossible with the current browsers/android.
#Hack To find an actual loophole we could exploit to get the alpha value we would need more detailed information about the JS SDK, the environment and how much influence on the android / webview side of the app you have. And even then after investing much effort the result would most likely be the same.

Related

Can I mute audio for a WebGl container?

I want to add an html "audio off" button to a webpage containing a WebGL Unity game.
I know I can do adding a button to my webgl game, but I want to do with outside html button.
My code:
<img id="btnaudio" src="images/audio-on.png" style="height: 24px;" data-toggle="tooltip" title="Audio On/Off" onclick="switchAudio();" />
<div id="gameContainer" ></div>
function switchAudio() {
document.getElementById("gameContainer").muted = !document.getElementById("gameContainer").muted;
var img = document.getElementById('btnaudio');
if(document.getElementById("gameContainer").muted)
img.src='images/audio-on.png';
else
img.src='images/audio-off.png';
}
But it not work.. audio keep playing (but image swap so, js code is executed).
Thanks
I think one way would probably be to simply turn off the camera's AudioListener component.
You can call methods in your Unity scene from the page's JavaScript e.g. like
c# code
public class MuteController : MonoBehaviour
{
// For storing previous volumes
private readonly Dictionary<AudioListener, float> mutedListeners = new Dictionary<AudioListener, float>();
public void Mute()
{
foreach (var listener in FindObjectsOfType<AudioListener>())
{
mutedListeners.Add(listener, listener.volume);
listener.volume = 0;
}
}
public void Unmute()
{
foreach (var kvp in mutedListeners)
{
kvp.Key.volume = kvp.Value;
}
mutedListeners.Clear();
}
}
Put it on a GameObject in your scene with the name MuteController.
And then call these methods from the JavaScript code like e.g.
unityInstance.SendMessage('MuteController', 'Mute');
and
unityInstance.SendMessage('MuteController', 'Unmute');

What is the Vaadin 8 way of adding code to the html head tag?

Other SO answers suggest overriding ApplicationServlet.writeAjaxPageHtmlHeader, but I cannot find those classes and methods in Vaadin 8.
I cannot find anything similar in com.vaadin.server.VaadinServlet or com.vaadin.ui.UI.
There is the #JavaScript annotation, but if I put that on my UI class, the script would be loaded for every page of my application. I need it only on one specific page.
The initial HTML page is called bootstrap page in Vaadin. There is some documentation that hints you to right direction in Book of Vaadin.
In Vaadin 8, you need to add BootstrapListener to session. You can get created sessions by adding SessionInitListener in VaadinServlet.
Register Session
This example is using Vaadin with Spring Boot but the same principle applies when not using Spring Boot.
#Component("vaadinServlet")
#WebServlet(urlPatterns = "/*", name = "BootstrapVaadinServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = BoostrapUi.class, productionMode = false)
public class BootstrapVaadinServlet extends SpringVaadinServlet {
private static final Logger logger = LoggerFactory.getLogger(BootstrapVaadinServlet.class);
#Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
getService().addSessionInitListener(this::addBoostrapListenerOnSessionInit);
}
private void addBoostrapListenerOnSessionInit(SessionInitEvent sessionInitEvent) {
sessionInitEvent.getSession().addBootstrapListener(new AppBootstrapListener());
}
}
Implement html head tag modification
public class AppBootstrapListener implements BootstrapListener {
#Override
public void modifyBootstrapFragment(BootstrapFragmentResponse bootstrapFragmentResponse) {
}
#Override
public void modifyBootstrapPage(BootstrapPageResponse res) {
Elements headTags = res.getDocument().getElementsByTag("head");
Element head = headTags.get(0);
head.appendChild(metaExample(res.getDocument()));
}
private Node metaExample(Document document) {
Element meta = document.createElement("meta");
meta.attr("author", "Me");
return meta;
}
}
If using add-on is ok, try HeaderTags
Overview
Using this add-on, you can define tags to add to the host page by
adding annotation to your UI class.
Sample from add ons usage example
#MetaTags({
// Replaces the Vaadin X-UA-Compatible header
#Meta(httpEquiv = "X-UA-Compatible", content = "hello"),
#Meta(name = "test", content = "test") })
// And showing how to create a link tag as well
#Link(rel = "foobar", href = "about:blank")
public class DemoUI extends UI {
...
}

Remove generic Tapestry JavaScript and CSS

I have created a new Tapestry 5.3 project using Maven. I noticed that Tapestry adds tons of different JS and CSS files to all pages:
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/default.css"/>
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/ctx/layout/layout.css"/>
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/tapestry-console.css"/>
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/t5-alerts.css"/>
<link type="text/css" rel="stylesheet" href="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/tree.css"/>
<script src="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/underscore_1_3_3.js" type="text/javascript"></script>
<script src="/tutorial1/assets/1.0-SNAPSHOT-DEV/tapestry/scriptaculous_1_9_0/prototype.js" type="text/javascript"></script>
And many, MANY more...
Are these required for my site to work properly? If not, how can I remove them? I am pretty comfortable to write JS myself and I do not need Tapestry to add anything for me.
It's funny: Tapestry provides very rich functionality to override default service behavior but not in this case.
The main culprit JavaScriptSupport is created on the fly and can't be decorated.
MarkupRendererFilter javaScriptSupport = new MarkupRendererFilter() {
public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) {
DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
//Surprise!;)
JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker, javascriptStackSource, javascriptStackPathConstructor);
environment.push(JavaScriptSupport.class, support);
renderer.renderMarkup(writer);
environment.pop(JavaScriptSupport.class);
support.commit();
}
};
So the only way is to patch sources (as we did many times), or to try to override the javascriptStackSource which goes as param to JavaScriptSupportImpl:
Your AppModule.java
#Decorate(serviceInterface = JavaScriptStackSource.class)
public JavaScriptStackSource decorateJavaScriptStackSource(JavaScriptStackSource original) {
return new MyJavaScriptStackSource(original);
}
MyJavaScriptStackSource.java
public class MyJavaScriptStackSource implements JavaScriptStackSource {
// This bunch of stacks comes from got5
private Set<String> SKIP = new HashSet<String>(Arrays.asList("Slider", "AjaxUploadStack", "DataTableStack", "FormFragmentSupportStack", "FormSupportStack",
"SuperfishStack", "JQueryDateFieldStack", "GalleryStack"));
private class JavaScriptStackWraper implements JavaScriptStack {
private final JavaScriptStack original;
JavaScriptStackWraper(JavaScriptStack original) {
if (original != null) {
System.out.println("Wrap " + original.getClass().getName());
}
this.original = original;
}
#Override
public List<String> getStacks() {
return original != null ? original.getStacks() : Collections.<String>emptyList();
}
#Override
public List<Asset> getJavaScriptLibraries() {
return original != null ? original.getJavaScriptLibraries() : Collections.<Asset>emptyList();
}
// Always return empty list
#Override
public List<StylesheetLink> getStylesheets() {
return Collections.<StylesheetLink>emptyList();
}
#Override
public String getInitialization() {
return original != null ? original.getInitialization() : null;
}
}
private final JavaScriptStackSource original;
public MyJavaScriptStackSource(JavaScriptStackSource original) {
this.original = original;
}
#Override
public JavaScriptStack getStack(String name) {
JavaScriptStack stack = original.getStack(name);
if (!SKIP.contains(stack.getClass().getSimpleName())) {
return new JavaScriptStackWraper(stack);
}
return new JavaScriptStackWraper(null);
}
#Override
public List<String> getStackNames() {
return original.getStackNames();
}
}
It's a big piece of shitty code but it works.. I'd like to have more control of whats displaying in my pages in Tapestry.
Part of the advantage of Tapestry is the number of components that provide DHTML and Ajax behaviors out of the box without writing any JavaScript, but just configuring components.
It is possible to disable this, but that means many components you might like to use, such as Zone, will be broken. Likewise, all client-side input validation will be gone. I do have clients that have done this, but it is not a small undertaking.
Basically, you can override Tapestry's "core" JavaScriptStack. There isn't a FAQ for this, because it is not frequently asked. It is also not for an absolute beginner, more a journeyman thing (it is relatively easy to override things in Tapestry due to its inversion of control container, but knowing WHAT to override is trickier).
In any case, Tapestry 5.4 is well underway and is changing Tapestry's JavaScript to be much lighter and much more modular, as well as giving you the choice between Prototype (mostly for compatibility in existing Tapestry projects) or jQuery. Even then, however, there will be some amount of JavaScript built into the framework.

Add JS to header despite JavascriptFilteredIntoFooterHeaderResponse

I order my imported CSS and JS with a JavascriptFilteredIntoFooterHeaderResponse. With this Class goes all my CSS in the header and all my JS to a separate container near the </body> tag. But now i need to add one JS to the header but wicket pushed every JS to the footer. Knows anybody a solution for this? JavascriptFilteredIntoFooterHeaderResponse is final and can't be overriden.
WicketApplication
#Override
public void init()
{
super.init();
setHeaderResponseDecorator( new IHeaderResponseDecorator()
{
#Override
public IHeaderResponse decorate( IHeaderResponse response )
{
return new JavascriptFilteredIntoFooterHeaderResponse( response, FOOTER_FILTER_NAME );
}
} );
}
BasePage.java
public BasePage()
{
add( new HeaderResponseFilteredResponseContainer( FOOTER_FILTER_NAME, FOOTER_FILTER_NAME ) );
}
BasePage.html
<body>
...
<div wicket:id="footerBucket" />
</body>
You don't need to override JavascriptFilteredIntoFooterHeaderResponse. Just use org.apache.wicket.resource.filtering.HeaderResponseContainerFilteringHeaderResponse with the following constructor:
HeaderResponseContainerFilteringHeaderResponse(IHeaderResponse response,
String headerFilterName, IHeaderResponseFilter[] filters)
For example, if you write:
IHeaderResponseFilter[] filters = new IHeaderResponseFilter[] {
new CssAcceptingHeaderResponseFilter(HEADER_FILTER_NAME),
new JavascriptAcceptingHeaderResponseFilter(FOOTER_FILTER_NAME) };
return new HeaderResponseContainerFilteringHeaderResponse(response,
HEADER_FILTER_NAME, filters);
it will be the same JavascriptFilteredIntoFooterHeaderResponse that you use in your code.
Here's an example of anonymous filter class that you can use instead of CssAcceptingHeaderResponseFilter to accomplish your task. File "script-for-the-header.js" is the script that you'd like to have in the header.
new CssAcceptingHeaderResponseFilter(HEADER_FILTER_NAME) {
#Override
public boolean acceptReference(ResourceReference ref) {
if (!Strings.isEmpty(ref.getName()) && ref.getName().equals("script-for-the-header.js")) {
return true;
} else {
return super.acceptReference(ref);
}
}
}
To better organize your code you may also consider overriding HeaderResponseContainerFilteringHeaderResponse (if you haven't guessed yet, it's also the parent of JavascriptFilteredIntoFooterHeaderResponse). See source code in JavascriptFilteredIntoFooterHeaderResponse.java for details.

Finding a div in my SharePoint 2010 Web Part

I have a Web Part that dynamically inserts div tags written in VS2010 using C#. I want to implement mouseover events for these div's. When the Web Part is deployed onto SP2010, my JavaScript is not able to find these div's when I just search for them with the control id that I have specified.
When I checked the page source, I found that some tags like ct100_m_g_ are prefixed to the control id that I have specified.
How can I guess these ids?
The ctlxxx stuff is automatically prepended to the control's ID by ASP.NET, to generate the client ID.
If you want to set a deterministic client ID, you can set the ClientID property instead of the ID property. See http://msdn.microsoft.com/en-us/library/system.web.ui.control.clientid.aspx
In webparts you can use the client ID to find your controls in javascript. Take a look at this example and it will become clear:
using System;
using System.Text;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
namespace MyDemo.WebParts
{
public class MyWebPart : WebPart
{
private TextBox txtInput;
private TextBox txtOutput;
private Button btnDoSomething;
protected override void CreateChildControls()
{
base.CreateChildControls();
txtInput = new TextBox();
Controls.Add(txtInput);
txtOutput = new TextBox();
txtOutput.ReadOnly = true;
Controls.Add(txtOutput);
btnDoSomething = new Button();
btnDoSomething.Text = "Do Something";
btnDoSomething.OnClientClick = string.Format("DoSomething('{0}', '{1}'); return false;", txtInput.ClientID, txtOutput.ClientID);
Controls.Add(btnDoSomething);
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// Check if the script is already registered, this is necessary because the webpart can be
// added multiple times to the same page
if (!Page.ClientScript.IsClientScriptBlockRegistered("MyWebPartScript"))
{
StringBuilder sb = new StringBuilder();
sb.Append("function DoSomething(inputControlID, outputControlID){");
sb.Append(" var inputControl = document.getElementById(inputControlID);");
sb.Append(" var outputControl = document.getElementById(outputControlID);");
sb.Append(" outputControl.value = inputControl.value;");
sb.Append("}");
Page.ClientScript.RegisterClientScriptBlock(GetType(), "MyWebPartScript", sb.ToString(), true);
}
}
protected override void RenderContents(System.Web.UI.HtmlTextWriter writer)
{
EnsureChildControls();
writer.Write("<table>");
writer.Write("<tr><td>");
txtInput.RenderControl(writer);
writer.Write("</td><td>");
txtOutput.RenderControl(writer);
writer.Write("</td></tr><tr><td colspan=\"2\">");
btnDoSomething.RenderControl(writer);
writer.Write("</td></tr></table>");
}
}
}
You could give each of your DIVs a class when generating them. THen, just use the class name to select them and add the event handler.

Categories