Upstream version 11.39.266.0
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / templates / articles / app_codelab8_webresources.html
1 <h1 id="lab_8_web_resources">Access Web Resources</h1>
2
3 <p>Chrome Apps have a strict
4 <a href="contentSecurityPolicy">Content Security Policy</a>
5 which will not let the user execute code or load resources that are hosted remotely.</p>
6
7 <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 inline or load and show images from a remote URL.</p>
8
9 <h2 id="loading_external_web_content_into_an_element">Load external web content</h2>
10
11 <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 potential source of exploits.</p>
12
13 <p>Chrome Apps offer developers the ability
14 to securely render third-party content in the <code>&lt;webview&gt;</code> tag.
15 A <a href="webview_tag">webview</a>
16 is like an iframe that you can control with greater flexibility and added security.
17 It runs in a separate sandboxed process and can&#39;t communicate directly with the application.</p>
18
19 <p>The <code>webview</code> has a very simple API.
20 From your app you can:</p>
21
22 <ul>
23 <li> Change the URL of the <code>webview</code>.</li>
24 <li> Navigate forwards and backward, stop loading and reload.</li>
25 <li> Check if the <code>webview</code> has finished loading and if it is possible, go back and forward in the history stack.</li>
26 </ul>
27
28 <p>We will change our code to render the content of URLs dropped
29 in the drag-and-drop operations in a <code>webview</code> when the user clicks on a link.</p>
30
31 <h3 id="manifest">Update manifest</h3>
32
33 <p>Request a new permission, &quot;webview&quot;, in the manifest.
34 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/1_webview/manifest.json">Angular JS manifest.json</a> and
35 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/1_webview/manifest.json">JavaScript manifest.json</a> are the same:
36 </p>
37
38 <pre data-filename="manifest.json">
39 &quot;permissions&quot;: [&quot;storage&quot;, &quot;webview&quot;]
40 </pre>
41
42 <h3 id="view">Update view</h3>
43
44 <p>Add a <code>&lt;webview&gt;</code> tag and a link to the view:
45 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/1_webview/index.html">AngularJS index.html</a> or
46 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/1_webview/index.html">JavaScript index.html</a>:
47 </p>
48
49 <tabs data-group="source">
50
51   <header tabindex="0" data-value="angular">Angular</header>
52   <header tabindex="0" data-value="js">JavaScript</header>
53
54   <content>
55     <pre data-filename="index.html">
56 &lt;!-- in TODO item: --&gt;
57 &lt;a ng-show=&quot;todo.uri&quot; href=&quot;&quot; ng-click=&quot;showUri(todo.uri)&quot;&gt;(view url)&lt;/a&gt;
58 &lt;!-- at the bottom, below the end of body: --&gt;
59 &lt;webview&gt;&lt;/webview&gt;
60     </pre>
61   </content>
62   <content>
63     <pre data-filename="index.html">
64 &lt;!-- right after the &quot;dropText&quot; div: --&gt;
65 &lt;webview&gt;&lt;/webview&gt;
66 &lt;!-- in the TODO template, right before the end of the li: --&gt;
67 &lt;a style=&quot;display: none;&quot; href=&quot;&quot;&gt;(view url)&lt;/a&gt;
68     </pre>
69   </content>
70
71 </tabs>
72
73 <h3 id="css">Update stylesheet</h3>
74
75 <p>Set an appropriate width and height to the <code>&lt;webview&gt;</code> tag in
76 the style sheet.
77 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/1_webview/todo.css">AngularJS todo.css</a> and
78 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/1_webview/todo.css">JavaScript todo.css</a> are the same:
79 </p>
80
81 <pre data-filename="todo.css">
82 webview {
83   width: 100%;
84   height: 200px;
85 }
86 </pre>
87
88 <h3 id="controller">Update controller</h3>
89
90 <p>We now only need to add a method,
91 <code>showUri</code>, to the
92 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/1_webview/controller.js">AngularJS controller.js</a>
93 or an event handler, <code>showUrl</code>, to the
94 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/1_webview/controller.js">JavaScript controller.js</a>.
95 </p>
96
97 <tabs data-group="source">
98
99   <header tabindex="0" data-value="angular">Angular</header>
100   <header tabindex="0" data-value="js">JavaScript</header>
101
102   <content>
103     <pre data-filename="controller.js">
104 $scope.showUri = function(uri) {
105   var webview=document.querySelector(&quot;webview&quot;);
106   webview.src=uri;
107 };
108     </pre>
109   </content>
110   <content>
111     <pre data-filename="controller.js">
112 if(/^http:\/\/|https:\/\//.test(todoObj.text)) {
113   var showUrl = el.querySelector('a');
114   showUrl.addEventListener('click', function(e) {
115     e.preventDefault();
116     var webview=document.querySelector("webview");
117     webview.src=todoObj.text;
118   });
119   showUrl.style.display = 'inline';
120 }
121     </pre>
122   </content>
123
124 </tabs>
125
126 <h3 id="results">Check the results</h3>
127
128 <p>To test, open the app, right-click, and select Reload App.
129 You should be able to click on the &quot;view url&quot; link
130 on any dropped URL Todo item,
131 and the corresponding web page will show in the <code>webview</code>.
132 If it&#39;s not showing,
133 inspect the page and check if you set the <code>webview</code> size appropriately.</p>
134
135 <p>If you get stuck and want to see the app in action,
136 go to <code>chrome://extensions</code>,
137 load the unpacked
138 <a href="https://github.com/GoogleChrome/chrome-app-codelab/tree/master/lab8_webresources/angularjs/1_webview">AngularJS 1_webview</a> or
139 <a href="https://github.com/GoogleChrome/chrome-app-codelab/tree/master/lab8_webresources/javascript/1_webview">JavaScript 1_webview</a>,
140 and launch the app from a new tab.</p>
141
142 <h2 id="loading_external_images">Load external images</h2>
143
144 <p>If you try to add an <code>&lt;img&gt;</code> tag to your <code>index.html</code>, and point its <code>src</code> attribute to any site on the web, the following exception is thrown in the console and the image isn&#39;t loaded:</p>
145
146 <p class="note"><b></b> Refused to load the image &#39;http://angularjs.org/img/AngularJS-large.png&#39; because it violates the following Content Security Policy directive: &quot;img-src &#39;self&#39; data: chrome-extension-resource:&quot;.</p>
147
148 <p>Chrome Apps cannot load any external resource directly in the DOM, because of the <a href="contentSecurityPolicy">CSP restrictions</a>.</p>
149
150 <p>To avoid this restriction, you can use XHR requests, grab the blob corresponding to the remote file and use it appropriately.
151 For example, <code>&lt;img&gt;</code> tags can use a blob URL.
152 Let&#39;s change our application to show a small icon in the Todo list if the dropped URL represents an image.</p>
153
154 <h3 id="manifest2">Update manifest</h3>
155
156 <p>Before you start firing XHR requests, you must request permissions.
157 Since we want to allow users to drag and drop images from any server,
158 we need to request permission to XHR to any URL.
159 Change
160 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/2_loading_resources/manifest.json">AngularJS manifest.json</a> or
161 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/2_loading_resources/manifest.json">JavaScript manifest.json</a>:
162 </p>
163
164 <pre data-filename="manifest.json">
165 &quot;permissions&quot;: [&quot;storage&quot;, &quot;webview&quot;, &quot;&lt;all_urls&gt;&quot;]
166 </pre>
167
168 <h3 id="image">Add image</h3>
169
170 <p>Add to your project a placeholder image
171 <img src="https://github.com/GoogleChrome/chrome-app-codelab/raw/master/lab8_webresources/angularjs/2_loading_resources/loading.gif" alt="loading.gif">
172 that will be shown while we are loading the proper image.</p>
173
174 <p>Then add the <code>&lt;img&gt;</code> tag to the Todo item on the view:
175 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/2_loading_resources/index.html">AngularJS index.html</a> or
176 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/2_loading_resources/index.html">JavaScript index_html</a>:
177 </p>
178
179 <tabs data-group="source">
180
181   <header tabindex="0" data-value="angular">Angular</header>
182   <header tabindex="0" data-value="js">JavaScript</header>
183
184   <content>
185     <pre data-filename="loader.js">
186 &lt;img style=&quot;max-height: 48px; max-width: 120px;&quot; ng-show=&quot;todo.validImage&quot;
187 ng-src=&quot;&#123;&#123;todo.imageUrl&#125;&#125;&quot;&gt;&lt;/img&gt;
188     </pre>
189   </content>
190   <content>
191     <pre data-filename="loader.js">
192 &lt;img style=&quot;max-height: 48px; max-width: 120px;&quot;&gt;&lt;/img&gt;
193     </pre>
194   </content>
195
196 </tabs>
197
198 <p>
199 In the
200 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/2_loading_resources/controller.js">JavaScript controller.js</a>,
201 add a constant for the placeholder image:
202 </p>
203
204 <pre data-filename="controller.js">
205 const PLACEHOLDER_IMAGE = "loading.gif";
206 </pre>
207
208 <p>
209 As you will see soon,
210 this element is only shown when the <code>validImage</code> attribute of the Todo item is true.
211 </p>
212
213 <h3 id="controller2">Update controller</h3>
214
215 <p>Add the method <code>loadImage</code> to a new script file
216 that will start a XHR request and execute a callback with a Blob URL.
217 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/2_loading_resources/loader.js">AngularJS loader.js</a> and
218 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/2_loading_resources/loader.js">JavaScript loader.js</a> are the same:
219 </p>
220
221 <pre data-filename="loader.js">
222 var loadImage = function(uri, callback) {
223   var xhr = new XMLHttpRequest();
224   xhr.responseType = &#39;blob&#39;;
225   xhr.onload = function() {
226     callback(window.URL.createObjectURL(xhr.response), uri);
227   }
228   xhr.open(&#39;GET&#39;, uri, true);
229   xhr.send();
230 }
231 </pre>
232
233 <p>In the
234 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/2_loading_resources/controller.js">AngularJS controller.js</a> or
235 <a href="">JavaScript controller.js</a>,
236 add a new method that will search the Todo list looking for images that are not loaded yet:
237 </p>
238
239 <tabs data-group="source">
240
241   <header tabindex="0" data-value="angular">Angular</header>
242   <header tabindex="0" data-value="js">JavaScript</header>
243
244   <content>
245     <pre data-filename="controller.js">
246 // for each image with no imageUrl, start a new loader
247 $scope.loadImages = function() {
248   for (var i=0; i&lt;$scope.todos.length; i++) {
249     var todo=$scope.todos[i];
250     if (todo.validImage &amp;&amp; todo.imageUrl===PLACEHOLDER_IMAGE) {
251       loadImage(todo.uri, function(blob_uri, requested_uri) {
252         $scope.$apply(function(scope) {
253           for (var k=0; k&lt;scope.todos.length; k++) {
254             if (scope.todos[k].uri==requested_uri) {
255               scope.todos[k].imageUrl = blob_uri;
256             }
257           }
258         });
259       });
260     }
261   }
262 };
263     </pre>
264   </content>
265   <content>
266     <pre data-filename="controller.js">
267  var maybeStartImageLoader = function(el, todo) {
268     var img = el.querySelector('img');
269     if (todo['extras'] && todo.extras.validImage && todo.extras.imageUrl) {
270       if (todo.extras.imageUrl===PLACEHOLDER_IMAGE) {
271         img.src = PLACEHOLDER_IMAGE;
272         img.style.display = 'inline';
273         window.loadImage(todo.extras.uri, function(blob_uri, requested_uri) {
274           todo.extras.imageUrl = blob_uri;
275           img.src = blob_uri;
276         });
277       } else {
278         img.src = todo.extras.imageUrl;
279         img.style.display = 'inline';
280       }
281     } else {
282       img.style.display = 'none'; 
283     }
284   };
285     </pre>
286   </content>
287
288 </tabs>
289
290 <p>
291 If writing your app in JavaScript,
292 you will need to call the <code>maybeStartImageLoader</code> function
293 in the
294 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/2_loading_resources/controller.js">JavaScript controller.js</a>
295 to update the Todo list from the model:
296 </p>
297
298 <pre data-filename="controller.js">
299 var updateTodo = function(model) {
300   var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
301   if (todoElement) {
302     var checkbox = todoElement.querySelector('input[type="checkbox"]');
303     var desc = todoElement.querySelector('span');
304     checkbox.checked = model.isDone;
305     desc.innerText = model.text;
306     desc.className = "done-"+model.isDone;
307
308     // load image, if this ToDo has image data
309     maybeStartImageLoader(todoElement, model);
310   }
311 }
312 </pre>
313
314 <p>Then in the 
315 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/angularjs/2_loading_resources/controller.js">AngularJS controller.js</a> or
316 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab8_webresources/javascript/2_loading_resources/controller.js">JavaScript.controller.js</a>,
317 <code>drop()</code> method,
318 change the handling of URIs to appropriately detect a valid image.
319 For simplicity sake, we only tested for png and jpg extensions.
320 Feel free to have a better coverage in your code.
321 </p>
322
323 <tabs data-group="source">
324
325   <header tabindex="0" data-value="angular">Angular</header>
326   <header tabindex="0" data-value="js">JavaScript</header>
327
328   <content>
329     <pre data-filename="controller.js">
330 var uri=e.dataTransfer.getData(&quot;text/uri-list&quot;);
331 var todo = {text:uri, done:false, uri: uri};
332 if (/.png$/.test(uri) || /.jpg$/.test(uri)) {
333   hasImage = true;
334   todo.validImage = true;
335   todo.imageUrl = PLACEHOLDER_IMAGE;
336 }
337 newTodos.push(todo);
338
339 // [...] inside the $apply method, before save(), call the loadImages method:
340 $scope.loadImages();
341     </pre>
342   </content>
343   <content>
344     <pre data-filename="controller.js">
345 var uri = e.dataTransfer.getData("text/uri-list");
346 var extras = { uri: uri };
347 if (/\.png$/.test(uri) || /\.jpg$/.test(uri)) {
348   hasImage = true;
349   extras.validImage = true;
350   extras.imageUrl = PLACEHOLDER_IMAGE;
351 }
352 model.addTodo(uri, false, extras);
353     </pre>
354   </content>
355
356 </tabs>
357
358 <p>And, finally, we will change the load method to reset the Blob URLs,
359 since Blob URLs don&#39;t span through sessions.
360 Setting Todo&#39;s imageUrls to the PLACEHOLDER_IMAGE
361 will force the loadImages method to request them again:
362 </p>
363
364 <tabs data-group="source">
365
366   <header tabindex="0" data-value="angular">Angular</header>
367   <header tabindex="0" data-value="js">JavaScript</header>
368
369   <content>
370     <pre data-filename="controller.js">
371 // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
372 $scope.load = function(value) {
373   if (value &amp;&amp; value.todolist) {
374     // ObjectURLs are revoked when the document is removed from memory,
375     // so we need to reload all images.
376     for (var i=0; i&lt;value.todolist.length; i++) {
377       value.todolist[i].imageUrl = PLACEHOLDER_IMAGE;
378     }
379     $scope.todos = value.todolist;
380     $scope.loadImages();
381   } else {
382     $scope.todos = [
383       {text:&#39;learn angular&#39;, done:true},
384       {text:&#39;build an angular app&#39;, done:false}];
385   }
386 }
387     </pre>
388   </content>
389   <content>
390     <pre data-filename="controller.js">
391 /**
392  * Listen to changes in the model and trigger the appropriate changes in the view
393  **/
394 model.addListener(function(model, changeType, param) {
395   if ( changeType === 'reset' ) {
396     // let's invalidate all Blob URLs, since their lifetime is tied to the document's lifetime
397     for (var id in model.todos) {
398       if (model.todos[id].extras && model.todos[id].extras.validImage) {
399         model.todos[id].extras.imageUrl = PLACEHOLDER_IMAGE;
400       }
401     }
402   }
403
404   if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
405     redrawUI(model);
406   } else if ( changeType === 'added' ) {
407     drawTodo(model.todos[param], list);
408   } else if ( changeType === 'stateChanged') {
409     updateTodo(model.todos[param]);
410   }
411   storageSave();
412   updateCounters(model);
413 });
414     </pre>
415   </content>
416
417 </tabs>
418
419 <h3 id="results2">Check the results</h3>
420
421 <p>To test, open the app, right-click, and select Reload App.
422 Go to
423 <a href="https://www.google.com/imghp?hl=en&amp;tab=wi&amp;authuser=0">Google images</a>,
424 search for and select an image,
425 then drag and drop the image into the Todo list app.
426 Assuming no mistakes were made,
427 you should now have a thumbnail of every image URL dropped into the Todo list app.</p>
428
429 <p>
430 Notice we are not handling local images dropped from the file manager in this change.
431 We leave this as a challenge for you.
432 </p>
433
434 <p>If you get stuck and want to see the app in action,
435 go to <code>chrome://extensions</code>, load the unpacked
436 <a href="https://github.com/GoogleChrome/chrome-app-codelab/tree/master/lab8_webresources/angularjs/2_loading_resources">AngularJS 2_loading_resources</a> or,
437 <a href="https://github.com/GoogleChrome/chrome-app-codelab/tree/master/lab8_webresources/javascript/2_loading_resources">JavaScript 2_loading_resources</a>
438 and launch the app from a new tab.</p>
439
440 <p>The <code>loadImage()</code> method above is not the best solution for this problem, because it doesn&#39;t handle errors correctly and it could cache images in a local filesystem.
441 We've created the
442 <a href="https://github.com/GoogleChrome/apps-resource-loader">apps-resource-loader library</a>
443 that's much more robust.</p>
444
445 <h2 id="takeaways_">Takeaways</h2>
446
447 <ul>
448 <li><p>The <code>&lt;webview&gt;</code> tag allows you to have a controlled browser inside your app.
449 You can use it if you have part of your application that is not CSP compatible and you don&#39;t have resources to migrate it immediately, for example.
450 One feature we didn&#39;t mention here is that webviews can communicate with your app and vice-versa using asynchronous <a href="app_external#postMessage">postMessages</a>.</p></li>
451 <li><p>Loading resources like images from the web is not straightforward compared to a standard web page.
452 But it&#39;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/GoogleChrome/apps-resource-loader">a sample library</a> to asynchronously handle resource loading from XHR calls. Feel free to use it in your projects.</p></li>
453 </ul>
454
455 <h2 id="you_should_also_read">You should also read</h2>
456
457 <ul>
458 <li><a href="webview_tag">Webview Tag API</a> reference</li>
459 <li><a href="app_external">Embed Content</a> tutorial</li>
460 </ul>
461
462 <h2 id="what_39_s_next_">What's next?</h2>
463
464 <p>In <a href="app_codelab_10_publishing">8 - Publish App</a>,
465 we finish off with instructions on how to publish your app in the Chrome Web Store.</p>