I'm working on making a browser as a hybrid app using worklight framework for Android. I implemented my address bar as an input element which received the user input and pass the arguments to the webview to load the page.
However, I cannot figure out how to do the reverse: whenever the user click on a link in webview, I want the address bar to change to the new location.
Are you implementing a native page that is opened? If so, take a look at ChildBrowser, that basically does the same thing. It has a TextView being used as an address bar. You may decide to use it, or get the bits and pieces you want out of it. Regardless, I would image what you want to do something like this. By overriding the onLoadResource in the WebViewClient, you should be able to grab the url and change your TextBox.
In response to the comment below: inside your environment's main js file in the wlEnvInit() function:
function wlEnvInit(){
wlCommonInit();
// Environment initialization code goes here
document.onclick=manageLinks;
}
Then in this function get the url and set the text of your input element:
function manageLinks(event) {
var link = event.target;
//go up the family tree until we find the A tag
while (link && link.tagName != 'A') {
link = link.parentNode;
}
if (link) {
var url = link.href;
console.log("url = " + url);
//You can decide if you want to separate external or
//internal links, depending on your application
var linkIsExternal = ((url.indexOf('http://') == 0) || (url.indexOf('https://') == 0));
if (linkIsExternal) {
myInput.setText(url);
return false;
}
}
return true;
}
Inside of your WebView, inside the plugin, intercept the URL like this:
webview.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
//use this area to set your input. Depending on how you
//implemented your plugin, you may need to return this value
//back to your main activity
Toast.makeText(cordova.getActivity(), "Loading: " + url, Toast.LENGTH_LONG).show();
}
});
Have you try to get the url from the href of and assign to the input variable and do the get/post? I know that it is possible in SDK i figure it dont will be harder in a framework. You can store the hiperlinks in a array with a parser or something similar.
example pseudocode:
When_hiperlink_clicked: //could be like a listener (search about it)
url = hiperlink.getURL("myHiperlink");
myinput.setText(url);
execute_input_bar_action();
Is difficult to figure out without code or something more, sorry.
Related
I'm building a little browser app using android webview and I've been using window.getSelection() in javascript to get the nature of any text selected by the user and show a custom context menu based on the type of the selection i.e. whether it's a range, a carat, whether it's in a contenteditable etc.
This works fine unless the selection is in an iframe, then the browser security measures kick in and prevent me sniffing what has been selected using window.getSelection(). How can I workaround this?
Ideally I need a way to get better information about what was selected from the webview or if that's not possible I need a way to sniff whether the selection occurred in an iframe so I can disable my custom context menu logic and fallback to the default android context menu.
UPDATE/FURTHER CLARIFICATION 07/05/2019:
Seems I wasn't clear enough in my initial description...
My goal is to have a visually and functionally custom menu when selecting content in the webview that can cut/copy/paste as the standard context menu does in any part of the page/iframes etc. e.g.
I realised my original approach using javascript to detect the type of selection and to perform the cut/copy/paste was wrong because it will be blocked by cross origin security in iframes.
What I need is a native android/webview based approach. I've discovered that I can sniff the type of selection in the webview by looking at the items in mode.getMenu() on onActionModeStarted. This will allow me to show the correct buttons in my custom menu UI but I have been unable to manually trigger the same logic that gets called when cut/copy/paste is clicked. I thought I found the solution with webView.performAccessibilityAction(AccessibilityNodeInfo.ACTION_CUT, null); but this doesn't work for some reason so I guess my question really is how can I manually trigger cut/copy/paste on the selected text from webview without using javascript? or any other approach that will allow me to have a custom selection menu with lots of options based on what was selected without hitting the browser security limitations?
Okay I figured out how roughly how to do this.
Step 1) In your activity, override onActionModeStarted and check the menu items available in the default context menu. This gives you a clue as to what the type of selection is and which buttons you will need to show in your custom menu. Also it gives you a reference to the item ID which you can use to later to trigger the action e.g.
systemSelectionMenu = mode.getMenu(); // keep a reference to the menu
MenuItem copyItem = systemSelectionMenu.getItem(0); // fetch any menu items you want
copyActionId = copyItem.getItemId(); // store reference to each item you want to manually trigger
Step 2) Instead of clearing the menu, use setVisible() to hide each menu item you want a custom button for e.g.
copyItem.setVisible(false);
Step 3) In your custom button onclick event you can trigger the copy action using:
myActivity.systemSelectionMenu.performIdentifierAction(myActivity.copyActionId, 0)
You can retrieve iframe's selection only if it has the same origin. Otherwise, you have no chances to track any iframe's events(clicks, touches, key presses, etc.).
const getSelectedText = (win, doc) => {
const isWindowSelectionAvailable = win && typeof win.getSelection != "undefined";
if (isWindowSelectionAvailable) {
return win.getSelection().toString();
}
const hasDocumentSelection = doc && typeof doc.selection != "undefined" && doc.selection.type == "Text";
if (hasDocumentSelection) {
return doc.selection.createRange().text;
}
return '';
}
const doIfTextSelected = (win, doc, cb) => () => {
const selectedText = getSelectedText(win, doc);
if (selectedText) {
cb(selectedText);
}
}
const setupSelectionListener = (win, doc, cb) => {
doc.onmouseup = doIfTextSelected(win, doc, cb);
doc.onkeyup = doIfTextSelected(win, doc, cb);
}
const getIframeWinAndDoc = (iframe) => {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
const win = iframe.contentWindow || iframe.contentDocument.defaultView;
return { win, doc };
} catch (e) {
console.error(`${e}`);
return {};
}
}
const callback = console.log;
setupSelectionListener(window, document, callback);
document.querySelectorAll('iframe').forEach(iframe => {
const { win, doc } = getIframeWinAndDoc(iframe, console.log);
// Only for same origin iframes due to https://en.wikipedia.org/wiki/Same-origin_policy
if (win && doc) {
setupSelectionListener(win, doc, callback);
}
})
<h3>Select me</h3>
<div class="container">
<iframe src="https://teimurjan.github.io"></iframe>
</div>
This issue varying from browser to other if it works with internet explorer so it may fall with chrome
Try this
App.util.getSelectedText = function(frameId) {
var frame = Ext.getDom(frameId);
var frameWindow = frame.contentWindow;
var frameDocument = frameWindow.document;
if (frameDocument.getSelection) {
return frameDocument.getSelection();
}
else if (frameDocument.selection) {
return frameDocument.selection.createRange().text;
}
};
Hope it runs fine
Main problem is the window.getSelection() will return selection only for the main context/window. As iframe is the other window and other context, you should call getSelection() from iframe which is "current".
My question is exactly the same as this one but for Android and not iOS.
Get URL from remote URL in webview and open it in safari
Anyone have an idea. I am creating a cross-platform app and I have used the Clayton's answer to get it to work for iOS with some tweaks to open with a controller. But when trying different methods on Android and it is not working. This is as close as I have gotten (which is what Aaron provided on that same page) and it is not quite right as it opens the remote web page in a new browser window as well in the apps webview:
$.floorView.addEventListener('load', function(e) {
if (e.url.indexOf("http") !== -1) {
// stop the event
e.bubble = false;
// stop the url from loading
$.floorView.stopLoading();
// open
Ti.Platform.openURL(e.url);
}
});
Thanks!
I'd listen to the beforeload event, although I'm not 100% sure if you can actually prevent the Webview from still continuing the load as well.
Another way would be to intercept these links via JS you load or inject (evalJS()) in the webpage. Then fire a Ti.App event and respond to it in Titanium.
The Titanium.UI.Webview has a specific property for intercepting links: onlink.
This is not implemented as an event because it is a callback and needs to return a boolean to tell the Webview whether or not to load the URL of the link.
Oddly, setting the onlink callback right away makes the URL immediately load in Safari, so I did it this way:
$.webview.addEventListener('load', function(e) {
$.webview.onlink = function(e) {
Ti.Platform.openURL(e.url);
return false;
};
});
You can of course check the e.url string and decide whether to open it internally or externally.
I think I may have figured it out. Thanks to those whose ideas and suggestions lead to this code.
It appears to be working as I want on iOS and Android. Any suggestions or issues that you guys have I would appreciate the feedback. Thanks!
if ("iOS") {
$.webView.addEventListener('beforeload', function(e) {
if (e.navigationType == Titanium.UI.iOS.WEBVIEW_NAVIGATIONTYPE_LINK_CLICKED) {
// stop the event
e.bubble = false;
// stop the url from loading
$.webView.stopLoading();
//opens up the clicked URL for bill in new webView
var link = e.url;
var args = {url: link,};
// open link in my default webView for iOS
var newWebView=Alloy.createController('defaultWebView', args).getView();
newWebView.open();
}
});
}
else if ("Android") {
$.webView.addEventListener('beforeload', function(e) {
if (e.url.indexOf("http") !== -1) {
function Parser(text) {
var html = text;
var urlRegex = /((http|https):\/\/(\w+:{0,1}\w*#)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%#!\-\/]))?)/gi;
this.getHTML = function() {
return html;
};
} // end Parser
var parser = new Parser(e.url);
html = parser.getHTML();
if (html != "url of $.webView") {
// stop it from loding in current webView
$.webView.stopLoading();
// open link in browser
Ti.Platform.openURL(html);
}
}
});
}
else {
.....................
}
I have Android WebView which displays some links as: Link1TextLink2Text Now I would like to retrieve Link1Text and Link2Text when I long press these links. I have contextMenu implemented in the code and I could successfully get the link urls (http://link1.html, http://link2.html) using HitTestResult getExtra() method but how ccan I get those link texts ?FYI, I require those link texts for implementing "Copy link text" option in the contextMenu.
To get the text of an achor link:
I. Hook a touchstart listener to every web pages in the onPageFinished() callback of WebViewClient via evaluateJavascript. like:
//Javascripts to evaluate in onPageFinished
const w=window;
w.addEventListener('touchstart',wrappedOnDownFunc);
function wrappedOnDownFunc(e){
if(e.touches.length==1){
w._touchtarget = e.touches[0].target;
}
console.log('hey touched something ' +w._touchtarget);
}
note we've saved the touch target.
II. Then implement OnLongClicklisenter for webview. use evaluateJavascript again when you long pressed on a link object:
#Override
public boolean onLongClick(View v) {
WebView.HitTestResult result = ((WebView)v).getHitTestResult();
if (null == result) return false;
int type = result.getType();
switch (type) {
case WebView.HitTestResult.SRC_ANCHOR_TYPE:
if(result.getExtra()!=null){
((WebView)v).evaluateJavascript("window._touchtarget?window._touchtarget.innerText:''", new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
System.out.println("hey received link text : "+value);
}
});
}
return true;
}
return false;
}
What's more, we can even choose to select the text of the anchor element! Actually this is one of the options that samsung browser offers when you long-pressed an tag .
To achieve this, we still need that recorded touch target. Besides we need 2 new javascript methods:
function selectTouchtarget(){
var tt = w._touchtarget;
if(tt){
w._touchtarget_href = tt.getAttribute("href");
tt.removeAttribute("href");
var sel = w.getSelection();
var range = document.createRange();
range.selectNodeContents(tt);
sel.removeAllRanges();
sel.addRange(range);
}
}
function restoreTouchtarget(){
var tt = w._touchtarget;
if(tt){
tt.setAttribute("href", w._touchtarget_href);
}
}
Finnaly in the onLongClick listener, instead of just fetch the innerText, we programmatically set the selection, trigger the action menu bar, and restore the removed href attribute of our touch target.
case WebViewmy.HitTestResult.SRC_ANCHOR_TYPE:
if(result.getExtra()!=null){
WebViewmy mWebView = ((WebViewmy)v);
mWebView.evaluateJavascript("selectTouchtarget()", new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
/* bring in action mode by a fake click on the programmatically selected text. */
MotionEvent te = MotionEvent.obtain(0,0,KeyEvent.ACTION_DOWN,mWebView.lastX,mWebView.lastY,0);
mWebView.dispatchTouchEvent(te);
te.setAction(KeyEvent.ACTION_UP);
mWebView.dispatchTouchEvent(te);
te.recycle();
//if it's not delayed for a while or the href attribute is not removed, then the above code would click into
// the anchor element instead of select it's text.
/* restore href attribute */
mWebView.postDelayed(() -> mWebView.evaluateJavascript("restoreTouchtarget()", null), 100);
}
});
}
return true;
In my case, I've extended the WebView as WebViewmy to record last touched positions, lastX and lastY, in the onTouchEvent method.
Unfortunately, a clear, official way to do this is not available. Although, there are two APIs (selectText and copySelection) which are pending API council approval, that may help to do this, but they are not available at the moment.
In Android i have a webview that lead to an google authentication form. When the authentication form is loaded in the webview it will call onReceivedLoginRequest:
public void onReceivedLoginRequest(final AuthenticationWebView client, final WebView view, final String realm,
final String account, final String args) {
...
}
When this method is called i get the Accounts stored on the device and let the user choose one and use that one to retrieve an authtoken:
mAccountManager.getAuthToken(account, mAuthToken, null, mWebViewActivity, this, null);
It all works perfectly fine (i think) and the next step looks good also:
final String result = bundle.getResult().getString(AccountManager.KEY_AUTHTOKEN);
if (result != null) {
// authentication succeeded, new url given as result for redirection
mWebView.loadUrl(result);
} else {
onLoginFailed();
}
A new method is called and returns the new url for redirection which i load in the webview. The result String returns a new adress which looks good but does nothing, it doens't log in but i don't get any message error either from the webview.
Url:
https://accounts.google.com/MergeSession?args=..&uberauth=WILL_NOT_SIGN_IN&source=AndroidWebLogin
I modified the url a bit with (triple)dots, don't know how secure it is and it was not such an interesting part of the url.
Anyone know why the url will not redirect me to the website behind it? When i use this url in my browser it recognizes my account, i only have to fill in my password and it redirects me correctly.
I use a native Android webview and i inject it with javascript but this only happens on the page after the login.
The settings i added to the webview are:
mSettings = getSettings();
mSettings.setJavaScriptEnabled(true);
mSettings.setDomStorageEnabled(true);
mSettings.setAllowFileAccess(true);
mSettings.setGeolocationEnabled(true);
So, my overall situation is as follows: I have to get some HTML from a wordpress database and render it on a webview.
So I cant work with youtube API , I must render this HTML, ill have embeded videos on it and I need them to pop up on fullscreen. This solution Videos not playing in fullscreen mode wont work on API 10.
Is there anyway to make the embeded videos play on fullscreen below API 11?
You can create Frame layout above your WebView and attach to it VideoView. This way you can manage size, add custom MediaControls but there are a lot of work and you will faced with many problems.
Edit: You can capture event by:
if you can handle html, you can add JS method, that calls your application method throught the JS bridge. As example you put an image with onclick event. Click by this image calls JsVideFrameCallbackImpl.playVideo('videoUrl') in your app (http://android-developers.blogspot.com/2008/09/using-webviews.html):
<img src="img/video_1.jpg" width="494px" height="340px" onclick="javascript: JsVideFrameCallbackImpl.playVideo('videoUrl')">
and attach addJavascriptInterface in your activity to webview:
wv.addJavascriptInterface(new JsVideFrameCallbackImpl(), "JsVideFrameCallbackImpl");
//....
private final class JsVideFrameCallbackImpl {
private final static String LOG_TAG = "JsVideFrameCallbackImpl";
#SuppressWarnings("unused")
#JavascriptInterface
public void playVideo(final String videoUri) {
// call VideoPlayer activity as example
startActivity(new Intent(context, VideoPlayer.class));
}
}
or you can get the coords of any html element on the page via the same js-bridge and place VideoView on FrameLayout (Do not forget enable js in webview wv.getSettings().setJavaScriptEnabled(true); before using javascript in webview):
wv.loadUrl("javascript: jsInitMedia()"); // call js method jsInitMedia
jsInitMedia in the html (or you can apply this code directly in WebView as: wv.loadUrl("javascript: var el = document.getElemen..."):
function jsInitMedia() {
var el = document.getElementById(dataArray[i]); var coords = el.getBoundingClientRect();
// get top, left coords, width and height with coords.width, el.offsetTop and so on. Send them back as string (i use json format)
JsVideFrameCallbackImpl.init(jsonDataWithCoordsAsString);
}
and in your java code in JsVideFrameCallbackImpl :
#JavascriptInterface
public void init(final String json) {
Log.d(LOG_TAG, "Init:" + json);
// parse json and extract left, top, width and height
// now you can place any View with corresponding coords on your frame
//as well you can easy handle all click events
}