Prevent Flash in Cocoa WebView - objective-c

In my Cocoa app, I want to prevent Flash from loading in a WebView, and let the user decide if the Flash should be shown for each page. (That's the same behavior already available through the ClickToFlash plugin or Safari extension. Bundling any of those extensions is probably not an option because of licensing issues.)
Unfortunately most of the Flash I'm trying to avoid is generated from embedded JavaScript specifically designed to prevent easy flash blocking, so I cannot filter the raw HTML for inclusion of Flash objects.
Also, I cannot disable JavaScript for my WebView, as the page I want to display looks completely different when JavaScript is turned off.
Is there a notification/hook I can use to modify the page DOM after JavaScript has been executed, but before the Flash plugin is loaded?
Or should I pursue a different direction?
Thanks,
Ilja

Ideally, you would just define your own WebKit plug-in that handles the application/shockwave-flash MIME type and make your plug-in do nothing.
However, there is unfortunately no way to control the priority of multiple WebKit plug-ins that all register for the same MIME type. The loading order of WebKit plug-ins is totally random and arbitrary, so you cannot guarantee that your plug-in will handle the Flash object instead of the Flash plug-in.
The only way around this that I've found is to subclass WebView and override the private method -_pluginForMIMEType: like so:
#class WebBasePluginPackage;
#interface WebView ( MyFlashPluginHack )
- (WebBasePluginPackage *)_pluginForMIMEType:(NSString *)MIMEType;
#end
#implementation MyWebView
- (WebBasePluginPackage *)_pluginForMIMEType:(NSString *)MIMEType
{
if ( [MIMEType isEqualToString:#"application/x-shockwave-flash"] )
{
return [super _pluginForMIMEType:#"application/my-plugin-type"];
}
else
{
return [super _pluginForMIMEType:MIMEType];
}
}
#end
Then you just need to create a custom WebKit plugin to handle "application/my-plugin-type" and have that plug-in do nothing at all.

Okay, we pretty much figured this out.
Since there is no official API that lets the host app know when JavaScript has finished or control what plugin should load, we are now using custom JavaScript that gets inserted into the received HTML we want to display.
The ClickToFlash Safari extension (not the Internet plugin, which it is based on) was a good inspiration.

Related

Enable WebGL in the Mac OS application on WebKit

I am writing an application for Mac OS - browser on WebKit to use for the some site on WebGL. All is ready, the application correctly displays normal HTML sites, but WebGL doesn't work. How can enable WebGL in my app?
Once you have a WebView, there is an undocumented method which can enable WebGL:
WebPreferences *p = [webView preferences];
if ([p respondsToSelector:#selector(setWebGLEnabled:)]) {
[p setWebGLEnabled:YES];
}
In this example I have protected it with respondsToSelector: to ensure the code will not abort if later versions remove this specific option.
Note that I understand that an application containing code using undocumented interfaces may be rejected if submitted to Apple's Mac App Store.
Another option is to use a different embedded renderer which officially supports WebGL (where Apple's WebKit as demonstrated in Safari only has it as a developer option, presumably intended to be experimental). Since both Firefox and Chrome support WebGL, have a look at Gecko and Chromium Embedded Framework. (Note: I have not been able to confirm whether whether embedded Gecko supports WebGL.)
In Mavericks, this is the correct code.
WebPreferences *preferences = [_webview preferences];
if([preferences respondsToSelector:#selector(setWebGLEnabled:)]){
[preferences performSelector:#selector(setWebGLEnabled:) withObject:[NSNumber numberWithBool:YES]];
}
You need to call performSelector otherwise you'll get a compile error.
I figured out how to do this without using an undocumented method. You need to set the user preference WebKitWebGLEnabled to #YES for your app. To have the setting apply the first time the app is run, it needs to be set early, before the WebView is initialized. In my case, the WebView instance is loaded from the main nib file, so that's very early indeed. I added this code to Supporting Files/main.m:
[[NSUserDefaults standardUserDefaults] setObject:#YES
forKey:#"WebKitWebGLEnabled"];
Please note, since no undocumented method is called, I assume this code is within app store guidelines, but my app hasn't been reviewed yet!

Safari extension options pages with access to background page

I'm developing a cross-platform browser extension, and have based all my code on the Chrome-way of doing this. I have counted on that the background page will be accessible from the options page, which in Safari extensions turns out to be not possible (since there is no such thing as an options-page). You can only access safari.extension.globalPage.contentWindow from within the extension popup, and the background page itself.
Now, I have an options page, which is an html-page within the extension bundle, and so far I haven't found a way for Safari to give it extension "rights". The closest I have come is adding a content script that's only added on the options page. This seems a bit silly, since the html page itself is in the extension bundle?!
Others have suggested using asynchronous ping-pong style message event handlers, and even the canLoad-mechanism (which is "only" able to run in a beforeload-event). I have been able to hack the canLoad-mechanism for synchronous messaging by forging the BeforeLoadEvent:
// Content script (run from anywhere)
var result = safari.self.tab.canLoad(new BeforeLoadEvent, "data")
-> "return value"
// Background page
safari.application.addEventListener('message', function(e) {
if ( e.name === "canLoad" )
e.message = "return value";
}, true);
It's a hack, but it works. However, I am crippled by the message transport serialization, since I need to be able access methods and data on my objects from the background page. Is there anyway around this?
Possible ways that might work but I don't know if possible:
Access options-page window-object from backgrounds page. Is that possible?
Message passing, need to bypass message serialization
Any shared/global object that I can attach objects to and fetch from the options page?
Make Safari run the options.html page from outside the content-script sandbox? It works in Chrome since they are both within the extension-bundle. It's quite annoying that safari doesn't do this too.
Run the options-page from within the popup. This is promising, but it crashes safari (which is very promising!). However, from the looks of it it's just something to do with a CSS animation in my options.html page. The biggest issue is that it has to be able to open an OAuth2 popup, but thanks to being able to programmatically open the popover, it might be a non-issue. However, this option is the most realistic, but I would rather have it open in a new tab.
Any suggestions and hackish workarounds would really help.

iOS access to Safari Reader feature through UIWebView

I am using iOS 4.3 & was wondering if there is any way that I can access the Safari's "Reader" feature through which webpages are removed of ads & other riff raff & the content takes the center stage.
If one opens any article in Safari (on say Wikipedia website), then a "Reader" button appears on the URL bar. Clicking on it presents a new window presenting the content beautifully.
How can I leverage this this functionality in iOS through UIWebView ?
PS: I know there is something called Readability Project. But I have no idea how to use this through UIWebView. Also for some websites Safari's Reader takes a call not to enable "Reader" feature, maybe it has no sufficient confidence?
Important: THIS ANSWER NO LONGER WORKS!
Readability shut down on September 30, 2016.
Here is something they recommend as a replacement:
https://mercury.postlight.com/web-parser/
Keeping the answer as a historical reference
--- Original answer ---
You can use Readability mobilizer for this. You will get a cleaned up version of any article, in the Readability styling:
http://www.readability.com/m?url=http://{URLOFTHEARTICLE}
Just prepare the URL and load it in your UIWebView. Here is how it looks in action:
http://www.readability.com/m?url=http%3A%2F%2Fwww.cnn.com%2F2013%2F01%2F11%2Fshowbiz%2Ftv%2Fgolden-globes-tv-vineyard%2Findex.html%3Fhpt%3Dhp_abar
Apple is making a pretty big deal about the inclusion of "Reader" in iOS 5. I'm assuming by the noise it's not available in 4.3
re: How to use through UIWebView
I can't find any mention of it in the Web Content Guide.
There's nothing about it in the UIWebView class reference.
And there's nothing in QA1630.
Dont parse HTML natively on iOS, I have done it before and its a messy business. Either create your own web service to do all the nasty work or look into using readability (readability.com) they provide an API.
There is also an open source ruby, python and php readability port that you can find here
https://github.com/iterationlabs/ruby-readability
https://github.com/gfxmonk/python-readability
http://code.fivefilters.org/p/php-readability/source/tree/master/
For you ruby enthusiasts, readability is also available as a gem, just google it.
Actually reader button do a bit of analysis where it parse the HTML Page and then it sees a clear body tag to parse. If that plugin is able to extract the exact body it will enable the reader button (My understanding from the readability source code). Now to implement the same for webview you just need to embed java script in your code (this java script is already available in the readability source code) and then you can achieve the same effect.
But I suspect the future plan from apple for the same. Because they can not just let anyone else do this content extraction with the huge business opportunity associated with iCloud with the combination of readability.
If you want you can simple extract the HTML from UIWebView and then extract the body and use it for your purpose. It's not a very rocket science to extract.
For analysis point of view, just have randomly some 10 HTML pages with Reader button enabled, you will see the core cotent belongs to body only and rest of the add, header, footer are separated.
I believe this is the time to re-invent the web content we use, and this is the perfect example of doing the same.
You can even do this by injecting javascript.
#define readJS #"(function(){window.baseUrl='https://www.readability.com';window.readabilityToken='';var s=document.createElement('script');s.setAttribute('type','text/javascript');s.setAttribute('charset','UTF-8');s.setAttribute('src',baseUrl+'/bookmarklet/read.js');document.documentElement.appendChild(s);})()"
And then when your webpage finishes loading
- (void)webViewDidFinishLoad:(UIWebView *)webview
{
[webview stringByEvaluatingJavaScriptFromString:readJS];
You can do it in iOS9.
first import SafariServices:
#import <SafariServices/SafariServices.h>
Afterwards we are instantiating SFSafariViewController and adding it as a subview. We have two options doing so:
Creating with only base URL
Creating with bas URL as well as entering 'Reading Mode' in case it is available
NSString *sURL = #"http://google.com";
NSURL *URL = [NSURL URLWithString:sURL];
SFSafariViewController *safari = [[SFSafariViewController alloc] initWithURL:URL]; // 1.
SFSafariViewController *safari = [[SFSafariViewController alloc] initWithURL:URL entersReaderIfAvailable:YES]; // 2.
[self presentViewController:safari animated:YES completion:nil];

Show contents of a zip file in a WebView

I want to have a WebView that displays some static files from the application bundle. Since I have a large number of small files, I'd like to pack them all into a compressed archive so the application doesn't take up too much space. What's the best way to make this happen?
This should help you out: http://code.google.com/p/ziparchive/
To display data in the WebView:
On Mac OS X use WebFrame's loadHTMLString:baseURL:
On iOS use UIWebView's loadHTMLString:baseURL:
What you probably want to do, is implement an NSURLProtocol subclass that will resolve relative URLs by reading them from the zip archive. That way, you only need to initially read the "main" HTML file from the zip into memory, and the others will be read in on demand. To get WebKit to use your custom URL protocols for resolving relative paths, you could instantiate the WebView like this:
[[web_view mainFrame] loadHTMLString:your_main_html baseURL:[[NSURL alloc] initWithString:#"zip:///"]];
Apple has a really good example of combining a custom URL protocol with a WebView here:
https://developer.apple.com/library/mac/#samplecode/SpecialPictureProtocol/Introduction/Intro.html#//apple_ref/doc/uid/DTS10003816

How to Write OS X Finder plugin

I'm looking for a guide or sample code for writing Mac OS X Finder plugins? It would like to know how to do some simple actions:
adding image overlayers to icons
adding context menu items
listen to file changes
I found the following two resources:
Writing Contextual Menu Plugins for OS X: An outdated document from 2002 that uses the COM API targeting Mac OS X 8/9.
SCPlugin: Open-source SVN Mac application that includes a Finder plug-in.
I am tempted to review the SCPlugin code, but was hoping to find an easier sample to digest.
The Finder Icon Overlay example project represents a small and very basic but actually working example of the answer below.
https://github.com/lesnie/Finder-Icon-Overlay
I know this is so old, but some may be still interested in topic (?)
Here is what I have it done under Leopard (10.6). At first proper Finder's headers are needed. Use class-dump tool to get it. Then write your code as a SIMBL plugin (refer to documentation how to do it), swizzling some methods. For instance to draw something over icon in ListView, drawIconWithFrame: method of TIconAndTextCell method must be overriden.
Here's the code for method swizzling:
+ (void) Plugin_load
{
Method old, new;
Class self_class = [self class];
Class finder_class = [objc_getClass("TIconAndTextCell") class];
class_addMethod(finder_class, #selector(FT_drawIconWithFrame:),
class_getMethodImplementation(self_class, #selector(FT_drawIconWithFrame:)),"v#:{CGRect={CGPoint=dd}{CGSize=dd}}");
old = class_getInstanceMethod(finder_class, #selector(drawIconWithFrame:));
new = class_getInstanceMethod(finder_class, #selector(FT_drawIconWithFrame:));
method_exchangeImplementations(old, new);
}
I am overriding "drawIconWithFrame:" method with my method "FT_drawIconWithFrame:". Below is sample implementation for this method.
- (void) FT_drawIconWithFrame:(struct CGRect)arg1
{
[self FT_drawIconWithFrame:arg1];
if ([self respondsToSelector:#selector(node)]) {
if ([[[[NSClassFromString(#"FINode") nodeWithFENode:[(TNodeIconAndNameCell *)self node]] fullPath] lastPathComponent] hasPrefix:#"A"])
[myPrettyIconOverlayImage drawInRect:NSMakeRect(arg1.origin.x, arg1.origin.y, arg1.size.height, arg1.size.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
}
}
Essentially it draws "myPrettyIconOverlayImage" over every icon for file with filename starts with letter "A". This logic is up to you.
Pay attention to this line: [self FT_drawIconWithFrame:arg1]; this is how to call 'super' in order to get normal icon and name etc. I know, looks weird, like loop, but actually it isn't. Then wrap in into SIMBL plugin, install SIMBL and ...run.
Due to changes in Lion some work have to be done from scratch (make new "Finder.h" file with all declarations needed in it, find proper classess and methods to override), but this technique still works.
Happy hacking!
For Yosemite (MacOS 10.10 & newer), you can use Apple's FinderSync framework, which allows Finder extensions to:
Express interest in specific folder hierarchies
Provide "badges" to
indicate the status of items inside those hierarchies
Provide dynamic
menu items in Finder contextual menus, when the selected items (or
the window target) are in those hierarchies
Provide a Toolbar Item
that displays a menu with dynamic items (even if the selection is
unrelated)
Sadly, programming a Finder plugin actually does still require getting your hands dirty with COM. If you look at the SCFinderPlugin subproject of the SCPlugin project, you will find that it follows exactly the same techniques outlined in your first link, including setting up a vtable for COM, writing AddRef/ReleaseRef functions, and so on. Writing a plugin, where you're simultaneously managing old-school Carbon memory management, COM-style memory management, and Cocoa/new-style Carbon memory management, can be an incredible pain—and that totally ignores the fact that you'll be interacting in three or more radically different APIs, with different naming conventions and calling semantics. Calling the situation hysterically poor would be a vast understatement.
On the bright side, the Finder in Mac OS X 10.6 Snow Leopard has been fully rewritten in Cocoa--and with that come vastly superior plugin interfaces. If you are lucky enough to be in a situation where you can actually only target Snow Leopard, you probably should grab an ADC Premier or higher membership, download the prerelease builds, and code against that. Besides, your plugin may not work on 10.6 anyway without a Cocoa rewrite, so it might make good sense to take a look at Snow Leopard before it gets released, regardless.
There is no official or supported plugin system for the Finder. Starting with OS X 10.6, you will need to inject code into the Finder process and override objective C methods in the Finder process.
I've done this for a proprietary project. I can tell you that the reason that there are no examples or tutorials for this is because it is a significantly difficult and time consuming development task. For this reason, there's plenty of incentive for individuals or organizations who have accomplished this to guard the specifics of their process closely.
If there's any way at all that you can accomplish your goal using the Services API, do it. Writing a Finder plugin will take you 1-2 solid months of painstaking development and reasonably deep knowledge of C and Objective-C internals.
If you're still convinced that you want do to this, grab mach_star. Good luck.
As far as I know, there's no official plugin architecture for the Finder. You may be able to add image overlays to icons through an external application without having to hook into the Finder, although it wouldn't be on the fly. I don't think there is a way to add contextual menu items aside from Folder Actions and Automator. You can also look into writing an external application to monitor File System changes using the FSEvents API.
Here's a completed solution for Finder icon badges and contextual menus in Lion and Mountain Lion using the techniques described by Les Nie.
Liferay Nativity provides a scripting bundle that will swizzle the relevant Finder methods and a Java client for setting the icons and context menus. It also includes equivalent projects for Windows and Linux.
The project is open source under LGPL, so feel free to contribute any bug fixes or improvements!
The pickings are slim; it's never been really clear to me whether Finder Plugins are actually supported. A few more leads, though:
SampleCMPlugIn - Carbon-based of course, since so is Finder. Note that almost any Finder plugin is probably going to stop working with 10.6.
Automator can save things as a "Finder plugin." It's a more supported version of what you're discussing, but of course less flexible.
To add Finder/File browser icon overlays and context menus, in a cross-platform manner, from Java, take a look at the Liferay Nativity library.
I also make mention of this in another SO post, which also contains links to Apple's 'Finder Sync' docs and API.