HTML5是下一代Web语言的标准,具有兼容性好,安全性高,功能丰富,开发便捷等优点,特别适合如Web操作系统一类的富客户端互联网应用的前端开发。本文将展示如何利用HTML5提供的多种新技术如:本地数据库、多线程开发、视频支持、离线编程等构建一个基本的Web操作系统。
简介
传统的操作系统有着一些难以克服的缺点,如仅能在本地终端访问,或仅支持有限的远程访问,限于本地终端的资源,计算能力薄弱,存储空间有限,缺乏强大的防火墙等一系列安全机制,安全性较差。鉴于以上缺点,Web操作系统应运而生-Web操作系统是一种基于浏览器的虚拟的操作系统,用户通过浏览器可以在其中进行应用程序的操作,以及相关数据的存储。Web操作系统提供的基本服务有文本文档的创建与存储,音频视频文件的播放与存储,提供对时间信息的支持等,更高级的服务则包含即时通信,邮件甚至游戏等服务。Web操作系统克服了传统操作系统的缺点,在网络的支持下,它可以在任何时间,任何地点经由任何支持Web的终端进行访问,可以利用服务器端无限的计算及存储资源,用户数据保存在服务器端,安全性较高。
相关技术
目前构建Web操作系统的前端技术主要有Flex、Silverlight、ActiveX 插件等等,它们各有一些优缺点。
Flex
Flex是一个优秀的富客户端应用框架,专注于页面显示,Adobe专业维护,统一稳定,而且其脚本语言ActionScript 3是面向对象的,非常适合程序员使用。缺点则是耗能高,占用带宽多,对移动应用的支持性差。
Silverlight
Silverlight是由微软推出的用以跟Flash抗衡的RIA(富互联网应用)解决方案,优点是具备硬件级的加速功能,但它目前仍不成熟,对非Windows系统的支持性并不够好,且学习难度较大。
ActiveX插件
ActiveX插件同样是微软推出的RIA解决方案,它是一个开放的解决方案,可以兼容多种语言,不过它的缺点也是显而易见的,用户需要调整浏览器的安全等级并下载插件才能运行RIA应用,极大地降低了安全性。
HTML5
为推动Web标准化运动的发展,W3C推出了下一代 HTML的标准-HTML5,为众多的公司所支持,因此具有良好的前景。它有以下特点:首先,为增强用户体验,强化了Web网页的表现性能;其次,为适应RIA应用的发展,追加了本地数据库等Web应用的功能;再次,由于高度标准化以及诸多浏览器厂商的大力支持,它的兼容性和安全性非常高;最后它是一种简洁的语言,容易为广大开发者掌握。更为难得的是,由于节能和功耗低,在移动设备上HTML5将具有更大的优势。因此更适合如Web操作系统一类的RIA 应用的前端开发。
系统简介
本系统基于HTML5开发,利用HTML5引入的多种新技术如拖拽API、视频标签、本地数据库、draw API、多线程开发、离线编程等提供了一个基本的Web操作系统环境,包含了对桌面的支持、应用程序的支持,提供了一个简单的视频播放器和记事本以及一个时钟,并对系统日志进行了记录,此外还提供了对离线状态的支持。
桌面实现
系统对桌面的支持主要包括应用程序图标的打开与拖拽,以及桌面的上下文菜单等。
桌面拖拽
桌面的布局由一定数量的div组成,它们按照次序依次排列在矩形的桌面上,为应用程序图标的打开与拖拽提供了基本的支持。
清单 1.创建 div
-
var iconHolder = document.createElement("div");
-
iconHolder.id = 'iconHolder' + i;
-
iconHolder.className = "iconHolder";
-
mainDiv.appendChild(iconHolder);
HTML5提供了对drag事件的支持,大大简化了实现拖拽的难度。通过对dragstart事件的监听,将被拖拽的应用程序图标所在的div记录下来,作为拖拽的源。
清单 2.拖拽支持
-
iconHolder.addEventListener("dragstart", function(ev) {
-
var dt = ev.dataTransfer;
-
dt.setData("text/plain", ev.currentTarget.id);// 记录被拖拽图标的 id
-
}, false);
-
-
iconHolder.addEventListener("drop", function(ev) {
-
var dt = ev.dataTransfer;
-
var srcIconHolderId = dt.getData("text/plain");
-
var srcIconHolder = document.getElementById(srcIconHolderId);
-
-
// 如果拖拽至回收站,则删掉被拖拽图标,否则互换两图标位置
-
if(ev.currentTarget.firstChild && ev.currentTarget.firstChild.id == "recycleBin" &&
-
srcIconHolder.firstChild.id != "recycleBin"){
-
srcIconHolder.innerHTML = "";
-
}else if(ev.currentTarget.firstChild){
-
var temp = ev.currentTarget.firstChild;
-
ev.currentTarget.appendChild(srcIconHolder.firstChild);
-
srcIconHolder.appendChild(temp);
-
}else{
-
ev.currentTarget.appendChild(srcIconHolder.firstChild);
-
}
-
}, false);
通过对drop事件的监听,可以获取拖拽的源,以及拖拽的目标div。若目标div为空,则将源div中的应用程序图标转移至目的div中。若目标div中已包含应用程序图标,则将两个图标的位置互换。若回收站图标处于目标div中,回收站将发挥作用并将源div中的应用程序图标删除。图1显示了桌面拖拽的效果。

图 1.桌面拖拽效果
程序打开
程序可以以两种方式打开,左键点击或通过上下文菜单打开。
通过监听div的onclick事件,获取要打开的应用程序id,并利用openApp方法打开相应的应用程序可实现对左键点击的支持。
清单 3.左键点击
-
iconHolder.onclick = function(ev){
-
if(ev.currentTarget.firstChild){
-
openApp(ev.currentTarget.firstChild.id);
-
ev.stopPropagation();
-
}
-
};
通过监听div的oncontextmenu事件,获取要打开的应用程序id,并利用openAppContextMenu 方法显示相应应用程序的上下文菜单,可实现对右键上下文菜单的支持。
清单 4.上下文菜单
-
iconHolder.oncontextmenu = function(ev){
-
if(ev.currentTarget.firstChild){
-
openAppContextMenu(ev.currentTarget.firstChild.id, ev);
-
ev.stopPropagation();
-
}
-
return false;
-
};
利用相应应用程序的id,可以获取对应应用程序的脚本,并执行,同时在系统日志中记录下相应的操作。
清单 5.程序打开
-
function openApp(appId){
-
var time = new Date().getTime();
-
var action = "open app";
-
var details = "open: " + appId;
-
addHistory(time, action, details);// 记录系统日志
-
var appScript = getAppScript(appId);// 获取应用程序脚本
-
eval(appScript);// 执行应用程序
-
}
清单 6.打开程序上下文菜单
-
-
function openAppContextMenu(appId, ev){
-
var appContextMenu = document.getElementById("appContextMenu");
-
appContextMenu.style.display="block";// 令上下文菜单可见
-
appContextMenu.style.pixelTop=ev.clientY;// 设置上下文菜单位置
-
appContextMenu.style.pixelLeft=ev.clientX;
-
appContextMenu.style.background = "#eee";
-
appContextMenu.style.color = "black";
-
appContextMenu.style.fontSize = "30";
-
appContextMenu.style.width = "200px";
-
appContextMenu.style.height = "220px";
-
appContextMenu.style.opacity = 0.5;// 令上下文菜单透明度为 50%
-
appContextMenu.innerHTML = "";
-
-
// 获取应用程序相应上下文菜单的内容
-
var apps = getApps();
-
for(var i=0; i<apps.length; i++){
-
if(apps[i].appId == appId){
-
for(var j=0; j<apps[i].contextMenu.length; j++){
-
appContextMenu.innerHTML += "<div class='appContextMenuItem'
-
onclick=\"appContextMenu.style.display='none';" +
-
apps[i].contextMenu[j].action + "\"
-
onmouseover='this.style.background=\"darkblue\"'
-
onmouseout='this.style.background=\"#eee\"'>"
-
+apps[i].contextMenu[j].name+"</div>";
-
}
-
break;
-
}
-
}
-
}
应用程序的上下文菜单由名为appContextMenu的div实现,将oncontextmenu事件中的clientX及clientY作为上下文菜单出现的位置,并将其透明度设置为 0.5。利用相应应用程序的id获取上下文菜单对应的内容,并将其填充至上下文菜单。
图 2显示了应用程序上下文菜单打开时的效果。

图 2.应用程序上下文菜单
上下文菜单
桌面上下文菜单的实现方式与应用程序上下文菜单的实现方式基本类似,图 3和图 4分别是桌面以及任务栏的上下文菜单。

图 3.桌面上下文菜单

图 4.任务栏上下文菜单
视频播放器
系统提供了一个简单的视频播放器,它支持从系统外部拖拽视频文件进行播放。
顺应网络媒体的发展,HTML5提供了视频标签video以便于加强对视频的支持,大大简化了Web播放器开发的难度,开发人员仅凭几行代码,就可以开发出一个基本功能完善的视频播放器。
清单 7.视频标签的创建
-
var video = document.createElement('video');
-
video.id ='video';
-
video.src ='';
-
video.width = 370;
-
video.height = 260;
-
video.controls = 'controls';
-
video.className = 'video';
-
appHolder.appendChild(video);
-
addDragSupport(appHolder);
清单 7中构造了一个video标签并将其添加到一个名为appHolder的div中。代码的最后一行为其添加了拖拽的支持。
HTML5不但支持浏览器内的拖拽,也支持浏览器与本地系统之间的拖拽。清单 8显示了为一个div添加拖拽支持的过程。
清单 8.添加拖拽支持
-
function addDragSupport(dropbox){
-
document.addEventListener("dragenter", function(e){
-
}, false);
-
document.addEventListener("dragleave", function(e){
-
}, false);
-
dropbox.addEventListener("dragenter", function(e){
-
}, false);
-
dropbox.addEventListener("dragleave", function(e){
-
}, false);
-
dropbox.addEventListener("dragenter", function(e){
-
e.stopPropagation();
-
e.preventDefault();
-
}, false);
-
dropbox.addEventListener("dragover", function(e){
-
e.stopPropagation();
-
e.preventDefault();
-
}, false);
-
dropbox.addEventListener("drop", function(e){
-
handleFiles(e.dataTransfer.files, e.currentTarget, e);
-
e.stopPropagation();
-
e.preventDefault();
-
}, false);
-
}
其中,handleFiles函数说明了如何对拖拽的文件进行处理。
清单 9.拖拽处理
-
function handleFiles(files, dropbox, e) {
-
if(files.length == 0){// 若文件不存在,则用相应文本代替
-
var dt = e.dataTransfer;
-
var text = dt.getData("text/plain");
-
var p = document.createElement("p");
-
p.innerHTML += text;
-
dropbox.appendChild(p);
-
return;
-
}
-
-
for (var i = 0; i < files.length; i++) {
-
var file = files[i];
-
var fileProcessor = dropbox.firstChild;
-
fileProcessor.classList.add("obj");
-
filefileProcessor.file = file; // 添加文件
-
-
var reader = new FileReader();
-
reader.onload = (// 读取文件内容
-
function(aFileProcessor) {
-
return function(e) {
-
aFileProcessor.src = e.target.result;
-
};
-
}
-
)(fileProcessor);
-
reader.readAsDataURL(file);
-
}
-
}
handleFiles函数首先判断文件是否存在,若不存在,则以相应文字取代,若存在,则对所有文件一一进行处理。向fileprocessor( 这里是视频标签 ) 添加文件,然后利用FileReader读取文件内容至fileprocessor进行处理。
图5显示了拖拽一个视频文件movie.ogg到播放器的效果。

图 5.视频播放
本地存储
Web操作系统通常将大部分数据存储于服务器端,这样做的好处显而易见,数据存储空间更大,安全性更好。然而这样做也有不足之处,由于网络的稳定性依然较本地磁盘差,所以在脱离网络的状况下,Web操作系统无法获取相应的数据资源,因此Web操作系统需要一定的访问本地存储空间的能力,当然本地存储空间仅是作为服务器端存储的一个补充,它的空间有限,访问也受到一定的限制。
一直以来,HTML以Cookie作为访问本地空间的方式,然而,这种方式有着很多缺点和不足,如存储的数据格式过于简单,通常仅为键值对;存储的空间大小有限。为此,HTML5 提供了本地数据库以增强本地存储空间的访问能力,它是一个简化版的数据库,能够支持模拟的SQL以及简单的事务处理等功能。
系统为支持本地存储,创建了一个名为 MyData 的数据库。清单 10 显示了数据库创建的过程。
清单 10. 创建数据库
-
var db;
-
var openDatabase;
-
if(openDatabase != undefined)
-
db = openDatabase('MyData', '', 'My Database', 102400);
其中MyData为数据库的名称,省略的参数为数据库的版本,My Database为显示的名称,最后的数字为数据库预估长度(以字节为单位)。
系统日志将系统在某一时间的行为操作记录下来,本地数据库为其提供存储支持。日志在数据库中存储为表History,包含3个字段,分别为时间,操作,及操作的详细信息。清单 11显示了系统是如何记录日志的。
清单 11.日志记录
-
var time = new Date().getTime();
-
var action = "open app";
-
var details = "open: " + appId;
-
addHistory(time, action, details);// 向系统日志中添加一条记录
-
-
-
function addHistory(time, action, details){
-
if(openDatabase != undefined)
-
db.transaction(
-
function(tx) {
-
tx.executeSql('CREATE TABLE IF NOT EXISTS History(time INTEGER,
-
action TEXT, details TEXT)',[]);// 创建日志记录表
-
tx.executeSql('INSERT INTO History VALUES(?, ?, ?)', [time,
-
action, details], // 插入一条日志
-
function(tx, rs) {
-
//alert("store: "+time+"-"+action+"-"+details);
-
},
-
function(tx, error) {
-
//alert(error.source + "::" + error.message);
-
});
-
});
-
}
清单的第一部分显示了如何调用日志记录,第二部分显示了日志记录的详细过程。在一个transaction中,首先判断表History是否存在,若不存在,则创建它。第二部分执行一条SQL语句,向数据库中插入当前的日志。
通过检索表History,我们可以查看系统日志,清单12显示了如何从数据库中查询系统日志,并将其显示出来。
清单 12.日志显示
-
var historyTable = document.getElementById("historyTable");
-
-
// 定义表头
-
historyTable.innerHTML = "";
-
var th = document.createElement('thead');
-
th.style = "color:#CC3300";
-
var th1 = document.createElement('td');
-
th1.align = "center";
-
th1.width=300;
-
th1.innerHTML = "Time";
-
var th2 = document.createElement('td');
-
th2.align = "center";
-
th2.width=100;
-
th2.innerHTML = "Action";
-
var th3 = document.createElement('td');
-
th3.align = "center";
-
th3.width=150;
-
th3.innerHTML = "Details";
-
th.appendChild(th1);
-
th.appendChild(th2);
-
th.appendChild(th3);
-
historyTable.appendChild(th);
-
-
if(openDatabase != undefined)
-
db.transaction(function(tx) {
-
tx.executeSql('SELECT * FROM History', [], function(tx, rs)
-
{
-
// 将日志逐条显示到表的各行中
-
for(var i = 0; i < rs.rows.length && i<15; i++) {
-
var tr = document.createElement('tr');
-
var td1 = document.createElement('td');
-
td1.style.paddingLeft = "3px";
-
td1.style.paddingRight = "3px";
-
-
var t = new Date();
-
t.setTime(rs.rows.item(i).time);
-
ttd1.innerHTML = t.toLocaleDateString()+
-
" "+t.toLocaleTimeString();
-
-
var td2 = document.createElement('td');
-
td2.style.paddingLeft = "3px";
-
td2.style.paddingRight = "3px";
-
td2.innerHTML = rs.rows.item(i).action;
-
-
-
var td3 = document.createElement('td');
-
td3.style.paddingLeft = "3px";
-
td3.style.paddingRight = "3px";
-
td3.innerHTML = rs.rows.item(i).details;
-
-
tr.appendChild(td1);
-
tr.appendChild(td2);
-
tr.appendChild(td3);
-
-
historyTable.appendChild(tr);
-
}
-
});
-
});
清单 12中,首先获取用于显示的日志的HTML表格historyTable,并设置其样式及表头。
然后在一个transaction(事务)中,执行一条SQL语句,查询系统日志,并将每条日志添加为historyTable 中的一行以便显示。图6显示了系统日志的效果。

图 6.系统日志
记事本
系统提供了一个简单的记事本,实现了文本文档的基本操作。文本文档包含标题和内容两个显式属性,以及一个名为 id 的隐式属性。与系统日志类似,本地数据库为文本数据的存储提供了底层的支持。图 7显示了记事本程序的界面。

图 7.记事本
当编辑完文档的标题与内容后,点击左上角的保存按钮,将执行createFile函数。清单 13显示了createFile函数的详细过程。
清单 13.创建文件
-
function createFile(fileId, fileTitle, fileContent){
-
var idx = 1;
-
var update = false;//false 表示新建,true 表示修改
-
-
if(openDatabase != undefined)
-
db.transaction(function(tx) {
-
tx.executeSql('CREATE TABLE IF NOT EXISTS TextFiles(idx INTEGER,
-
title TEXT, content TEXT)',[]);// 创建文本文档表
-
tx.executeSql('SELECT * FROM TextFiles', [], function(tx, rs){
-
for(var i = 0; i < rs.rows.length; i++) {
-
// 若文档存在,则修改它
-
if(rs.rows.item(i).idx == fileId){
-
db.transaction(function(tx) {
-
tx.executeSql('UPDATE TextFiles
-
SET title=?, content=?
-
WHERE idx='+fileId,
-
[fileTitle, fileContent],
-
function(tx, rs) {
-
alert("update successfully");
-
});
-
});
-
return;
-
}
-
}
-
// 若文档不存在,则新建一个文档
-
if(rs.rows.length>0)
-
idx = rs.rows.item(rs.rows.length-1).idx + 1;
-
db.transaction(function(tx) {
-
tx.executeSql('INSERT INTO TextFiles VALUES(?, ?, ?)', [idx, fileTitle, fileContent],
-
function(tx, rs){
-
alert("save successfully: "+idx+"-"+fileTitle+ "-"+fileContent);
-
createFileIcon(idx);
-
},
-
function(tx, error) {
-
alert(error.source + "::" + error.message);
-
});
-
});
-
});
-
});
-
}
清单13首先在一个transaction中,首先判断用于存储文本文档的表TextFiles是否存在,若不存在,则创建它。然后通过查询表TextFiles判断文本文档是否存在,若存在,则当前操作为更新操作,程序将执行一条SQL语句,对当前文本文档进行更新。若不存在,则取当前最大文档id并加1作为新文档的id,并执行一条SQL语句,将文档信息,包括文档id,以及标题和内容插入到数据库中,并于插入操作结束后的回调方法中,利用createFileIcon方法在桌面上为新文档创建一个文档图标。清单14显示了 createFileIcon方法的具体过程。
清单 14.创建文档图标
-
function createFileIcon(fileId){
-
var iconHolder;
-
for(var i=1;i<=120;i++){// 查询第一个为空的位置
-
iconHolder = document.getElementById('iconHolder' + if(!iconHolder.firstChild ){
-
var text = document.createElement('img');
-
text.src = "images/text.gif";
-
text.id = fileId;
-
iconHolder.appendChild(text);
-
text.onclick = function(ev){
-
if(ev.currentTarget){
-
openApp('notebook');// 打开记事本应用程序
-
var saveHolder = document.getElementById('saveHolder');
-
saveHolder.onclick = function(){
-
var title = document.getElementById('title');
-
var content = document.getElementById('content');
-
createFile(fileId, title.value, content.value);// 创建文本文档
-
};
-
-
var openedFileId = ev.currentTarget.id;
-
if(openDatabase != undefined)
-
db.transaction(function(tx) {// 查询数据库,显示文档内容
-
tx.executeSql('SELECT * FROM TextFiles', [], function(tx, rs){
-
for(var i = 0; i < rs.rows.length; i++) {
-
if((rs.rows.item(i).idx+"") == (openedFileId+"")){
-
var title = document.getElementById('title');
-
var content = document.getElementById('content');
-
title.value = rs.rows.item(i).title;
-
content.value = rs.rows.item(i).content;}
-
}
-
});
-
});
-
ev.stopPropagation();
-
}
-
};
-
break;
-
}
-
}//for
-
}
清单14首先在桌面中寻找一个空的div,然后创建一个文档图标,并将其填充至div。文档图标有一个id属性对应文档id。最后为文档图标添加点击事件处理函数,当点击文档图标时,会首先打开记事本,然后根据文档图标的id查询数据库,提取文档的标题和内容进行显示。
图8显示了创建后的文本文档,点击后的效果如图7所示。

图 8.文本文档
时钟
系统提供了一个简单的时钟用以显示当前时间,它由一个表盘以及分针和时针组成,能够随着时间的变化动态地变换。以往的Web应用利用JavaScript或Flash完成此类功能,其复杂性可想而知。借助HTML5的draw API,可以轻松地画出所需的图形,极大的方便了此类应用的构建,此外,HTML5还提供了以往JavaScript无法支持的多线程编程,大大加强了Web应用的交互性和丰富性。
时钟有一个基本的表盘,它仅是一副简单的图片,如图 9 所示。

图 9. 表盘
在表盘之上,建有一个 canvas( 画布 ),如清单15所示。
清单 15.画布
-
<canvas id="canvas" width="128px" height="128px" class="canvas"></canvas>
接下来,清单17将在画布上模拟出时钟以及分针,在这之前,额外需要一个后台线程用以计算时间,它被定义在名为time.js的独立脚本文件中,如清单16所示。
清单 16.后台线程
-
onmessage = function(event)
-
{
-
//var i = 1;
-
setInterval(function() {
-
//i++;
-
postMessage("");
-
}, 60000);
-
};
每过60秒钟,后台线程将会向前台线程发送一个空消息,以告诉前台线程有60秒钟已经过去了。
清单 17.前台线程的初始化
-
var canvas = document.getElementById("canvas");
-
if (canvas == null)
-
return false;
-
var context = canvas.getContext('2d');// 这是一个二维的图像
-
context.lineWidth = 2;
-
context.translate(64, 64);// 定义原点
-
-
// 初始化分针
-
context.beginPath();
-
context.moveTo(0,0);// 从原点开始
-
var date = new Date();
-
var mhx = 37*Math.cos((date.getMinutes()-15)*Math.PI/30);
-
var mhy = 37*Math.sin((date.getMinutes()-15)*Math.PI/30);
-
context.lineTo(mhx, mhy);// 至分针末端所在位置
-
context.closePath();
-
context.stroke();
-
-
// 初始化时针
-
context.beginPath();
-
context.moveTo(0,0);// 从原点开始
-
var date = new Date();
-
var hour = date.getHours();
-
if(hour>=12)
-
hourhour = hour - 12;
-
var minute = date.getMinutes();
-
var hhx = 27*Math.cos((hour-3)*Math.PI/6 + minute*Math.PI/360);
-
var hhy = 27*Math.sin((hour-3)*Math.PI/6 + minute*Math.PI/360);
-
context.lineTo(hhx, hhy);// 至时针末端所在位置
-
context.closePath();
-
context.stroke();
前台线程首先会获取canvas,并设置表盘中心为坐标原点。然后,获取当前时间,计算分针当前所应指向的坐标,然后从原点出发,画出分针。对于时针,若系统为24小时制,需要首先转化为12小时制,此后的处理类似于分针。
接下来,需要将前台与后台线程联系起来,利用HTML5提供的多线程编程方法,声明Worker对象作为后台线程的代理,并利用onmessage事件,对后台线程发出的消息进行处理。
清单 18.前台线程的onmessage事件
-
var worker = new Worker("js/timer.js");
-
-
worker.onmessage = function(event){
-
-
context.clearRect(-64, -64, 128, 128);// 清空分针和时针
-
-
// 重画分针
-
context.beginPath();
-
context.moveTo(0,0);// 从原点开始
-
var date = new Date();
-
var mhx = 37*Math.cos((date.getMinutes()-15)*Math.PI/30);
-
var mhy = 37*Math.sin((date.getMinutes()-15)*Math.PI/30);
-
context.lineTo(mhx, mhy);// 至分针末端所在位置
-
context.closePath();
-
context.stroke();
-
-
// 重画时针
-
context.beginPath();
-
context.moveTo(0,0);// 从原点开始
-
var date = new Date();
-
var hour = date.getHours();
-
if(hour>=12)
-
hourhour = hour - 12;
-
var minute = date.getMinutes();
-
var hhx = 27*Math.cos((hour-3)*Math.PI/6 + minute*Math.PI/360);
-
var hhy = 27*Math.sin((hour-3)*Math.PI/6 + minute*Math.PI/360);
-
context.lineTo(hhx, hhy);// 至时针末端所在位置
-
context.closePath();
-
context.stroke();
-
};
-
worker.postMessage("");
每过60秒钟,后台线程将会向前台线程发送一个空消息,前台线程接收到消息后,首先,清空canvas,然后重新获取当前时间,计算分针以及时针对应的坐标,并重新画出时针和分针,从而完成对分针以及时针的更新,最终,每过1分钟,表盘更新一次,从而模拟出动态时针的效果,如图10所示。

图10.时钟
离线支持
虽然Web操作系统的优点是可以利用网络随时随地进行访问。然而在无法访问网络的情况下,Web操作系统便无法发挥作用。因此Web操作系统有必要在离线状态下,仍能对部分应用及其功能进行支持。事实上,各种浏览器已提供了各式各样的缓存机制以提供对离线应用的支持,然后这些缓存机制往往是临时性的,不可控的。HTML5为开发人员提供了解决此问题的另一种途径,它提供了一种永久性的,自定义的缓存方法,使得Web操作系统可以在离线的状况下,依然支持部分应用的功能。
HTML5离线支持的核心是一个缓存清单,其中列出了需要缓存的文件,本系统中的缓存文件index.manifest,如清单19所示。
清单 19.缓存清单
-
CACHE MANIFEST
-
#version 1.10
-
CACHE:
-
index.html
-
js/app.js
-
js/clock.js
-
js/data.js
-
js/database.js
-
js/desktop.js
-
js/history.js
-
js/taskbar.js
-
js/timer.js
-
js/file.js
-
js/utils.js
-
css/index.css
-
images/appHolder1.png
-
images/background.jpg
-
images/clock.png
-
images/close.gif
-
images/computer.gif
-
images/history.png
-
images/network.gif
-
images/recycleBin.gif
-
images/startIcon.png
-
images/taskBar.png
-
images/vidioplayer.gif
-
images/notebook.gif
-
images/text.gif
-
images/save.gif
-
movs/movie.ogg
-
sounds/WindowsLogonSound.wav
其中,CACHE MANIFEST标示本文件为缓存文件,#version 1.10标示了本文件的版本。
CACHE之后所罗列的则是开发人员自定义的内容,其中包含了所有在离线状态下用户访问应用程序所必不可少的文件。
缓存清单定义结束后,在index.html中插入这个清单文件名,这样,当浏览器加载这个页面的时候,会自动缓存清单文件中所罗列的文件。
清单 20.应用缓存清单
-
<html manifest="index.manifest">
值得一提的是,若要支持离线缓存,除客户端浏览器的支持以外,服务端的支持也是必不可少的,就本系统所使用的tomca 而言,需要在其配置文件web.xml中添加清单 21 所示的条目。
清单 21.服务器端缓存配置
-
<mime-mapping>
-
<extension>manifest</extension>
-
<mime-type>text/cache-manifest</mime-type>
-
</mime-mapping>
最后,禁用本地机器的网络,重新打开浏览器并访问Web操作系统所在的网址,系统中的大部分应用程序依然可以正常工作,如图11所示。

图 11.离线系统
结束语
本文介绍了Web操作系统的基本知识,并与传统的操作系统进行了比较,进而介绍了HTML5这种新技术为Web操作系统开发带来的益处,并与传统的Web前端开发技术进行了比较,最后通过构建一个基本的Web操作系统详细的展现了Web操作系统的基本模式和功能以及支撑其运行的Web前端开发技术是如何实现其具体功能的。从本文的讨论中可以看出,基于HTML5的Web操作系统是未来的一大趋势,必将逐步走入人们的日常生活工作中去。