手把手教你开发浏览器[一]

浏览器大家都不陌生,想要上网,就不得不用到浏览器。

国内网民计算机上常见的网页浏览器有,QQ 浏览器、Internet Explorer、Firefox、Safari,Opera、Google Chrome、百度浏览器、搜狗浏览器、猎豹浏览器、360 浏览器、UC 浏览器、傲游浏览器、世界之窗浏览器等。

Safari 浏览器、Opera 浏览器、谷歌浏览器用的都是 WebKit 引擎,其他浏览器引擎还有 Gecko(Mozilla Firefox 等使用)和 Trident(也称 MSHTML,IE 使用)

什么是 WebKit?

WebKit 是开源的 Web 浏览器引擎,它就像一个黑盒,我们把 HTML、CSS、JS 和其他一大堆东西丢进去,然后 WebKit 魔法般的以某种方式把一个看起来不错的网页展现给我们。

WebKit 内核在手机上的应用也十分广泛,例如 Android、iPhone、Nokia’s 60 系列的手机浏览器所使用的内核引擎,都是基于 WebKit。

一个浏览器的诞生

既然是开发浏览器,很重要的一点就是如何展示浏览器界面,WebView 控件是 OS X 平台上负责展现 Web 内容、实现和用户交互的核心控件,相比 IOS 平台上同类型的 UIWebView,它功能更强大(先挖个坑,以后有机会再介绍手机浏览器上的 UIWebView)。

下面用一个 Demo(源码:https://github.com/hxzqlh/WebView-Tutorial)来说明一下 WebView 主要的 api 接口, 最终效果如下图所示:

该 Demo 实现的是一个微型浏览器,包含了基本的前进、后退、刷新、回车访问 url,点击 Go 按钮访问 url,在标题栏显示网站的标题等功能。

实现过程:

一、首先要引入 WebKit.framework

#import <WebKit/WebKit.h>

二、从 Library 面板拉一个 WebView 控件,在程序创建该 WebView 控件的object,比如名字为 webView,并通过 IBOutlet 关联起来。通过发送下面的消息让 WebView 控件加载一个网页。

[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"www.google.com"]];

至此,WebView 控件就可以展示网页了。也就是说只要一行代码就可以简单实现一个浏览器展示页面的功能。

三、跟踪 WebView 整个 loading 的过程,用到 WebFrameLoadDelegate

1.加载过程:

在访问一个网页的的整个过程,包括开始加载、加载标题、加载结束等。webkit 都会发送相应的消息给 WebFrameLoadDelegate 。

  • webView:didStartProvisionalLoadForFrame:—开始加载,我们可以在这里获取加载的 url,并展示出来
  • webView:didReceiveTitle:forFrame:—获取到网页标题
  • webView:didFinishLoadForFrame:—加载完成,我们可以在这里设置前进后退按钮的状态

2.错误的处理:

加载的过程当中,有可能会发生错误。错误的消息也会发送给WebFrameLoadDelegate。我们可以在这两个函数里面对错误信息进行处理

  • webView:didFailProvisionalLoadWithError:forFrame: 这个错误发生在请求数据之前,最常见是发生在无效的URL或者网络断开无法发送请求
  • webView:didFailLoadWithError:forFrame: 这个错误发生在请求数据之后

3.显示 loading 状态:

网页只有在加载完成后,才会被展示出来。加载过程如果很久的话,会一片空白然后才出来网页。为了让我们的网页更加人性化,可以在webView:didStartProvisionalLoadForFrame: 加一些 loading 的状态

四、处理加载策略,用到 WebPolicyDelegate

在 safari 浏览器,对于网页里面有 target=_blank 的链接,默认行为是点击后会弹出新的标签窗口。

如果在我们的 webView 控件里面点击此类链接的时候,会触发该消息并会发送给 WebPolicyDelegate,然后我们可以在这里控制对该事件的处理。比如我们可以使用我们自定义的控件打开该url。

1
2
3
4
5
6
7
8
9
10
//网页里面target=_blank的链接,在这里捕获,并在这里控制对该事件的处理。
- (void)webView:(WebView *)sender decidePolicyForNewWindowAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request newFrameName:(NSString *)frameName decisionListener:(id < WebPolicyDecisionListener >)listener
{
NSURL *URL = [request URL];
//在当前窗口打开
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:URL]];
//也可以用默认浏览器打开
//[[NSWorkspace sharedWorkspace] openURL:URL];
//或者也可以加代码,新建一个tab打开
}

前进,后退,刷新等按钮的事件,可以直接绑定到 WebView 控件的 goForward、goBack、reload 事件,而不需要我们自己写事件去处理。

主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//
// AppDelegate.h
// WebView-Tutorial
//
// Created by hxz on 9/9/15.
// Copyright (c) 2015 vobile. All rights reserved.
//

#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (weak) IBOutlet NSWindow *window;
@property (weak) IBOutlet WebView *webView;
@property (weak) IBOutlet NSButton *btnGoBack;
@property (weak) IBOutlet NSButton *btnGoForward;
@property (weak) IBOutlet NSButton *btnReload;
@property (weak) IBOutlet NSTextField *navBar;
@property (weak) IBOutlet NSButton *btnGo;

- (IBAction)clickGo:(id)sender;
- (IBAction)enterTogo:(id)sender;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//
// AppDelegate.m
// WebView-Tutorial
//
// Created by hxz on 9/9/15.
// Copyright (c) 2015 vobile. All rights reserved.
//

#import "AppDelegate.h"

@implementation AppDelegate

@synthesize btnGoBack;
@synthesize btnGoForward;
@synthesize btnReload;
@synthesize navBar;
@synthesize webView;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}

- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}

- (void) awakeFromNib {
//设置delegate
[webView setFrameLoadDelegate:self];
[webView setPolicyDelegate:self];

[[webView preferences] setPlugInsEnabled:YES];
[navBar setStringValue:@"http://www.baidu.com"];
[self onGo];
}

//加载网站
- (void)onGo
{
NSString *urlString = [navBar stringValue];
if(![urlString hasPrefix:@"http://"]){
urlString = [NSString stringWithFormat:@"http://%@",urlString];
}
NSLog(@"onGo url:%@",urlString);
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
}

- (IBAction)clickGo:(id)sender
{
[self onGo];
}

//回车访问网站
- (IBAction)enterTogo:(id)sender
{
[self onGo];
}

//开始加载,可以在这里加loading
- (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame
{
NSString * currentURL = [webView mainFrameURL];
[navBar setStringValue:currentURL];
NSLog(@"webview url:%@",currentURL);
}

//收到标题,把标题展示到窗口上面
- (void)webView:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
{
NSLog(@"receive title:%@",title);

// Report feedback only for the main frame.
if (frame == [sender mainFrame]){
[[sender window] setTitle:title];
}
}

//加载完成
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
//设置前进,后退按钮的状态
if (frame == [sender mainFrame]){
[btnGoBack setEnabled:[sender canGoBack]];
[btnGoForward setEnabled:[sender canGoForward]];
}
}

//错误处理:如无效的URL或者网络断开无法发送请求
- (void)webView:(WebView *)sender didFailProvisionalLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
{

}

//错误处理
- (void)webView:(WebView *)sender didFailLoadWithError:(NSError *)error forFrame:(WebFrame *)frame
{

}

//网页里面target=_blank的链接,在这里捕获,并在这里控制对该事件的处理。
- (void)webView:(WebView *)sender decidePolicyForNewWindowAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request newFrameName:(NSString *)frameName decisionListener:(id < WebPolicyDecisionListener >)listener
{
NSURL *URL = [request URL];
//在当前窗口打开
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:URL]];
//也可以用默认浏览器打开
//[[NSWorkspace sharedWorkspace] openURL:URL];
//或者也可以加代码,新建一个tab打开
}

@end