macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

BYOB: Build Your Own Browser, Part 3
Pages: 1, 2

The apply and windowDidBecomeKey methods are inverses of each other; apply sets values into WebPreferences and MyDocument and windowDidBecomeKey looks up values in WebPreferences and MyDocument. One odd thing that happens is that we setAutosaves every time to YES in the WebPreferences object, p. This is not strictly necessary, but we do this to ensure that the preferences are being saved, on the off chance that the value has changed since the last time we accessed it.

Here is the code for apply and windowDidBecomeKey:


- (IBAction)apply:(id)sender
{  
  NSUserDefaults *defaults;
  defaults = [NSUserDefaults standardUserDefaults];

  NSString* dHomepage = [defaultHomepage stringValue];
  [defaults setObject:dHomepage forKey:@"defaultHomepage"];
  [md setDefaultHomepage:dHomepage];

  NSString* iContent = [ignoreContent stringValue];
  [defaults setObject:iContent forKey:@"ignoreContent"];
  [md setIgnoreContent:iContent];

  [p setAutosaves:YES];
  
  if ([autoloadImages state] == NSOffState){
  [p setLoadsImagesAutomatically:NO];
  }
  else {
  [p setLoadsImagesAutomatically:YES];
  }

  if ([allowAnimatedImages state] == NSOffState){
  [p setAllowsAnimatedImageLooping:NO];
  }
  else {
  [p setAllowsAnimatedImageLooping:YES];
  }

  if ([javascriptEnabled state] == NSOffState){
    [p setJavaScriptEnabled:NO];
  }
  else {
  [p setJavaScriptEnabled:YES];
  }
}


- (void)windowDidBecomeKey:(NSNotification *)aNotification
{
  [defaultHomepage setStringValue:[md getDefaultHomepage]];
  [ignoreContent setStringValue:[md getIgnoreContent]];

  [p setAutosaves:YES];
  
  if ([p loadsImagesAutomatically]){
    [autoloadImages setState:NSOnState];
  }
  else {
  [autoloadImages setState:NSOffState];
  }

  if ([p allowsAnimatedImageLooping]){
    [allowAnimatedImages setState:NSOnState];
  }
  else {
    [allowAnimatedImages setState:NSOffState];
  }

  if ([p isJavaScriptEnabled]){
    [javascriptEnabled setState:NSOnState];
  }
  else {
    [javascriptEnabled setState:NSOffState];
  }
}

You probably noticed that we made use of the NSUserDefaults classes in all three methods to save the information for default homepage. NSUserDefaults allows developers to save information into the user's defaults database that can be reused between instance of the program. This is very convenient and makes it so that developers do not need to make awkward preferences files that are lost if they are not in a specific location.

While I don't go into details about using this here, NSUserDefaults is well documented on Apple's developer site. You may also want to check out the defaults command to see what the defaults are for this and other applications.

Once all the code is written and saved, you're done. When you run the code, you should see that preferences first come up with the default values that you set in Interface Builder. But if you change them, quit and run the browser again; the values you entered on the preference pane should be saved. Adding additional preference variables, or other variables that might be interesting for you (for instance, the value for our Content Eliminator) is pretty straightforward. Build up the interface in Interface Builder and then connect the pieces by adding to each of the methods in Controller.

Content Eliminator

The final feature we're going to add to our browser is a content eliminator. Put simply, a content eliminator will prevent any content from being downloaded if its URL contains certain substrings. For instance, let's say you're sick of the number of banner ads that are on the pages you read. You still want to be able to download the pages that the ads are on, but you don't want to see the ads. If the ads come from a site named singlebutton.com, you would add that string to the content eliminator's list of strings to avoid, and it will prevent the content from being downloaded.

This feature can be used to prevent downloading from ad sites, as a child-guard-type system, or can even be reversed, to only allow content from certain URLs to be used in a kiosk-type application. If you are planning to use this in a kiosk-type application, make sure that you are very careful with the way that you check the strings, or else you may end up with people accessing yoursite.porno-site.com instead of yoursite.com.

To add a content eliminator to our browser, we need to use another of WebView's delegates, this time the policyDelegate. The policyDelegate allows an external application to make decisions about content, specifically how it is to be downloaded and how it is to be displayed. There are several methods that this delegate provides for policy decisions that need to be made. The method that we will be using to make policy decisions is:


decidePolicyForNavigationAction:(NSDictionary *)actionInformation 
  request:(NSURLRequest *)request
  frame:(WebFrame *)frame 
  decisionListener:(id<WebPolicyDecisionListener<)listener

When this method is called by WebView, WebView sends it information about the request being made in several objects, and a listener whose job is to report back to WebView what the method has decided to do about the request. When we implement the method, our code needs to comb through the information that is provided about the request and tell the listener what policy decision was made based on that information.

Before we get too far, the first thing that we need for our content eliminator is to tell the WebView what the policyDelegate is going to be. We do this the same way we did with the UIDelegate and the frameDelegate, by adding:


[webView setPolicyDelegate:self];

to the windowControllerDidLoadNIB method.

Once that is added, we need a static list of sites from which we do not want content to be downloaded. To accomplish this, we are going to use a semi-colon delimited static NSString named ignoreContent. Since our NSString is static, we need to declare it in the MyDocuments implementation file, MyDocument.m, as we did our defaultHomepage variable:


static NSString *ignoreContent = @"site1.com; site2.com;site3.com";

Now we need to build a decidePolicyActionForNavigation method that does four things:

  • Figure out what the URL is.
  • Convert the semi-colon delimited list into an array.
  • Go through the array and decide if the content needs to be eliminated; if so, tell the listener to ignore it.
  • If nothing needs to be eliminated, then let the content go through.

The implementation of the method follows these parameters:


- (void)webView:(WebView *)sender decidePolicyForNavigationAction:
  (NSDictionary *)actionInformation request:(NSURLRequest *)request frame:
  (WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
{
  NSString* urlKey = [[actionInformation objectForKey:WebActionOriginalURLKey] host];

  if(ignoreContent == nil){
  [listener use];
  return;
  }
  if (urlKey == nil){
  [listener ignore];
  return;
  }

  NSArray* theList = [ignoreContent componentsSeparatedByString:@";"];
  int count = [theList count];
  while(count > 0 ){
  NSRange range = [urlKey rangeOfString:[theList objectAtIndex:--count]];
  if(range.length >0){
    [listener ignore];
    return;
  }
  }
  
  [listener use];
}

Once the method has been added, test it out and see if you can block some content or even entire sites. While I left it out of the article, if you follow the instructions in the preferences section, the ignoreContent string is one of the variables that can be added to both the user default database and the preferences window. While this is a simple of example of what a policy delegate can do, policy delegates can be used for any number of interesting filtering-type applications

Final Thoughts

As you can see, WebKit provides the custom browser developer with loads of features that can be used in custom browsers, kiosk applications, browsers built in to other applications, or, really, whatever browsing application you can imagine. Hopefully, you will be inspired to create cool applications that really test the bounds of what WebKit can do.

Good luck developing your browser! Drop us a line if you find any interesting links or if you create any interesting WebKit-based projects.

Andrew Anderson is a software developer and consultant in Chicago who's been using and programming on the Mac as long as he can remember.


Return to the Mac DevCenter