OLD | NEW |
(Empty) | |
| 1 <h1 id="lab_8_web_resources">Lab 8 - Web Resources</h1> |
| 2 |
| 3 <p>Chrome apps have a strict <a href="http://developer.chrome.com/trunk/apps/app
_csp.html">Content Security Policy</a> which will not let the user execute code
or load resources that are hosted remotely.</p> |
| 4 |
| 5 <p>Many applications, however, need to be able to load and display content from
a remote location. A News Reader, for example, needs to display remote content i
nline or load and show images from a remote URL.</p> |
| 6 |
| 7 <h2 id="you_should_also_read">You should also read</h2> |
| 8 |
| 9 <ul> |
| 10 <li><a href="http://developer.chrome.com/apps/app_external.html">Embed Content</
a> in Chrome app docs</li> |
| 11 </ul> |
| 12 |
| 13 <h2 id="loading_external_web_content_into_an_element">Loading external web conte
nt into an element</h2> |
| 14 |
| 15 <p>Sites on the internet are inherently a security risk and rendering arbitrary
web pages directly into your application with elevated privileges would be a pot
ential source of exploits.</p> |
| 16 |
| 17 <p>Chrome apps offer developers the ability to securely render third-party conte
nt in the <code><webview></code> tag. A WebView is like an iframe that you
can control with greater flexibility and added security. |
| 18 It runs in a separate sandboxed process and can't communicate directly with
the application.</p> |
| 19 |
| 20 <p class="note"><b>Tip:</b> The WebView has a very simple API. From your app y
ou can:</p> |
| 21 |
| 22 <ul> |
| 23 <li> Change the URL of the WebView.</li> |
| 24 <li> Navigate forwards and backward, stop loading and reload.</li> |
| 25 <li> Check if the WebView has finished loading and if it is possible, go back an
d forward in the history stack.</li> |
| 26 </ul></p> |
| 27 |
| 28 <p>We will change our code to render the content of URLs dropped in the drag-and
-drop operations in a WebView when the user clicks on a link.</p> |
| 29 |
| 30 <ol> |
| 31 <li><p>Request a new permission, "webview", in <a href="https://github
.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/1_w
ebview/manifest.json">manifest.json</a>: |
| 32 <pre> |
| 33 "permissions": ["storage", "webview"] |
| 34 </pre></p></li> |
| 35 <li><p>Add a WebView tag and a link to <a href="https://github.com/GoogleChrome/
chrome-app-codelab/blob/master/lab8_webresources/angularjs/1_webview/index.html"
>index.html</a>: |
| 36 <pre> |
| 37 <!-- in TODO item: --> |
| 38 <a ng-show="todo.uri" href="" ng-click="showUri(todo
.uri)">(view url)</a> |
| 39 <!-- at the bottom, below the end of body: --> |
| 40 <webview></webview> |
| 41 </pre></p></li> |
| 42 <li><p>Set an appropriate width and height to the webview tag in <a href="https:
//github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angul
arjs/1_webview/todo.css">todo.css</a> (it has zero size by default): |
| 43 <pre> |
| 44 webview { |
| 45 width: 100%; |
| 46 height: 200px; |
| 47 } |
| 48 </pre></p></li> |
| 49 <li><p>Thanks to AngularJS, we now only need to add the <code>showUri</code> met
hod to our <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/mast
er/lab8_webresources/angularjs/1_webview/controller.js">controller.js</a> and we
're done: |
| 50 <pre> |
| 51 $scope.showUri = function(uri) { |
| 52 var webview=document.querySelector("webview"); |
| 53 webview.src=uri; |
| 54 }; |
| 55 </pre></p></li> |
| 56 </ol> |
| 57 |
| 58 <p>To test, open the app, right-click, and select Reload App. |
| 59 You should be able to click on the "view url" link on any dropped URL
Todo item, and the corresponding web page will show in the webview. |
| 60 If it's not showing, inspect the page and check if you set the webview size
appropriately.</p> |
| 61 |
| 62 <p class="note"><b>Note:</b> If you get stuck and want to see the app in action
, go to <code>chrome://extensions</code>, |
| 63 load the unpacked <a href="https://github.com/GoogleChrome/chrome-app-codelab/tr
ee/master/lab8_webresources/angularjs/1_webview">1_webview</a>, and launch the a
pp from a new tab.</p> |
| 64 |
| 65 <h2 id="loading_external_images">Loading external images</h2> |
| 66 |
| 67 <p>If you try to add an <code><img></code> tag to your <code>index.html</c
ode>, and point its <code>src</code> attribute to any site on the web, the follo
wing exception is thrown in the console and the image isn't loaded:</p> |
| 68 |
| 69 <p class="note"><b></b> Refused to load the image 'http://angularjs.org/img/
AngularJS-large.png' because it violates the following Content Security Poli
cy directive: "img-src 'self' data: chrome-extension-resource:"
;.</p> |
| 70 |
| 71 <p>Chrome apps cannot load any external resource directly in the DOM, because of
the <a href="http://developer.chrome.com/apps/app_csp.html">CSP restrictions</a
>.</p> |
| 72 |
| 73 <p>To avoid this restriction, you can use XHR requests, grab the blob correspond
ing to the remote file and use it appropriately. |
| 74 For example, <code><img></code> tags can use a blob URL. |
| 75 Let's change our application to show a small icon in the Todo list if the dr
opped URL represents an image:</p> |
| 76 |
| 77 <ol> |
| 78 <li><p>Before you start firing XHR requests, you must request permissions. |
| 79 Since we want to allow users to drag and drop images from any server, we need to
request permission to XHR to any URL. |
| 80 Change <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/l
ab8_webresources/angularjs/2_loading_resources/manifest.json">manifest.json</a>: |
| 81 <pre> |
| 82 "permissions": ["storage", "webview", "<al
l_urls>"] |
| 83 </pre></p></li> |
| 84 <li><p>Add to your project a placeholder image <img src="https://github.com/Goog
leChrome/chrome-app-codelab/raw/master/lab8_webresources/angularjs/2_loading_res
ources/loading.gif" alt="loading.gif"> that will be shown while we are loading t
he proper image.</p></li> |
| 85 <li><p>Add the <code><img></code> tag to the Todo item on the <a href="htt
ps://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/an
gularjs/2_loading_resources/index.html">index.html</a>: |
| 86 <pre> |
| 87 <img style="max-height: 48px; max-width: 120px;" ng-show="todo
.validImage" ng-src="{{todo.imageUrl}}">&l
t;/img> |
| 88 </pre> |
| 89 As you will see soon, this element is only shown when the validImage attribute o
f the Todo item is true.</p></li> |
| 90 <li><p>Add the method loadImage (either in <a href="https://github.com/GoogleChr
ome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/2_loading_resourc
es/controller.js">controller.js</a> or in a <a href="https://github.com/GoogleCh
rome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/2_loading_resour
ces/loader.js">separate script file</a> as we did), that will start a XHR reques
t and execute a callback with a Blob URL: |
| 91 <pre> |
| 92 var loadImage = function(uri, callback) { |
| 93 var xhr = new XMLHttpRequest(); |
| 94 xhr.responseType = 'blob'; |
| 95 xhr.onload = function() { |
| 96 callback(window.webkitURL.createObjectURL(xhr.response), uri); |
| 97 } |
| 98 xhr.open('GET', uri, true); |
| 99 xhr.send(); |
| 100 } |
| 101 </pre></p></li> |
| 102 <li><p>In the <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/m
aster/lab8_webresources/angularjs/2_loading_resources/controller.js">controller.
js</a>, add a new method that will search the scope.todolist looking for images
that are not loaded yet: |
| 103 <pre> |
| 104 // for each image with no imageUrl, start a new loader |
| 105 $scope.loadImages = function() { |
| 106 for (var i=0; i<$scope.todos.length; i++) { |
| 107 var todo=$scope.todos[i]; |
| 108 if (todo.validImage && todo.imageUrl===PLACEHOLDER_IMAGE) { |
| 109 loadImage(todo.uri, function(blob_uri, requested_uri) { |
| 110 $scope.$apply(function(scope) { |
| 111 for (var k=0; k<scope.todos.length; k++) { |
| 112 if (scope.todos[k].uri==requested_uri) { |
| 113 scope.todos[k].imageUrl = blob_uri; |
| 114 } |
| 115 } |
| 116 }); |
| 117 }); |
| 118 } |
| 119 } |
| 120 }; |
| 121 </pre></p></li> |
| 122 <li><p>In the <code>controller.js</code>, <code>drop()</code> method, change the
handling of URIs to appropriately detect a valid image. For simplicity sake, we
only tested for png and jpg extensions. Feel free to have a better coverage in
your code. |
| 123 <pre> |
| 124 var uri=e.dataTransfer.getData("text/uri-list"); |
| 125 var todo = {text:uri, done:false, uri: uri}; |
| 126 if (/.png$/.test(uri) || /.jpg$/.test(uri)) { |
| 127 hasImage = true; |
| 128 todo.validImage = true; |
| 129 todo.imageUrl = PLACEHOLDER_IMAGE; |
| 130 } |
| 131 newTodos.push(todo); |
| 132 |
| 133 // [...] inside the $apply method, before save(), call the loadImages method: |
| 134 $scope.loadImages(); |
| 135 </pre></p></li><li><p>And, finally, we will change the load method to reset the
Blob URLs, since Blob URLs don't span through sessions. |
| 136 Setting Todo's imageUrls to the PLACEHOLDER_IMAGE will force the loadImages
method to request them again: |
| 137 <pre> |
| 138 // If there is saved data in storage, use it. Otherwise, bootstrap with sample t
odos |
| 139 $scope.load = function(value) { |
| 140 if (value && value.todolist) { |
| 141 // ObjectURLs are revoked when the document is removed from memory, |
| 142 // so we need to reload all images. |
| 143 for (var i=0; i<value.todolist.length; i++) { |
| 144 value.todolist[i].imageUrl = PLACEHOLDER_IMAGE; |
| 145 } |
| 146 $scope.todos = value.todolist; |
| 147 $scope.loadImages(); |
| 148 } else { |
| 149 $scope.todos = [ |
| 150 {text:'learn angular', done:true}, |
| 151 {text:'build an angular app', done:false}]; |
| 152 } |
| 153 } |
| 154 </pre></p></li> |
| 155 </ol> |
| 156 |
| 157 <p>To test, open the app, right-click, and select Reload App. |
| 158 Go to <a href="https://www.google.com/imghp?hl=en&tab=wi&authuser=0">Goo
gle images</a>, search for and select an image, |
| 159 then drag and drop the image into the Todo list app. |
| 160 Assuming no mistakes were made, you should now have a thumbnail of every image U
RL dropped into the Todo list app.</p> |
| 161 |
| 162 <p class="note"><b>Note:</b> If you get stuck and want to see the app in action
, go to <code>chrome://extensions</code>, |
| 163 load the unpacked <a href="https://github.com/GoogleChrome/chrome-app-codelab/tr
ee/master/lab8_webresources/angularjs/2_loading_resources">2_loading_resources</
a>, and launch the app from a new tab.</p> |
| 164 |
| 165 <p>The <code>loadImage()</code> method above is not the best solution for this p
roblem, because it doesn't handle errors correctly and it could cache images
in a local filesystem. |
| 166 We are working on a library that will be much more robust and easier to use.</p> |
| 167 |
| 168 <h1 id="takeaways_">Takeaways:</h1> |
| 169 |
| 170 <ul> |
| 171 <li><p>The <code><webview></code> tag allows you to have a controlled brow
ser inside your app. |
| 172 You can use it if you have part of your application that is not CSP compatible a
nd you don't have resources to migrate it immediately, for example. |
| 173 One feature we didn't mention here is that WebViews can communicate with you
r app and vice-versa using asynchronous <a href="https://developer.chrome.com/tr
unk/apps/app_external.html#postMessage">postMessages</a>.</p></li> |
| 174 <li><p>Loading resources like images from the web is not straightforward compare
d to a standard web page. |
| 175 But it's not too different from traditional native platforms, where you need
to handle the resource download and, only when it is correctly downloaded, you
can render it in the UI. We have also developed <a href="https://github.com/Goog
leChrome/apps-resource-loader">a sample library</a> to assyncronously handle res
ource loading from XHR calls. Feel free to use it in your projects.</p></li> |
| 176 </ul> |
| 177 |
| 178 <h1 id="what_39_s_next_">What's next?</h1> |
| 179 |
| 180 <p>In <a href="app_codelab9_multipleviews.html">lab9_multipleviews</a>, |
| 181 you will see how an app can have multiple windows that talk to each other and th
e event page directly.</p> |
OLD | NEW |