UiWebView response to window.print - uiwebview

Is there a way to detect window.print from a UIWebView and respond to it? I have printing in my UiWebView and need to have a way to react to that event from the UIWebView.

First, this is the solution I came across. But UIWebview does not support those events and the API like in a desktop browser. And haven't yet got the matchMedia to work. Probably will work when I have a printer configured!?!.
The actual reason is that the media type of the web page did not change from screen to print in the UIWebView, unlike desktop browsers, so there is no trigger, till this point.
And UIWebView has no delegate method for printing exposed till date.
Anyways, where there is a will, there is a [hack] way. Two step solution.
Step 1. Javascript
If you have access to the HTML or JS file, and it is used only for this UIWebView, you can add the below lines to the JS
(function(){
var originalPrintFn = window.print; // Keep it cached in this variable, just in case
window.print = function(){
// Trigger location change
window.location = "your_app_scheme:print";
}
})();
If you don't have access to the HTML or JS file, in your UIWebView delegate, add the below code
[self.myWebView stringByEvaluatingJavaScriptFromString:#"(function(){var originalPrintFn = window.print;window.print = function(){window.location = 'your_app_scheme:print';}})();"];
This can be done in the delegate's webViewDidFinishLoad method
Step 2. Catching the call in native
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([[[request URL] absoluteString] hasPrefix:#"your_app_scheme:"]) {
// Call the given selector
[self performSelector:#selector(your_handle_to_print)];
// Cancel the event, so the webview doesn't load the url
return NO;
}
return YES;
}
After this in your_handle_to_print, you can do your printing magic.
Hope this helps!

Related

Get the current full URL for WKWebView

Is there a way to get the FULL URL loaded by a WKWebView for every request?
webView:didFinishNavigation:
Works only for mainFrame navigations and does not provide a URL request parameter.
How do I get the FULL URL just like in UIWebViewDelegate's
webViewDidFinishLoad:webView
...which gets invoked after any loading finishes and you can get the full request URL from the webView parameter.
It's nice that WKWebView's URL property saves the work that needs to be done to extract a UI-friendly base URL, but it's a huge loss we can't get the full one!
I have tried using
webView:decidePolicyForNavigationAction:decisionHandler:
...but it produces different results for URLs compared to what a UIWebView's request property holds after finishing the load of a page.
You can get URL for a newly requested Webpage by "navigationAction.request.URL" in decidePolicyForNavigationAction delegate method.
func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
if let urlStr = navigationAction.request.URL?.absoluteString{
//urlStr is what you want, I guess.
}
decisionHandler(.Allow)
}
First, I think you are confusing NSURL and NSURLRequest. The first is readily accessibly via webView.URL and it does actually give you the full URL of whatever was loaded. Assuming that where you say URL you mean NSURL.
If that is not what you meant, for example if you wanted to see the redirect chain or the response headers, then I'm afraid the answer is that you cannot get to tht specific information via the WKWebView.
You will have to fall back to UIWebView where you can intercept requests relatively easily and see the full request/response.
This is Yuichi Kato's answer for Swift 4. It retrieves the full URL from the request property of the navigation action in the webView(_:decidePolicyFor:decisionHandler:) method of WKNavigationDelegate.
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let urlStr = navigationAction.request.url?.absoluteString {
//urlStr is what you want
}
decisionHandler(.allow)
}
Don't forget to conform your class to WKNavigationDelegate and set your web view's delegate accordingly:
class WebViewController: UIViewController, WKNavigationDelegate
[...]
webView.navigationDelegate = self

Re-inject content scripts after update

I have a chrome extension which injects an iframe into every open tab. I have a chrome.runtime.onInstalled listener in my background.js which manually injects the required scripts as follows (Details of the API here : http://developer.chrome.com/extensions/runtime.html#event-onInstalled ) :
background.js
var injectIframeInAllTabs = function(){
console.log("reinject content scripts into all tabs");
var manifest = chrome.app.getDetails();
chrome.windows.getAll({},function(windows){
for( var win in windows ){
chrome.tabs.getAllInWindow(win.id, function reloadTabs(tabs) {
for (var i in tabs) {
var scripts = manifest.content_scripts[0].js;
console.log("content scripts ", scripts);
var k = 0, s = scripts.length;
for( ; k < s; k++ ) {
chrome.tabs.executeScript(tabs[i].id, {
file: scripts[k]
});
}
}
});
}
});
};
This works fine when I first install the extension. I want to do the same when my extension is updated. If I run the same script on update as well, I do not see a new iframe injected. Not only that, if I try to send a message to my content script AFTER the update, none of the messages go through to the content script. I have seen other people also running into the same issue on SO (Chrome: message content-script on runtime.onInstalled). What is the correct way of removing old content scripts and injecting new ones after chrome extension update?
When the extension is updated Chrome automatically cuts off all the "old" content scripts from talking to the background page and they also throw an exception if the old content script does try to communicate with the runtime. This was the missing piece for me. All I did was, in chrome.runtime.onInstalled in bg.js, I call the same method as posted in the question. That injects another iframe that talks to the correct runtime. At some point in time, the old content scripts tries to talk to the runtime which fails. I catch that exception and just wipeout the old content script. Also note that, each iframe gets injected into its own "isolated world" (Isolated world is explained here: http://www.youtube.com/watch?v=laLudeUmXHM) hence newly injected iframe cannot clear out the old lingering iframe.
Hope this helps someone in future!
There is no way to "remove" old content scripts (Apart from reloading the page in question using window.location.reload, which would be bad)
If you want to be more flexible about what code you execute in your content script, use the "code" parameter in the executeScript function, that lets you pass in a raw string with javascript code. If your content script is just one big function (i.e. content_script_function) which lives in background.js
in background.js:
function content_script_function(relevant_background_script_info) {
// this function will be serialized as a string using .toString()
// and will be called in the context of the content script page
// do your content script stuff here...
}
function execute_script_in_content_page(info) {
chrome.tabs.executeScript(tabid,
{code: "(" + content_script_function.toString() + ")(" +
JSON.stringify(info) + ");"});
}
chrome.tabs.onUpdated.addListener(
execute_script_in_content_page.bind( { reason: 'onUpdated',
otherinfo: chrome.app.getDetails() });
chrome.runtime.onInstalled.addListener(
execute_script_in_content_page.bind( { reason: 'onInstalled',
otherinfo: chrome.app.getDetails() });
)
Where relevant_background_script_info contains information about the background page, i.e. which version it is, whether there was an upgrade event, and why the function is being called. The content script page still maintains all its relevant state. This way you have full control over how to handle an "upgrade" event.

Chrome extension, onbeforerequest, which page is making the call?

Im making a small google chrome extension that is watching all calls a page is making. The idea is to log how a page behaves and how many external calls are made. Got everything working except the part where i need to get the source url of the page that initiates the call.
For example im going to www.stackoverflow.com in my browser, then my onbeforerequest lister kicks in and gives me all the calls. So far so good. But i still want the name of the page which is making the calls, in this case i want: "www.stackoverflow.com" and the owner of the calls.
I tried getting it from the tabs, but chrome.tabs.get uses a callback and that is not called before its all over and i got all the calls processed.
any ideas on how to get the source url?
edit
Im using this code right now, to get the url, but it keeps returning "undefined":
var contentString = "";
chrome.webRequest.onBeforeRequest.addListener(
function (details) {
var tabid = details.tabId;
var sourceurl = "N/A";
if (tabid >= 0) {
chrome.tabs.get(parseInt(tabid), function (tab) {
sourceurl = tab.url;
alert(sourceurl);
});
}
});
When doing the alert, i get undefined for every request
edit 2 - this is working for me
chrome.tabs.get(parseInt(tabid), function (tab) {
if (tab != undefined) {
alert(tab.url);
}
});
onBeforeRequest returns a TabID, then you can then use the get method of the tabs API to get a reference to the tab and thus the URL of the page.
You can use the details.initiator (see here for more details) which is supported since Chrome 63 version. Below you can see what is mentioned on Chrome APIs page about the initiator.
The origin where the request was initiated. This does not change
through redirects. If this is an opaque origin, the string 'null' will
be used.

How to use the experimental offscreenTab API?

I've been searching for examples and reference and have come up with nothing. I found a note in offscreenTab source code mentioning it cannot be instantiated from a background page (it doesn't have a tab for the offscreenTab to relate to). Elsewhere I found mention that popup also has no tie to a tab.
How do you successfully create an offscreenTab in a Chrome extension?
According to the documentation, offscreenTabs.create won't function in a background page. Although not explicitly mentioned, the API cannot be used in a Content script either. Through a simple test, it seems that the popup has the same limitation as a background page.
The only leftover option is a tab which runs in the context of a Chrome extension. The easiest way to do that is by using the following code in the background/popup:
chrome.tabs.create({url: chrome.extension.getURL('ost.htm'), active:false});
// active:false, so that the window do not jump to the front
ost.htm is a helper page, which creates the tab:
chrome.experimental.offscreenTabs.create({url: '...'}, function(offscreenTab) {
// Do something with offscreenTab.id !
});
To change the URL, use chrome.experimental.offscreenTabs.update.
offscreenTab.id is a tabId, which ought to be used with the chrome.tabs API. However, at least in Chrome 20.0.1130.1, this is not the case. All methods of the tabs API do not recognise the returned tabID.
A work-around is to inject a content script using a manifest file, eg:
{"content_scripts": {"js":["contentscript.js"], "matches":["<all_urls>"]}}
// contentscript.js:
chrome.extension.sendMessage({ .. any request .. }, function(response) {
// Do something with response.
});
Appendum to the background page:
chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
// Instead of checking for index == -1, you can also see if the ID matches
// the ID of a previously created offscreenTab
if (sender.tab && sender.tab.index === -1) {
// index is negative if the tab is invisible
// ... do something (logic) ...
sendResponse( /* .. some response .. */ );
}
});
With content scripts, you've got full access to a page's DOM. But not to the global object. You'll have to inject scripts (see this answer) if you want to run code in the context of the page.
Another API which might be useful is the chrome.webRequest API. It can be used to modify headers/abort/redirect requests. Note: It cannot be used to read or modify the response.
Currently, the offscreenTabs API is experimental. To play with it, you have to enable the experimental APIs via chrome://flags, and add "permissions":["experimental"] to your manifest file. Once it's not experimental any more, use "permissions":["offscreenTabs"].

Video/audio streaming does not stop even if UIWebView is closed - iPad

I see this issue only on the iPad. The same things works as expected on the iPhone.
I am opening the URL from my application in a UIWebView. If the URL is a normal web page, it works fine as expected. But if the URL is that of a remote video/audio file, the UIWebView opens the default player which is again good.
Now when I dismiss the UIWebView (by clicking on the Done button on the player), the streaming doesn't stop and the audio/video keeps playing in the background (I cannot see it but it does keep playing in the background, can hear it). The UIViewController in which the webview was created is also dealloced (I put in a log statement in the dealloc method) but the streaming doesn't stop.
Can someone please help me out on why this could be happening? And how can I stop the audio/video streaming when the UIWebView is closed?
Thanks.
I have the same issue as stated but in this case the video that won't stop playing is a Youtube video embeded using the object/embed method.
I spent a long time trying to figure out how to get the video to stop playing and the only solution I found was to tell the UIWebView to load a blank page before dismissing the view:
[self.webContent loadRequest:NSURLRequestFromString(#"about:blank")];
Edit(2015-05-12): As mentioned by #chibimai below, this answer by alloc_iNit works along the same lines but since my answer is from 5 years ago -- and his only 4 -- the linked answer may be more applicable. I no longer do iPhone dev work so I cannot determine which is better either way.
I also found this behaviour simply because the web view hadn't been released.
I know this is pretty old thread, but for the sake of new developers like me.
My scenario was: I was using split controller, with media list on master and player in the detail controller section
I faced the same issue and tried all sorts of workaround.
I finally figured it out that the problem was simple, I was holding the reference of the detail controller in my master, and was not releasing the earlier detail controller. A simple release in the master at the right place solved the issue.
I'm working on the same problem. I've found that if you define something like this in your script tag:
function stopVideo(){ video.pause(); }
window.onunload = stopVideo;
Then in your UIViewController, add in:
-(void)viewWillDisappear:(BOOL)animated{
[webView stringByEvaluatingJavaScriptFromString:#"window.onunload();"];
[super viewWillDisappear:animated];
}
It seems to try to pause/stop the video for several seconds, but then you hear the audio continue to play!
Update!
This is a general bug with the media player. You have to set the playback time to -1 in order to make it really stop.
I've just had an issues with an mp4 file opening automatically in media player from a web-page without possibility to close it - "Done" button was only seen in video fullscreen mode and just closed fullscreen, so I had no way to return to the webpage without adding a "Back" button to the navigation bar. (on iPhone the video was opening in a separate view with "Done" button taking back to the WebView with the source page)
The workaround that helped me is to open the video file in a separate media player.
catch opening an MP4 file
- (BOOL) webView: (UIWebView *) webView shouldStartLoadWithRequest: (NSURLRequest *) request navigationType: (UIWebViewNavigationType) navigationType
{
NSRange range = [request.URL.absoluteString rangeOfString: #".mp4" options: NSCaseInsensitiveSearch];
if ( range.location != NSNotFound ) //opening MP4 video file
{
[self showFullscreenMediaWithURL: request.URL];
return NO;
}
return YES;
}
where
- (void) showFullscreenMediaWithURL: (NSURL *) mediaURL
{
MPMoviePlayerViewController *ctrl = [[MPMoviePlayerViewController alloc] initWithContentURL: mediaURL];
ctrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController: ctrl animated: YES];
[ctrl release];
}
opens video with URL in a media player in a model view
don't forget to add MediaPlayer.framework to the project and import
#import <MediaPlayer/MediaPlayer.h>
for the project to be built
PS. many thanks to Viktor Gubrienko for the solution
In fact, I prefer to use the null link to solve the problem.
like this:
[self.webView loadRequest:NSURLRequestFromString(#"about:blank")];
thanks to your thread on this issue I was able to figure out a solution that resolves the issue completely, and I tested it on my IPad, not just the simulator. This also resolves the issue with the audio playing. This is not the permanent resolution but an effective work around.
Overview of the approach:
Basically all that is needed is to send a short audio file to the webView. I made a copy of the IMac submarine.m4v file, I called ping.m4v and added it to my xcode project in the resource folder.
Then at the point when I want the video / audio to stop I do the following steps:
webView.hidden = TRUE;
NSString *mimeType = [[NSString alloc] initWithString:#"video/x-m4v"];
NSData *PingData = [NSData alloc];
PingData = [NSData dataWithContentsOfFile:PingFilePath];
[webView loadData:PingData MIMEType:mimeType textEncodingName:nil baseURL:[NSURL URLWithString:PingFilePath]];
Now you also have to handle error "204". Error 204 seems to be just a warning saying that webView is going to handle this audio file. To handle the error I added this single line (see >>>) to didFailLoadWithError
(void)webView:(UIWebView *)BHwebView didFailLoadWithError:(NSError *)error
{
NSLog(#"Error %i", error.code);
if (error.code == NSURLErrorCancelled) return; // this is Error -999
if (error.code == 204) return; // this is Error 204 - for audio player in webview.
Finally I turn webView.hidden = FALSE right before I display my next Audio/Video. This way the little play bar for the sound file does not show on the display. There is just a soft ping sound... any audio file will do...
I hope this helps...
This is what I use. It does log this error "Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session." but does stop the audio file from playing (fading out) and remembers the play position.
AVAudioSession *s = [AVAudioSession sharedInstance];
if (s != nil)
[s setActive:NO error:nil];
As web view hadn't been released, the video keeps playing on the web view in background.
So you need to release the web view simply by using below code which works fine for me.
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:(BOOL)animated];
[self.wvwebview removeFromSuperview];
}
I hope this helps...
in my case using loadHTMLString and not loadRequest with #"about:blank", fix the problem on macOS
[self.wkWebView loadHTMLString:#"about:blank" baseURL:nil];
I had the same issue in Table view where the table view cell has a webview which loads an audio file . Once you play and go back to another screen the audio still plays .
Solution : reload the table view in viewWillDisappear(_:) of the table view controller and the audio will stop playing once you navigate to another screen .

Resources