该页面翻译自 Google Chrome Extensions 与 Google Chrome Apps。除非特别说明,该页面的内容遵循 Creative Commons Attribution 3.0 License,代码示例遵循 BSD License。
Chrome 应用有一项严格的内容安全策略,不会让用户执行或加载远程托管的代码和资源。
然而许多应用都需要加载并显示来自远程位置的内容。例如新闻阅读器需要以内嵌方式显示远程内容,或者从远程 URL 加载并显示图片。
互联网上的站点有固有的安全风险,直接在您的应用中以提升的权限渲染网页是漏洞的潜在来源。
Chrome 应用为开发者提供了在 <webview>
标签中安全渲染第三方内容的能力。webview
就像 iframe
一样,但是您能够更灵活地控制它,并具有额外的安全性。它在单独的经过沙盒屏蔽的进程中运行,不能直接与应用通信。
webview 包含一些很简单的 API,在您的应用中您可以:
webview 的 URL。webview 是否加载完成,如果可能的话在历史栈中前进和后退。
我们会修改我们的代码,当用户单击某个链接时,在 webview 中渲染拖放操作中的
URL 的内容。
在清单文件中请求一个新的权限——"webview",AngularJS manifest.json 和 JavaScript manifest.json 是一样的:
"permissions": ["storage", "webview"]
在视图中添加一个 <webview> 标签和一个链接:AngularJS index.html 或
JavaScript index.html:
<!-- in TODO item: -->
<a ng-show="todo.uri" href="" ng-click="showUri(todo.uri)">(view url)</a>
<!-- at the bottom, below the end of body: -->
<webview></webview>
<!-- right after the "dropText" div: -->
<webview></webview>
<!-- in the TODO template, right before the end of the li: -->
<a style="display: none;" href="">(view url)</a>
在样式表中为 webview 标签设置合适的宽度和高度(默认大小为零),AngularJS todo.css 和 JavaScript todo.css 是一样的:
webview {
width: 100%;
height: 200px;
}
我们现在只需要向
AngularJS controller.js
添加 showUrl方法,或者向
JavaScript controller.js
添加一个 showUrl 事件处理程序。
$scope.showUri = function(uri) {
var webview=document.querySelector("webview");
webview.src=uri;
};
if(/^http:\/\/|https:\/\//.test(todoObj.text)) {
var showUrl = el.querySelector('a');
showUrl.addEventListener('click', function(e) {
e.preventDefault();
var webview=document.querySelector("webview");
webview.src=todoObj.text;
});
showUrl.style.display = 'inline';
}
为了测试,请打开应用,单击右键,并选择“重新加载应用”。您应该能够在拖放的 URL
待办事项上单击“view url”链接,相应的网页就会显示在 webview
中。如果没有显示的话,审查页面并确认您正确设置了 webview 的大小。
如果您遇到了困难,希望立刻看到修改后的应用,请进入
chrome://extensions,加载未打包的
AngularJS 1_webview 或
JavaScript 1_webview
,并从新标签页中运行应用。
如果您试着在 index.html 中添加一个 <img>
标签,并将它的 src
属性指向网上的任何一个站点,则会在控制台中产生如下异常,图片不会加载:
Refused to load the image 'http://angularjs.org/img/AngularJS-large.png' because it violates the following Content Security Policy directive: "img-src 'self' data: chrome-extension-resource:".(拒绝加载图片 'http://angularjs.org/img/AngularJS-large.png',因为它违反了以下内容安全策略的指示:"img-src 'self' data: chrome-extension-resource:"。)
由于内容安全策略的限制,Chrome 应用不能直接在 DOM 中加载任何外部资源。
为了避免这一限制,您可以使用 XHR 请求,获取远程文件对应的
Blob,并以合适的方式使用它。例如,<img> 标签可以使用 Blob
URL。让我们修改我们的应用,如果拖放的 URL
表示图片的话,在待办事项列表中显示一个小图标。
在您开始发出 XHR 请求前,您必须请求权限。因为我们希望允许用户拖放来自任意服务器的图片,我们需要请求向任意 URL 发出 XHR 的权限。修改 AngularJS manifest.json 或 JavaScript manifest.json:
"permissions": ["storage", "webview", "<all_urls>"]
在您的项目中添加一个占位符图片
,当我们加载正确的图片时显示它。
然后在视图中向待办事项添加 <img> 标签:AngularJS index.html 或
JavaScript index_html:
<img style="max-height: 48px; max-width: 120px;" ng-show="todo.validImage"
ng-src="{{todo.imageUrl}}"></img>
<img style="max-height: 48px; max-width: 120px;"></img>
在 JavaScript controller.js 中,添加一个占位符图片的常量:
const PLACEHOLDER_IMAGE = "loading.gif";
您很快就会看到,只有当待办事项的 validImage 属性为 true 时才会显示该元素。
添加 loadImage 方法,发出 XHR 请求,并执行一个接受 Blob URL 的回调函数,AngularJS loader.js 和 JavaScript loader.js 是一样的:
var loadImage = function(uri, callback) {
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function() {
callback(window.webkitURL.createObjectURL(xhr.response), uri);
}
xhr.open('GET', uri, true);
xhr.send();
}
在 AngularJS controller.js 或 JavaScript controller.js 中,添加一个新方法,搜索待办事项列表,寻找还未加载的图片:
// for each image with no imageUrl, start a new loader
$scope.loadImages = function() {
for (var i=0; i<$scope.todos.length; i++) {
var todo=$scope.todos[i];
if (todo.validImage && todo.imageUrl===PLACEHOLDER_IMAGE) {
loadImage(todo.uri, function(blob_uri, requested_uri) {
$scope.$apply(function(scope) {
for (var k=0; k<scope.todos.length; k++) {
if (scope.todos[k].uri==requested_uri) {
scope.todos[k].imageUrl = blob_uri;
}
}
});
});
}
}
};
var maybeStartImageLoader = function(el, todo) {
var img = el.querySelector('img');
if (todo['extras'] && todo.extras.validImage && todo.extras.imageUrl) {
if (todo.extras.imageUrl===PLACEHOLDER_IMAGE) {
img.src = PLACEHOLDER_IMAGE;
img.style.display = 'inline';
window.loadImage(todo.extras.uri, function(blob_uri, requested_uri) {
todo.extras.imageUrl = blob_uri;
img.src = blob_uri;
});
} else {
img.src = todo.extras.imageUrl;
img.style.display = 'inline';
}
} else {
img.style.display = 'none';
}
};
如果您使用 JavaScript 编写您的应用,您需要在
JavaScript controller.js
中调用 maybeStartImageLoader 函数,根据模型更新待办事项列表:
var updateTodo = function(model) {
var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
if (todoElement) {
var checkbox = todoElement.querySelector('input[type="checkbox"]');
var desc = todoElement.querySelector('span');
checkbox.checked = model.isDone;
desc.innerText = model.text;
desc.className = "done-"+model.isDone;
// load image, if this ToDo has image data
maybeStartImageLoader(todoElement, model);
}
}
然后,在
AngularJS controller.js 或
JavaScript.controller.js
的
drop()
方法中,修改 URI 的处理方式,适当地检测有效的图片。为了简单起见,我们只测试
png 和 jpg 扩展名,您可以在您的代码中检测其他类型。
var uri=e.dataTransfer.getData("text/uri-list");
var todo = {text:uri, done:false, uri: uri};
if (/.png$/.test(uri) || /.jpg$/.test(uri)) {
hasImage = true;
todo.validImage = true;
todo.imageUrl = PLACEHOLDER_IMAGE;
}
newTodos.push(todo);
// [...] inside the $apply method, before save(), call the loadImages method:
$scope.loadImages();
var uri = e.dataTransfer.getData("text/uri-list");
var extras = { uri: uri };
if (/\.png$/.test(uri) || /\.jpg$/.test(uri)) {
hasImage = true;
extras.validImage = true;
extras.imageUrl = PLACEHOLDER_IMAGE;
}
model.addTodo(uri, false, extras);
最后,我们要修改 load 方法,重新设置 Blob URL,因为 Blob URL 不会在会话间保留。将待办事项的 imageUrls 设置为 PLACEHOLDER_IMAGE 强制使 loadImages 方法再次请求它们:
// If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
$scope.load = function(value) {
if (value && value.todolist) {
// ObjectURLs are revoked when the document is removed from memory,
// so we need to reload all images.
for (var i=0; i<value.todolist.length; i++) {
value.todolist[i].imageUrl = PLACEHOLDER_IMAGE;
}
$scope.todos = value.todolist;
$scope.loadImages();
} else {
$scope.todos = [
{text:'learn angular', done:true},
{text:'build an angular app', done:false}];
}
}
/**
* Listen to changes in the model and trigger the appropriate changes in the view
**/
model.addListener(function(model, changeType, param) {
if ( changeType === 'reset' ) {
// let's invalidate all Blob URLs, since their lifetime is tied to the document's lifetime
for (var id in model.todos) {
if (model.todos[id].extras && model.todos[id].extras.validImage) {
model.todos[id].extras.imageUrl = PLACEHOLDER_IMAGE;
}
}
}
if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
redrawUI(model);
} else if ( changeType === 'added' ) {
drawTodo(model.todos[param], list);
} else if ( changeType === 'stateChanged') {
updateTodo(model.todos[param]);
}
storageSave();
updateCounters(model);
});
为了测试,打开应用,单击右键,并选择重新加载应用。进入 Google 图片,搜索并选择一个图片,并将图片拖放到待办事项列表应用。如果没有任何错误的话,您应该看到拖放到待办事项应用的每一个图片 URL 的缩略图。
注意,我们在这一更改中没有处理从文件管理器中拖放的本地图片,我们将它作为一项挑战留给您。
如果您遇到了困难,希望立刻看到修改后的应用,请进入
chrome://extensions,加载未打包的
AngularJS 2_loading_resources 或
JavaScript 2_loading_resources,并从新标签页中运行应用。
以上的 loadImage()
方法并不是这一问题的最佳解决方案,因为它不能正确处理错误,还会将图片缓存在本地文件系统中。我们建立了
apps-resource-loader 库,它的健壮性更好。
<webview>
标签允许您在您的应用中包含一个受控的浏览器。例如,如果您的应用有一部分不兼容
CSP,而且您没有精力马上迁移,您可以使用它。我们没有在这里提到的一个特性是
webview 可以使用异步的
postMessages
和您的应用(或反过来)通信。
与标准网页相比,从网上加载图片之类的资源并不是那么直白,但是与传统的本机平台相比并没有太大的区别,您都需要处理资源下载,只有当它正确下载后才能在用户界面中显示。我们也开发了一个示例库,异步处理从 XHR 调用加载的资源。您可以在您的项目中自由使用它。
在 8 - 发布应用中,我们将告诉您如何在 Chrome 网上应用店中发布您的应用,结束实验。