从本章开始将为大家讲解应用(App)的部分。很多人难以区分Chrome中扩展和应用的区别,后面的内容将向大家介绍何时使用扩展而何时使用应用,以及创建Chrome应用需要注意的地方。
6.1应用与扩展的区别
Chrome将其平台上的程序分为扩展与应用,并且使用了同样的文件结构,那么两者的区别是什么呢?在早期的Chrome版本中两者的区别非常模糊,而且有些扩展也可以用应用实现,反之亦然。但今天看来,Google正在努力使两者的界限变得清晰。
总的来说,扩展与浏览器结合得更紧密些,更加强调扩展浏览器功能。而应用无法像扩展一样轻易获取用户在浏览器中浏览的内容并进行更改,实际上应用有更加严格的权限限制。所以应用更强调是一个独立的与Chrome浏览器关联不大的程序,此时你可以把Chrome看成是一个开发环境,而不是一个浏览器。
不过到目前为止,Google还没有强制规定只能用扩展做什么,只能用应用做什么,所以对于那些扩展和应用都可以实现的功能,到底用何种方式实现,那是你自己的选择。不过我建议大家遵照上述的原则选择实现方式。
除此之外,Chrome应用还分为Hosted App(托管应用)和Packaged App(打包应用),这两者也是有明显区别的。相对而言,Hosted App更像是一个高级的书签,这种应用只提供一个图标和Manifest文件,在Manifest中声明了此应用的启动页面URL,以及包含的其他页面URL和这些页面请求的高级权限。比如下面的例子创建了一个启动页面为http://mail.google.com/mail/,包含mail.google.com/mail/和www.google.com/mail/且请求unlimitedStorage和notifications权限的应用。
{ "name": "Google Mail", "description": "Read your gmail", "version": "1", "app": { "urls": [ "*://mail.google.com/mail/", "*://www.google.com/mail/" ], "launch": { "web_url": "http://mail.google.com/mail/" } }, "icons": { "128": "icon_128.png" }, "permissions": [ "unlimitedStorage", "notifications" ] }
Packaged App,顾名思义,就是将所有文件打包在一起的应用,这类应用通常可以在离线时使用,因为它运行所需的全部文件都在本地。
由于Hosted App结构和功能都相对简单,所以后面的内容都将重点讲解Packaged App。
6.2更加严格的内容安全策略
在讲解Chrome扩展的安全策略时,提到过其不允许inline-script,默认也不允许引用外部的JavaScript文件,而Chrome应用使用了更加严格的限制。
Chrome扩展和应用都使用了CSP(Content Security Policy)声明可以引用哪些资源,虽然之前我们并没有涉及到CSP的内容,但是Chrome扩展和应用会在我们创建时提供一个默认的值,对于Chrome扩展来说是script-src 'self'; object-src 'self'。上面的CSP规则表示只能引用自身(同域下)的JavaScript文件和自身的object元素(如Flash等),其他资源未做限定。
Chrome扩展允许开发者放宽一点点CSP的限制,即可以在声明权限的情况下引用https协议的外部JavaScript文件,如script-src 'self' https://ajax.googleapis.com; object-src 'self'。但是Chrome应用不允许更改默认的CSP设置。
那么CSP到底是什么呢?它是如何定义安全内容引用范围的?
CSP通常是在header或者HTML的meta标签中定义的,它声明了一系列可以被当前网页合法引用的资源,如果不在CSP声明的合法范围内,浏览器会拒绝引用这些资源,CSP的主要目的是防止跨站脚本攻击(XSS)。
CSP还是W3C草案,最新的1.1版文档还在撰写之中,所以在未来可能还会增加更多特性。目前CSP定义了9种属性,分别是connect-src、font-src、frame-src、img-src、media-src、object-src、style-src、script-src和default-src。connect-src声明了通过XHR和WebSocket等方式的合法引用范围,font-src声明了在线字体的合法引用范围,frame-src声明了嵌入式框架的合法引用范围,img-src声明了图片的合法引用范围,media-src声明了声音和视频媒体的合法引用范围,object-src声明了Flash等对象的合法引用范围,style-src声明了CSS的合法引用范围,script-src声明了JavaScript的合法引用范围,最后default-src声明了未指定的其他引用方式的合法引用范围。
CSP的可选属性值有'self'、'unsafe-inline'、'unsafe-eval'、'none',这四个属性值都必须带引号代表特殊含义的值,分别表示允许引用同域资源、允许执行inline-script、允许执行字符串转换的代码(如在eval函数和setTimeout中的字符串代码)、不允许引用任何资源。
同时还支持host,如www.google.com表示可以引用www.google.com的资源,或者*.google.com允许引用google.com所有子域的资源(但不允许google.com根域的资源)。也可以声明只允许引用https下的资源,属性值声明为https:即可。或者声明只允许引用特定协议特定host的资源,如https://github.com。*则代表任何资源,即不受限制。
Chrome应用默认的CSP规则为:
default-src 'self'; connect-src *; style-src 'self' data: chrome-extension-resource: 'unsafe-inline'; img-src 'self' data: chrome-extension-resource:; frame-src 'self' data: chrome-extension-resource:; font-src 'self' data: chrome-extension-resource:; media-src *;
也就是说在Chrome应用中,我们可以使用XHR请求任何资源;但是只能引用应用自身的CSS文件或者是dataURL转换的CSS文件和chrome-extension-resource协议下的CSS文件,同时我们可以在HTML直接写style代码块和在DOM中指定style属性;图片、嵌入式框架和字体只能引用自身或者dataURL转换的文件和chrome-extension-resource协议下的文件;可以引用任何音频和视频媒体资源;其他未指定的引用方式(object-src和script-src)只能引用自身资源。
这么做显然会大大提高Chrome应用的安全性,防止被黑客利用盗取用户的数据,但也显然带来了新的问题。从Chrome应用的CSP规则中我们发现其不允许通过嵌入式框架引用外部资源,那么如果我们真的需要将一个外部页面展示在Chrome应用中怎么办呢?Google提供了webview标签代替iframe标签,使用webview标签必须指定大小和引用URL。
<webview src="http://news.google.com/" width="640" height="480"></webview>
同样Chrome应用也不允许引用外部的图片,但是我们可以通过XHR请求外部图片资源(XHR是可以请求到任何资源的,只要在Manifest中声明权限),然后通过转换成blob URL再添加到应用中。
var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://supersweetdomainbutnotcspfriendly.com/image.png', true); xhr.responseType = 'blob'; xhr.onload = function(e) { var img = document.createElement('img'); img.src = window.URL.createObjectURL(this.response); document.body.appendChild(img); }; xhr.send();
最后如果无法避免使用inline-script和eval等方式执行JavaScript代码,我们可以将“违规”的页面放入沙箱中执行,方法是在Manifest的sandbox中列出需要在沙箱中执行的页面。
"sandbox": { "pages": ["sandboxed.html"] }
6.3图标设计规范
虽然Google没有对应用图标的设计做出强制规定,但给出了一份建议文档,完整文档可以参见https://developer.chrome.com/webstore/images,本节将根据原始文档内容,对图标设计规范相关的部分进行转述。
在应用展示页面(chrome://apps/),Chrome默认会以128像素的尺寸展示应用图标,但根据窗口实际尺寸会自动进行缩放,最小展示64像素的图标。
Chrome应用的图标只支持png格式,而且Google建议将图标的可视部分定在96像素之内,在可视部分周围留出边距。即如下图所示,将正方形的图标限定在方形框中。
正方形图标模板,图片来自developers.google.com
如果是圆形的图标,同样限定在上述模板的方形框中会显得过小,可以控制在下图的圆形图标模板的圆形圈中。
圆形图标模板,图片来自developers.google.com
对于那些不规则的图标,可以结合正方形和圆形的模板进行设计。正方形和圆形的图标看上去往往给人感觉比实际的尺寸要大一些,所以在设计图标时要注意这一点,尽量在视觉上让不同的图标保持一致的尺寸。下面是不同形状的图标在一起的对比。
不同形状图标尺寸的对比,图片来自developers.google.com
下面是一些具体的例子。
不同形状图标设计实例,图片来自developers.google.com
由于Chrome允许用户更换主题,所以应考虑图标在不同明暗背景下的显示效果。如果图标本身是浅色系,则应在图标周围添加深色边界,反之亦然。
最后Google还建议,如果设计的图标有一定浮雕效果,凸起高度最好限制在4像素。图标最好是正对用户的,而不是侧面45度的透视效果,如下图所示。
诸如此类透视效果的图标不建议使用,图片来自developers.google.com
虽然Google不会因为开发者未遵循上述规范而驳回或撤销应用,但是图标是一个应用给用户的第一印象,与周围应用图标风格明显有别的应用会流失一部分用户。
6.4应用的生命周期
本章第一节的内容简单讲解了应用和扩展的区别,本节将为大家讲解应用和扩展的另一大区别,生命周期。
对于扩展来说,如果定义了后台脚本,同时指定persistent属性为true,那么这个扩展只要浏览器运行就会一直运行,除非用户手动去关闭它。如果声明了background权限,则这个扩展会一开机就运行,并且一直运行下去。但是对于应用来说情况会有所不同。
Chrome应用目前不允许使用永久运行的后台脚本(仅指Packaged App),无论你多么想让它一直运行下去,比如用于监听来自WebSocket的消息等,Chrome在认为应该关闭它时就会关闭它。这种做法确实有利于减少不必一直运行的应用消耗有限的内存资源,但就像上面举的例子那样,也同样会在某些方面带来局限。
既然关闭应用的行为并非开发者可以直接控制,那么我们就有必要了解应用何时开始运行,又会在何时被关闭——也就是应用的生命周期。下图给出了应用生命周期的简单图示。
Chrome应用的生命周期,图片来自developer.chrome.com
Event Page就是Chrome应用的后台脚本,它用于监听各种事件。当用户运行应用,Event Page加载完成后,onLaunched事件就会被触发。如果这个应用运行后要向用户提供一个窗口,就是在onLaunched事件触发后后台脚本创建的。当这个窗口被关闭后,并且Event Page也没有需要处理的任务,Chrome就会彻底关闭这个应用——连同Event Page一起关闭。在关闭应用前会触发onSuspend事件,这个事件可以提醒应用的后台脚本应用即将被关闭,以给应用一个准备退出的机会。
每一个应用都会有一个Event Page,可以通过Event Page监听onLaunched事件,然后创建一个窗口。在Manifest的app属性中,通过background域定义Event Page。
"app": { "background": { "scripts": ["background.js"] } }
然后在background.js中指定应用启动时创建窗口1。
1 从Chrome 36开始,create方法的选项对象不再支持bounds、minWidth、maxWidth、minHeight和maxHeight属性,请使用innerBounds和outerBounds属性代替。innerBounds和outerBounds的属性值包括width、height、left、top、minWidth、maxWidth、minHeight和maxHeight。
chrome.app.runtime.onLaunched.addListener(function() { chrome.app.window.create('main.html', { id: 'MyWindowID', bounds: { width: 800, height: 600, left: 100, top: 100 }, minWidth: 800, minHeight: 600 }); });
有关在应用中创建窗口的具体内容将在下一节给出。
在应用首次被安装或者更新到新版本时,会触发onInstalled事件,Event Page可以在此事件触发时做一些初始化任务,如向本地写入默认设置、向用户展示欢迎窗口等等。
Chrome应用可以使用chrome.storage存储数据,如:
chrome.runtime.onInstalled.addListener(function() { chrome.storage.local.set(object items, function callback); });
数据保存在用户本地时,可能会面临数据永远丢失的风险——当用户卸载应用或者重新安装操作系统后,应用保存在本地的数据都会永久丢失。为防止这种风险,可以选择使用一种在线的存储方式,最简单的方法就是使用Chrome storage API中的sync域储存数据:
chrome.runtime.onInstalled.addListener(function() { chrome.storage.sync.set(items, function(){...}); });
这样这些数据会随Chrome在线同步保存在Google的服务器中。这对诸如应用设置等数据非常重要,因为用户也许会有一天重新安装卸载掉的应用。
最后当Chrome认为一个应用处于空闲状态时就会清理掉这个应用的进程,在清理之前会触发onSuspend事件,以让Event Page在退出前有机会做一些清理工作,从而不会导致意外退出。
有一种办法可以让应用一直保持运行,就是永远都不关闭前端窗口——当用户关闭窗口时隐藏它而不是真的关闭,或者让后台直接创建一个隐藏的窗口,这样前端窗口就可以一直运行且不显示出来。当用户再次启动应用时,再显示出来就可以了。
虽然也可以使用setInterval定期做一些简单的任务,如每10秒就让Event Page做点什么,但是Chrome清理应用进程的时间间隔是可以通过指定参数更改的,并且将来很有可能会发生变化,所以这种方法并不值得推荐。
对于个别应用确实需要长时间在后台运行,Google也清楚这一点,但是为什么Chrome应用却不提供background权限呢?按照Chromium开发者的说法,由于要专门为应用提供system tray特性,应用可以通过这一特性常驻后台,在没有提供这一特性前,为了防止应用开发者依赖background权限,所以禁用了它。
那么现在的情况就比较尴尬了,system tray尚未实现,background权限又已被禁用,所以如果想让应用随系统启动并常驻后台,目前一种可行的方法——虽然笨拙但却有效,安装一个拥有background权限的扩展,这样可以让Chrome一直运行;在Event Page中添加一个setInterval任务以防止被Chrome认为应用处于空闲状态;创建一个Chrome应用的快捷方式(在chrome://apps/中应用图标的右键菜单中,选择“创建快捷方式...”),并将这个快捷方式放到系统的启动文件夹下。
6.5应用窗口
Chrome应用中创建的窗口与Chrome浏览器中的窗口没有任何关系,这一点与Chrome扩展不同。 本节将详细讲解应用窗口的创建风格以及窗口相关的其他方法和事件。请记住,我们并不是在创建一个网页,而是在创建一个桌面程序,不要把应用的窗口风格搞得和网页一样。
6.5.1创建窗口
通过chrome.app.window.create方法可以创建应用窗口,应用窗口与扩展中新建的窗口并不相同,应用窗口的默认样式与操作系统并没有太大关系,所以不同平台下Chrome应用的窗口能够保持较高的一致性。
如果不给出任何有关窗口外观样式的参数,应用窗口会是下面的样子:
应用窗口默认样式
创建这个窗口的代码为:
chrome.app.window.create('blank.html', { id: 'default' });
其中blank.html为新建窗口嵌入的页面。
在上面的代码中只指定了窗口的id,在以后创建新窗口时,也建议总是指定一个窗口id,id相同的窗口只会创建一个。
这个窗口没有指定大小,Chrome给出的默认大小一般是512×384像素(不算标题栏),标题栏一般的默认高度是22像素,具体与系统设置有关。
窗口的控制按钮Chrome根据系统不同给出了相应的样式和位置,以照顾不同平台用户的使用习惯。比如Windows下控制按钮是方形并放置在右上角的,而对于OS X则是圆形的并放置在左上角。
Chrome默认的应用窗口非常简洁,但在实际使用时需要注意在某些系统下默认窗口是没有阴影效果的,在白色背景的衬托下,用户将很难找到应用窗口的边缘。比如在早期版本的Windows下,如果没有定义一个窗口边框,应用窗口又恰好在一张白色网页前打开,用户就会看到下面的情景(窗口两侧是未被遮挡的Google搜索框):
未定义窗口边框时在个别系统下用户会很难找到窗口边缘
最简单的方法是用CSS为body添加一个边框:
body { border: black 1px solid; }
或者为body指定一个背景颜色,而不使用窗口默认的白色:
body { background: #EEE; }
在创建窗口时也可以指定窗口的大小,如:
chrome.app.window.create('main.html', { id: 'main', bounds: { width: 800, height: 600 } });
如果窗口定义了id,且用户对窗口进行了尺寸调整,下次再创建此窗口时Chrome会使用用户上次调整后的尺寸取代代码中的尺寸,这也是指定窗口id的好处之一。
通过bounds指定的尺寸是不包含窗口外框的,如标题栏等,只是窗口内嵌入页面的显示尺寸。如果不希望用户调整窗口尺寸可以指定窗口的resizable属性值为false:
chrome.app.window.create('main.html', { id: 'main', bounds: { width: 800, height: 600 }, resizable: false });
也可以指定窗口可调节尺寸的范围,比如:
chrome.app.window.create('main.html', { id: 'main', bounds: { width: 800, height: 600, minWidth: 400, minHeight: 300, maxWidth: 1600, maxHeight: 1200 } });
除了指定窗口大小,还可以指定窗口位置,如果不指定,则默认显示在屏幕中心。
chrome.app.window.create('main.html', { id: 'main', bounds: { top: 0, left: 0 } });
上面的代码创建了一个在屏幕左上角的窗口。如果指定了id,Chrome同样会记住用户上次将窗口放置的位置,并在下次创建窗口时使用记录的值。
其他的特性还包括新建窗口状态(最大化、最小化、正常或者全屏)和窗口是否总是在最前面,在声明app.window.alwaysOnTop权限的情况下,下面的代码创建了一个总是在最前面的全屏窗口:
chrome.app.window.create('main.html', { id: 'main', state: 'fullscreen', alwaysOnTop: true });
其中state的值还可以是normal、maximized和minimized。
最后窗口的hidden属性是非常重要的,它可以让窗口在后台静默运行,类似于后台脚本,但在需要时可以使用show方法重新显示出来,具体有关隐藏窗口的内容将在后面的内容中详细讲解。下面的代码创建了一个隐藏的窗口:
chrome.app.window.create('main.html', { id: 'main', hidden: true });
窗口创建完成后我们也可以使用回调函数获取刚刚创建窗口的属性:
chrome.app.window.create('main.html', {'id': 'main'}, function(appWindow){ console.log(appWindow); });
6.5.2样式更加自由的窗口
在上一节中讲解了创建应用窗口的方法,同时也介绍了部分窗口属性。虽然默认的窗口样式已经非常简洁,我们可以在窗口内部自由地进行设计,但是自带的标题栏却无法更改样式,本节将进一步讲解如何创建样式更加自由的窗口。
将窗口的frame属性值定为'none',新建的窗口将不显示标题栏,如:
chrome.app.window.create('blank.html', { id: 'blank', frame: 'none' });
上面的代码会生成下面所示的窗口:
没有标题栏的窗口
由于这个窗口没有标题栏,也没有控制按钮,所以无法拖拽,也无法通过在窗口上点击鼠标来关闭它,或者改变它的显示状态,比如最大化最小化等。
我们可以在HTML中指定可以拖拽的元素,这样当鼠标在这些元素上的时候就可以拖拽整个窗口了,下面对blank.html进行改进一下:
<html> <head> <title>A more free style window</title> <style> body { margin: 0; padding: 0; border: #EEE 1px solid; } #title_bar { -webkit-app-region: drag; height: 22px; line-height: 22px; font-size: 16px; background: #EEE; padding: 0 10px; box-sizing: border-box; } </style> </head> <body> <div id="title_bar">A more free style window</div> </body> </html>
可以拖拽的窗口
现在这个窗口可以拖拽了,重点就在于上面代码中的-webkit-app-region: drag。
在介绍自定义窗口控制按钮之前需要先了解获取当前窗口的方法,因为所有控制函数都是当前窗口对象的子元素。
var current_window = chrome.app.window.current();
上面的代码可以获取到当前代码所在窗口的窗口对象。
窗口对象的close方法可以关闭当前窗口,如:
current_window.close();
同样还可以最大化窗口、最小化窗口、还原窗口或全屏窗口:
current_window.maximize(); current_window.minimize(); current_window.restore(); current_window.fullscreen();
也可以获取当前窗口是否处于某种状态:
var is_maximize = current_window.isMaximized(); var is_minimize = current_window.isMinimized(); var is_fullscreen = current_window.isFullscreen();
以上函数均返回布尔型结果。
下面给blank.html页面添加上控制按钮。首先在title_bar的右侧添加三个圆形的按钮,分别对应最小化、最大化(还原)和关闭。
<div id="title_bar">A more free style window <a id="close" href="#"></a> <a id="maximize" href="#"></a> <a id="minimize" href="#"></a> </div>
然后在样式表中添加这三个按钮的显示样式:
#title_bar a { display: inline-block; float: right; height: 12px; width: 12px; margin: 5px; border: black 1px solid; box-sizing: border-box; border-radius: 6px; }
在添加按钮元素时,之所以将关闭按钮放在最前面,是因为样式表中定义了float: right,这将使最先出现的元素放置在最右侧。
下面是添加按钮后的窗口:
添加按钮后的窗口
现在看起来虽然感觉好多了,但是当鼠标悬浮在按钮上时并没有反馈交互,所以我们还应该更加细化一下设计。继续在样式表中添加交互特性:
#title_bar a:hover { background: black; }
当鼠标放在按钮上,按钮就会变成黑色的实心圆:
鼠标悬浮在按钮上的反馈交互
下面来为这三个按钮绑定事件。最小化和关闭按钮都很容易:
var current_window = chrome.app.window.current(); document.getElementById('minimize').onclick = function(){ current_window.minimize(); } document.getElementById('close').onclick = function(){ current_window.close(); }
对于最大化的按钮,因为同时也是还原窗口的按钮,所以当用户点击时要进行判断:
document.getElementById('maximize').onclick = function(){ current_window.isMaximized() ? current_window.restore() : current_window.maximize(); }
最后将写好的JavaScript紧贴在</body>标签之前引用。
但当我们进行测试时却发现点击按钮并没有反应,这是怎么回事呢?因为我们将控制按钮放在了title_bar之内,而title_bar之前定义了-webkit-app-region: drag样式用于拖拽,这会使Chrome阻止鼠标点击事件,解决的方法是专门为控制按钮定义-webkit-app-region: no-drag样式。这一点没有在创建按钮时直接提出是为了强调其重要性,尤其是对于整个窗口都可以拖动的应用,应该为所有的控制按钮都专门指定-webkit-app-region: no-drag样式。
下面是改进后完整的HTML代码:
<html> <head> <title>A more free style window</title> <style> body { margin: 0; padding: 0; border: #EEE 1px solid; } #title_bar { -webkit-app-region: drag; height: 22px; line-height: 22px; font-size: 16px; background: #EEE; padding: 0 10px; box-sizing: border-box; } #title_bar a { -webkit-app-region: no-drag; display: inline-block; float: right; height: 12px; width: 12px; margin: 5px; border: black 1px solid; box-sizing: border-box; border-radius: 6px; } #title_bar a:hover { background: black; } </style> </head> <body> <div id="title_bar">A more free style window <a id="close" href="#"></a> <a id="maximize" href="#"></a> <a id="minimize" href="#"></a> </div> <script src="control.js"></script> </body> </html>
下面是完整的JavaScript代码:
var current_window = chrome.app.window.current(); document.getElementById('minimize').onclick = function(){ current_window.minimize(); } document.getElementById('close').onclick = function(){ current_window.close(); } document.getElementById('maximize').onclick = function(){ current_window.isMaximized() ? current_window.restore() : current_window.maximize(); }
6.5.3获取窗口
之前我们接触了chrome.app.window.current方法获取代码所在的窗口,除此之外还可以通过chrome.app.window.getAll方法获取全部窗口,以及chrome.app.window.get方法获取指定窗口。
chrome.app.window.current和chrome.app.window.get方法均返回窗口对象,chrome.app.window.getAll方法则返回包含若干窗口对象的数组。
调用chrome.app.window.get方法时需要指定窗口id:
var main_window = chrome.app.window.get('main');
以下是窗口对象的完整结构,其中除id为字符串、contentWindow为JavaScript window object,其他均为函数。
{ focus: 将焦点放在窗口上, fullscreen: 将窗口全屏, isFullscreen: 判断窗口是否处于全屏状态, minimize: 将窗口最小化, isMinimized: 判断窗口是否处于最小化状态, maximize: 将窗口最大化, isMaximized: 判断窗口是否处于最大化状态, restore: 还原窗口, moveTo: 将窗口移动到指定位置,调用方法为moveTo(left, top), resizeTo: 将窗口尺寸设定为指定大小,调用方法为resizeTo(width, height), drawAttention: 将窗口高亮显示, clearAttention: 清除窗口高亮显示, close: 关闭窗口, show: 显示隐藏窗口, hide: 隐藏窗口, getBounds: 获取窗口内容区域尺寸和位置, setBounds: 设置窗口内容区域尺寸和位置, isAlwaysOnTop: 判断窗口是否一直显示在最前端, setAlwaysOnTop: 将窗口设为总是最前端显示, contentWindow: JavaScript window object, id: 窗口id,此id为创建时所指定 }
获取窗口时,如果指定的窗口不存在则返回null。使用getAll方法时,如果不存在任何窗口则返回一个空数组。
在6.4节中提到了用隐藏窗口的方法防止应用被Chrome关闭,下面对之前的代码进行更改。首先将关闭按钮绑定的事件改为隐藏:
var current_window = chrome.app.window.current(); document.getElementById('close').onclick = current_window.hide();
其次将Event Page中启动事件改写成先判断窗口是否存在,如果存在则调用show方法显示,否则创建:
chrome.app.runtime.onLaunched.addListener(function() { var main_window = chrome.app.window.get('main'); if(main_window){ main_window.show(); } else{ chrome.app.window.create('main.html', { id: 'main', bounds: { width: 800, height: 600, left: 100, top: 100 }, frame: 'none' }); } });
当窗口关闭后,可以看到扩展程序管理器里显示main.html依然在运行。
使用hide方法阻止应用被关闭
6.5.4窗口事件
应用窗口有6种事件,其中有4种用于监听窗口状态,分别是onFullscreened、onMaximized、onMinimized和onRestored:
chrome.app.window.onFullscreened.addListener(function(){ //do something when the window is set to fullscreen. }); chrome.app.window.onMaximized.addListener(function(){ //do something when the window is set to maximized. }); chrome.app.window.onMinimized.addListener(function(){ //do something when the window is set to minimized. }); chrome.app.window.onRestored.addListener(function(){ //do something when the window is set to restored. });
另外两种事件一个用于监听窗口尺寸变化,另一个用于监听窗口被关闭:
chrome.app.window.onBoundsChanged.addListener(function(){ //do something when the window is resized. }); chrome.app.window.onClosed.addListener(function(){ //do something when the window is closed. });
6.6编写第一个Chrome应用
在编写Chrome应用时请时刻记住,这已经不是单纯地开发浏览器扩展了,现在要编写的是一款真正的桌面程序,而Chrome只是类似CLR和Java的环境而已。
下面我们来一起编写一个计算机性能监视器。
首先来创建Manifest文件:
{ "app": { "background": { "scripts": ["background.js"] } }, "manifest_version": 2, "name": "Performance Monitor", "version": "1.0", "description": "A performance monitor to show cpu and memory status.", "icons": { "16": "images/icon16.png", "48": "images/icon48.png", "128": "images/icon128.png" }, "permissions": [ "system.cpu", "system.memory" ] }
下面编写background.js脚本,根据6.5的内容可以直接写出如下代码:
chrome.app.runtime.onLaunched.addListener(function() { chrome.app.window.create('main.html', { 'id': 'main', 'bounds': { 'width': 542, 'height': 360 }, 'resizable': false, 'frame': 'none' }); });
同理,main.html中的自定义菜单我们也用上一节提到的代码,但取消最大化按钮。为绘制曲线和饼图,本例使用了一个JS图表库,Chart.js,有关Chart.js的详细内容可以通过http://www.bootcss.com/p/chart.js/查看。
下面是main.html的代码:
<html> <head> <title>Performance Monitor</title> <style> body { margin: 0; padding: 0; border: #EEE 1px solid; } #title_bar { -webkit-app-region: drag; height: 22px; line-height: 22px; font-size: 16px; background: #EEE; padding: 0 10px; box-sizing: border-box; } #title_bar a { -webkit-app-region: no-drag; display: inline-block; float: right; height: 12px; width: 12px; margin: 5px; border: gray 1px solid; box-sizing: border-box; border-radius: 6px; } #title_bar a:hover { background: gray; } #box_body { padding: 20px; } .chart { margin-bottom: 20px; font-size: 0; } .usage_item { color: gray; padding: 2px 0; font-size: 14px; border-bottom: #EEE 1px solid; margin-bottom: 4px; } </style> </head> <body> <div id="title_bar">Performance Monitor <a id="close" href="#"></a> <a id="minimize" href="#"></a> </div> <div id="box_body"> <div class="usage_item">CPU Usage</div> <div class="chart"> <canvas id="cpu_total" width="100" height="100"></canvas> <canvas id="cpu_history" width="400" height="100"></canvas> </div> <div class="usage_item">Memory Usage</div> <div class="chart"> <canvas id="mem_total" width="100" height="100"></canvas> <canvas id="mem_history" width="400" height="100"></canvas> </div> </div> <script src="control.js"></script> <script src="Chart.js"></script> <script src="main.js"></script> </body> </html>
其中的canvas用来展示数据。
control.js的代码:
var current_window = chrome.app.window.current(); document.getElementById('minimize').onclick = function(){ current_window.minimize(); } document.getElementById('close').onclick = function(){ current_window.close(); }
下面来编写main.js,这个脚本用来定时获取数据并进行展示。
function getCpuUsage(callback){ chrome.system.cpu.getInfo(function(info){ var total = 0; var user = 0; var kernel = 0; for(var i=0; i<info.processors.length; i++){ total += info.processors[i].usage.total - cpu_history.last_total[i]; cpu_history.last_total[i] = info.processors[i].usage.total; user += info.processors[i].usage.user - cpu_history.last_user[i]; cpu_history.last_user[i] = info.processors[i].usage.user; kernel += info.processors[i].usage.kernel - cpu_history.last_kernel[i]; cpu_history.last_kernel[i] = info.processors[i].usage.kernel; } user = Math.round(user/total*100); kernel = Math.round(kernel/total*100); callback({user:user,kernel:kernel,total:user+kernel}); }); } function getMemUsage(callback){ chrome.system.memory.getInfo(function(info){ callback(info); }); } function updateCpuHistory(){ getCpuUsage(function(usage){ cpu_history.user.shift(); cpu_history.user.push(usage.user); cpu_history.kernel.shift(); cpu_history.kernel.push(usage.kernel); cpu_history.total.shift(); cpu_history.total.push(usage.total); showCpu(); }); } function updateMemHistory(){ getMemUsage(function(usage){ mem_history.used.shift(); mem_history.used.push(Math.round((usage.capacity-usage.availableCapacity)/usage.capacity*100)); showMem(); }); } function updateData(){ updateCpuHistory(); updateMemHistory(); } function showCpu(){ var history = { labels : (function(){for(var i=0,labels=[];i<ponits_num;labels.push(''),i++);return labels;})(), datasets : [ { fillColor : "rgba(220,220,220,0.5)", data : cpu_history.total }, { fillColor : "rgba(90,140,255,0.5)", data : cpu_history.kernel }, { fillColor : "rgba(255,90,90,0.5)", data : cpu_history.user } ] }; var now = [ { value: cpu_history.total[ponits_num-1], color:"rgba(220,220,220,0.7)" }, { value : 100-cpu_history.total[ponits_num-1], color : "rgba(220,220,220,0.3)" } ]; var his_ctx = document.getElementById('cpu_history').getContext("2d"); var now_ctx = document.getElementById("cpu_total").getContext("2d"); new Chart(his_ctx).Line(history, {scaleFontSize:4,pointDot:false,animation:false}); new Chart(now_ctx).Pie(now, {segmentShowStroke:false,animation:false}); } function showMem(){ var history = { labels : (function(){for(var i=0,labels=[];i<ponits_num;labels.push(''),i++);return labels;})(), datasets : [ { fillColor : "rgba(220,220,220,0.5)", data : mem_history.used } ] }; var now = [ { value: mem_history.used[ponits_num-1], color:"rgba(220,220,220,0.7)" }, { value : 100-mem_history.used[ponits_num-1], color : "rgba(220,220,220,0.3)" } ]; var his_ctx = document.getElementById('mem_history').getContext("2d"); var now_ctx = document.getElementById("mem_total").getContext("2d"); new Chart(his_ctx).Line(history, {scaleFontSize:4,pointDot:false,animation:false}); new Chart(now_ctx).Pie(now, {segmentShowStroke:false,animation:false}); } function init(){ cpu_history = { user: [], kernel: [], total: [], last_user: [], last_kernel: [], last_total: [] }; mem_history = { used: [] }; init_cpu_history(); } function init_cpu_history(){ for(var i=0; i<ponits_num; i++){ cpu_history.user.push(0); cpu_history.kernel.push(0); cpu_history.total.push(0); } chrome.system.cpu.getInfo(function(info){ for(var i=0; i<info.processors.length; i++){ cpu_history.last_total.push(info.processors[i].usage.total); cpu_history.last_user.push(info.processors[i].usage.user); cpu_history.last_kernel.push(info.processors[i].usage.kernel); } init_mem_history(); }); } function init_mem_history(){ for(var i=0; i<ponits_num; i++){ mem_history.used.push(0); } updateData(); setInterval(updateData, 1000); } var cpu_history, mem_history, ponits_num=20; init();
其中getCpuUsage和getMemUsage函数分别用于获取CPU和内存的使用量。值得注意的是,Chrome返回的CPU使用量是累计使用时间,并不是获取瞬间的CPU占用量,所以需要对前后两个时间点获得的结果做差。showCpu和showMem方法将获取的数据显示成图表,两个函数都是参照Chart.js文档编写的,感兴趣的读者可以自行查阅。
另外Chart.js本身有new Function声明函数的部分,由于之前介绍的CSP规则,这在Chrome应用中是不被允许的,所以本例中的Chart.js是编者改写后的,具体差异读者可以下载后自行对照。在以后编写Chrome应用引用现成的库时,可能会经常遇到由于CSP的限制而无法直接运行的情况,这需要读者拥有自行更改库代码的能力。
本例应用运行的截图如下所示:
Performance Monitor运行截图
本节中讲解的实例的源代码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/performance%20monitor下载到。
-----------------------------------------------------
转载请注明来源此处
原地址:#
发表