> [!CAUTION]
> **Important:** Chrome will be removing support for Chrome Apps on all platforms. Chrome browser and the Chrome Web Store will continue to support extensions. [**Read the announcement**](https://blog.chromium.org/2020/08/changes-to-chrome-app-support-timeline.html) and learn more about [**migrating your app**](https://developer.chrome.com/apps/migration).

> [!NOTE]
> **Note:** **Want to start fresh from here?** Find the previous step's code in the [reference code zip](https://github.com/mangini/io13-codelab/archive/master.zip) under ***cheat_code \> solution_for_step4***.

In this step, you will learn:

- How to load resources from outside your app and add them to the DOM through XHR and ObjectURLs.

*Estimated time to complete this step: 20 minutes.*   

To preview what you will complete in this step, [jump down to the bottom of this page ↓](https://developer.chrome.com/docs/apps/app_codelab_images.md.txt#launch).

## How CSP affects the use of external resources

The Chrome Apps platform forces your app to be fully compliant with Content Security Policies (CSP).
You can't directly load DOM resources like images, fonts, and CSS from outside of your Chrome App
package.

If you want to show an external image in your app, you need to request it via [XMLHttpRequest](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest),
transform it into a [Blob](https://developer.mozilla.org/docs/Web/API/Blob), and create an [ObjectURL](https://developer.mozilla.org/docs/Web/API/URL.createObjectURL). This `ObjectURL` can be added to the
DOM because it refers to an in-memory item in the context of the app.

## Show thumbnail images for todo items

Let's change our app to look for image URLs in a todo item. If the URL looks like an image (for
example, ends with .png, .jpg, .svg, or .gif), apply the process mentioned above in order to show an
image thumbnail next to the URL.

### Update permissions

In a Chrome App, you can make XMLHttpRequest calls to any URL as long as you specify its domain in
the manifest. Since you won't know beforehand what image URL the user will type, ask permission to
make requests to `"<all_urls>"`.

In ***manifest.json*** , request the "" permission:

```json
"permissions": ["storage", "alarms", "notifications",
                "webview", "<all_urls>"],
```

### Create and clear ObjectURLs

In ***controller.js*** , add a `_createObjectURL()` method to create ObjectURLs from a Blob:

```javascript
Controller.prototype._createObjectURL = function(blob) {
  var objURL = URL.createObjectURL(blob);
  this.objectURLs = this.objectURLs || [];
  this.objectURLs.push(objURL);
  return objURL;
};
```

ObjectURLs hold memory, so when you no longer need the ObjectURL, you should revoke them. Add this
`_clearObjectURL()` method to *controller.js* to handle that:

```javascript
Controller.prototype._clearObjectURL = function() {
  if (this.objectURLs) {
    this.objectURLs.forEach(function(objURL) {
      URL.revokeObjectURL(objURL);
    });
    this.objectURLs = null;
  }
};
```

### Make a XHR request

Add a `_requestRemoteImageAndAppend()` method to execute a XMLHttpRequest on a given image URL:

```javascript
Controller.prototype._requestRemoteImageAndAppend = function(imageUrl, element) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', imageUrl);
  xhr.responseType = 'blob';
  xhr.onload = function() {
    var img = document.createElement('img');
    img.setAttribute('data-src', imageUrl);
    img.className = 'icon';
    var objURL = this._createObjectURL(xhr.response);
    img.setAttribute('src', objURL);
    element.appendChild(img);
  }.bind(this);
  xhr.send();
};
```

On XHR load, this method creates an `ObjectURL` from the XHR's response, and adds an `<img>` element
with this `ObjectURL` to the DOM.

### Parse for image URLs in todo items

Now add a `_parseForImageURLs()` method that finds all links not yet processed and checks them for
images. For each URL that looks like an image, execute `_requestRemoteImageAndAppend()`:

```javascript
Controller.prototype._parseForImageURLs = function () {
  // remove old blobs to avoid memory leak:
  this._clearObjectURL();
  var links = this.$todoList.querySelectorAll('a[data-src]:not(.thumbnail)');
  var re = /\.(png|jpg|jpeg|svg|gif)$/;
  for (var i = 0; i<links.length; i++) {
    var url = links[i].getAttribute('data-src');
    if (re.test(url)) {
      links[i].classList.add('thumbnail');
      this._requestRemoteImageAndAppend(url, links[i]);
    }
  }
};
```

### Render thumbnails in the todo list

Now call `_parseForImageURLs()` from `showAll()`, `showActive()`, and `showCompleted()`:

    /**
     * An event to fire on load. Will get all items and display them in the
     * todo-list
     */
    Controller.prototype.showAll = function () {
      this.model.read(function (data) {
        this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
        this._parseForImageURLs();
      }.bind(this));
    };

    /**
     * Renders all active tasks
     */
    Controller.prototype.showActive = function () {
      this.model.read({ completed: 0 }, function (data) {
        this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
        this._parseForImageURLs();
      }.bind(this));
    };

    /**
     * Renders all completed tasks
     */
    Controller.prototype.showCompleted = function () {
      this.model.read({ completed: 1 }, function (data) {
        this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
        this._parseForImageURLs();
      }.bind(this));
    };

Do the same for `editItem()`:

    Controller.prototype.editItem = function (id, label) {
      ...
      var onSaveHandler = function () {
        ...
        if (value.length && !discarding) {
          ...
          label.innerHTML = this._parseForURLs(value);
          this._parseForImageURLs();
        } else if (value.length === 0) {
      ...
    }

### Constrain the displayed image dimensions

Finally, in **_bower*components/todomvc-common/base.css***, add a CSS rule to limit the size of the
image:

```css
.thumbnail img[data-src] {
  max-width: 100px;
  max-height: 28px;
}
```

## Launch your finished Todo app

You are done Step 5! Reload your app and add a todo item with a URL to an image hosted online. Some
URLs you could use: **http://goo.gl/nqHMF#.jpg** or **http://goo.gl/HPBGR#.png**.

> [!NOTE]
> **Note:** **Tip** : For real-world situations, when you need to control offline cache and dozens of simultaneous resource downloads, we have created [a helper library](https://github.com/GoogleChrome/apps-resource-loader#readme) to handle some common use cases.

## For more information

For more detailed information about some of the APIs introduced in this step, refer to:

- [Content Security Policy](https://developer.chrome.com/apps/contentSecurityPolicy "Read 'Content Security Policy' in the Chrome developer docs") [↑](https://developer.chrome.com/docs/apps/app_codelab_images.md.txt#csp-compliance "This feature mentioned in 'Learn how CSP affects the use of external web resources'")
- [Declare Permissions](https://developer.chrome.com/apps/declare_permissions "Read 'Declare Permissions' in the Chrome developer docs") [↑](https://developer.chrome.com/docs/apps/app_codelab_images.md.txt#update-permissions "This feature mentioned in 'Update permissions'")

Ready to continue onto the next step? Go to [Step 6 - Export todos to the filesystem \>\>](https://developer.chrome.com/docs/app_codelab_filesystem)