diff --git a/core/assets/vendor/picturefill/picturefill.js b/core/assets/vendor/picturefill/picturefill.js
index 4d69569..0c24944 100644
--- a/core/assets/vendor/picturefill/picturefill.js
+++ b/core/assets/vendor/picturefill/picturefill.js
@@ -1,126 +1,659 @@
-/*jshint loopfunc: true, browser: true, curly: true, eqeqeq: true, expr: true, forin: true, latedef: true, newcap: true, noarg: true, trailing: true, undef: true, unused: true */
-/*! Picturefill - Author: Scott Jehl, 2012 | License: MIT/GPLv2 */
-(function (w) {
+/*! Picturefill - v2.2.0 - 2014-10-30
+* http://scottjehl.github.io/picturefill
+* Copyright (c) 2014 https://github.com/scottjehl/picturefill/blob/master/Authors.txt; Licensed MIT */
+/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */
 
-  // Enable strict mode.
+window.matchMedia || (window.matchMedia = function() {
   "use strict";
 
-  // Test if `<picture>` is supported natively, if so, exit.
-  if (!!(w.document.createElement('picture') && w.document.createElement('source') && w.HTMLPictureElement)) {
+  // For browsers that support matchMedium api such as IE 9 and webkit
+  var styleMedia = (window.styleMedia || window.media);
+
+  // For those that don't support matchMedium
+  if (!styleMedia) {
+    var style       = document.createElement('style'),
+      script      = document.getElementsByTagName('script')[0],
+      info        = null;
+
+    style.type  = 'text/css';
+    style.id    = 'matchmediajs-test';
+
+    script.parentNode.insertBefore(style, script);
+
+    // 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers
+    info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle;
+
+    styleMedia = {
+      matchMedium: function(media) {
+        var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }';
+
+        // 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers
+        if (style.styleSheet) {
+          style.styleSheet.cssText = text;
+        } else {
+          style.textContent = text;
+        }
+
+        // Test if media query is true or false
+        return info.width === '1px';
+      }
+    };
+  }
+
+  return function(media) {
+    return {
+      matches: styleMedia.matchMedium(media || 'all'),
+      media: media || 'all'
+    };
+  };
+}());
+/*! Picturefill - Responsive Images that work today.
+*  Author: Scott Jehl, Filament Group, 2012 ( new proposal implemented by Shawn Jansepar )
+*  License: MIT/GPLv2
+*  Spec: http://picture.responsiveimages.org/
+*/
+(function( w, doc, image ) {
+  // Enable strict mode
+  "use strict";
+
+  // If picture is supported, well, that's awesome. Let's get outta here...
+  if ( w.HTMLPictureElement ) {
+    w.picturefill = function() { };
     return;
   }
 
-  w.picturefill = function () {
-    // Copy attributes from the source to the destination.
-    function _copyAttributes(src, tar) {
-      if (src.getAttribute('width') && src.getAttribute('height')) {
-        tar.width = src.getAttribute('width');
-        tar.height = src.getAttribute('height');
+  // HTML shim|v it for old IE (IE9 will still need the HTML video tag workaround)
+  doc.createElement( "picture" );
+
+  // local object for method references and testing exposure
+  var pf = {};
+
+  // namespace
+  pf.ns = "picturefill";
+
+  // srcset support test
+  (function() {
+    pf.srcsetSupported = "srcset" in image;
+    pf.sizesSupported = "sizes" in image;
+  })();
+
+  // just a string trim workaround
+  pf.trim = function( str ) {
+    return str.trim ? str.trim() : str.replace( /^\s+|\s+$/g, "" );
+  };
+
+  // just a string endsWith workaround
+  pf.endsWith = function( str, suffix ) {
+    return str.endsWith ? str.endsWith( suffix ) : str.indexOf( suffix, str.length - suffix.length ) !== -1;
+  };
+
+  /**
+   * Shortcut method for https://w3c.github.io/webappsec/specs/mixedcontent/#restricts-mixed-content ( for easy overriding in tests )
+   */
+  pf.restrictsMixedContent = function() {
+    return w.location.protocol === "https:";
+  };
+  /**
+   * Shortcut method for matchMedia ( for easy overriding in tests )
+   */
+
+  pf.matchesMedia = function( media ) {
+    return w.matchMedia && w.matchMedia( media ).matches;
+  };
+
+  // Shortcut method for `devicePixelRatio` ( for easy overriding in tests )
+  pf.getDpr = function() {
+    return ( w.devicePixelRatio || 1 );
+  };
+
+  /**
+   * Get width in css pixel value from a "length" value
+   * http://dev.w3.org/csswg/css-values-3/#length-value
+   */
+  pf.getWidthFromLength = function( length ) {
+    // If a length is specified and doesn’t contain a percentage, and it is greater than 0 or using `calc`, use it. Else, use the `100vw` default.
+    length = length && length.indexOf( "%" ) > -1 === false && ( parseFloat( length ) > 0 || length.indexOf( "calc(" ) > -1 ) ? length : "100vw";
+
+    /**
+     * If length is specified in  `vw` units, use `%` instead since the div we’re measuring
+     * is injected at the top of the document.
+     *
+     * TODO: maybe we should put this behind a feature test for `vw`?
+     */
+    length = length.replace( "vw", "%" );
+
+    // Create a cached element for getting length value widths
+    if ( !pf.lengthEl ) {
+      pf.lengthEl = doc.createElement( "div" );
+
+      // Positioning styles help prevent padding/margin/width on `html` or `body` from throwing calculations off.
+      pf.lengthEl.style.cssText = "border:0;display:block;font-size:1em;left:0;margin:0;padding:0;position:absolute;visibility:hidden";
+    }
+
+    pf.lengthEl.style.width = length;
+
+    doc.body.appendChild(pf.lengthEl);
+
+    // Add a class, so that everyone knows where this element comes from
+    pf.lengthEl.className = "helper-from-picturefill-js";
+
+    if ( pf.lengthEl.offsetWidth <= 0 ) {
+      // Something has gone wrong. `calc()` is in use and unsupported, most likely. Default to `100vw` (`100%`, for broader support.):
+      pf.lengthEl.style.width = doc.documentElement.offsetWidth + "px";
+    }
+
+    var offsetWidth = pf.lengthEl.offsetWidth;
+
+    doc.body.removeChild( pf.lengthEl );
+
+    return offsetWidth;
+  };
+
+  // container of supported mime types that one might need to qualify before using
+  pf.types =  {};
+
+  // Add support for standard mime types
+  pf.types[ "image/jpeg" ] = true;
+  pf.types[ "image/gif" ] = true;
+  pf.types[ "image/png" ] = true;
+
+  // test svg support
+  pf.types[ "image/svg+xml" ] = doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image", "1.1");
+
+  // test webp support, only when the markup calls for it
+  pf.types[ "image/webp" ] = function() {
+    // based on Modernizr's lossless img-webp test
+    // note: asynchronous
+    var type = "image/webp";
+
+    image.onerror = function() {
+      pf.types[ type ] = false;
+      picturefill();
+    };
+    image.onload = function() {
+      pf.types[ type ] = image.width === 1;
+      picturefill();
+    };
+    image.src = "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=";
+  };
+
+  /**
+   * Takes a source element and checks if its type attribute is present and if so, supported
+   * Note: for type tests that require a async logic,
+   * you can define them as a function that'll run only if that type needs to be tested. Just make the test function call picturefill again when it is complete.
+   * see the async webp test above for example
+   */
+  pf.verifyTypeSupport = function( source ) {
+    var type = source.getAttribute( "type" );
+    // if type attribute exists, return test result, otherwise return true
+    if ( type === null || type === "" ) {
+      return true;
+    } else {
+      // if the type test is a function, run it and return "pending" status. The function will rerun picturefill on pending elements once finished.
+      if ( typeof( pf.types[ type ] ) === "function" ) {
+        pf.types[ type ]();
+        return "pending";
+      } else {
+        return pf.types[ type ];
       }
     }
+  };
 
-    // Get all picture tags.
-    var ps = w.document.getElementsByTagName('picture');
+  // Parses an individual `size` and returns the length, and optional media query
+  pf.parseSize = function( sourceSizeStr ) {
+    var match = /(\([^)]+\))?\s*(.+)/g.exec( sourceSizeStr );
+    return {
+      media: match && match[1],
+      length: match && match[2]
+    };
+  };
 
-    // Loop the pictures.
-    for (var i = 0, il = ps.length; i < il; i++) {
-      var sources = ps[i].getElementsByTagName('source');
-      var picImg = null;
-      var matches = [];
+  // Takes a string of sizes and returns the width in pixels as a number
+  pf.findWidthFromSourceSize = function( sourceSizeListStr ) {
+    // Split up source size list, ie ( max-width: 30em ) 100%, ( max-width: 50em ) 50%, 33%
+    //                            or (min-width:30em) calc(30% - 15px)
+    var sourceSizeList = pf.trim( sourceSizeListStr ).split( /\s*,\s*/ ),
+      winningLength;
 
-      // If no sources are found, they're likely erased from the DOM.
-      // Try finding them inside comments.
-      if (!sources.length) {
-        var picText = ps[i].innerHTML;
-        var frag = w.document.createElement('div');
-        // For IE9, convert the source elements to divs.
-        var srcs = picText.replace(/(<)source([^>]+>)/gmi, '$1div$2').match(/<div[^>]+>/gmi);
+    for ( var i = 0, len = sourceSizeList.length; i < len; i++ ) {
+      // Match <media-condition>? length, ie ( min-width: 50em ) 100%
+      var sourceSize = sourceSizeList[ i ],
+        // Split "( min-width: 50em ) 100%" into separate strings
+        parsedSize = pf.parseSize( sourceSize ),
+        length = parsedSize.length,
+        media = parsedSize.media;
 
-        frag.innerHTML = srcs.join('');
-        sources = frag.getElementsByTagName('div');
+      if ( !length ) {
+        continue;
+      }
+      if ( !media || pf.matchesMedia( media ) ) {
+        // if there is no media query or it matches, choose this as our winning length
+        // and end algorithm
+        winningLength = length;
+        break;
       }
+    }
+
+    // pass the length to a method that can properly determine length
+    // in pixels based on these formats: http://dev.w3.org/csswg/css-values-3/#length-value
+    return pf.getWidthFromLength( winningLength );
+  };
+
+  pf.parseSrcset = function( srcset ) {
+    /**
+     * A lot of this was pulled from Boris Smus’ parser for the now-defunct WHATWG `srcset`
+     * https://github.com/borismus/srcset-polyfill/blob/master/js/srcset-info.js
+     *
+     * 1. Let input (`srcset`) be the value passed to this algorithm.
+     * 2. Let position be a pointer into input, initially pointing at the start of the string.
+     * 3. Let raw candidates be an initially empty ordered list of URLs with associated
+     *    unparsed descriptors. The order of entries in the list is the order in which entries
+     *    are added to the list.
+     */
+    var candidates = [];
+
+    while ( srcset !== "" ) {
+      srcset = srcset.replace( /^\s+/g, "" );
 
-      // See which sources match.
-      for (var j = 0, jl = sources.length; j < jl; j++) {
-        var media = sources[j].getAttribute('media');
-        // If there's no media specified or the media query matches, add it.
-        if (!media || (w.matchMedia && w.matchMedia(media).matches)) {
-          matches.push(sources[j]);
+      // 5. Collect a sequence of characters that are not space characters, and let that be url.
+      var pos = srcset.search(/\s/g),
+        url, descriptor = null;
+
+      if ( pos !== -1 ) {
+        url = srcset.slice( 0, pos );
+
+        var last = url.slice(-1);
+
+        // 6. If url ends with a U+002C COMMA character (,), remove that character from url
+        // and let descriptors be the empty string. Otherwise, follow these substeps
+        // 6.1. If url is empty, then jump to the step labeled descriptor parser.
+
+        if ( last === "," || url === "" ) {
+          url = url.replace( /,+$/, "" );
+          descriptor = "";
         }
+        srcset = srcset.slice( pos + 1 );
+
+        // 6.2. Collect a sequence of characters that are not U+002C COMMA characters (,), and
+        // let that be descriptors.
+        if ( descriptor === null ) {
+          var descpos = srcset.indexOf( "," );
+          if ( descpos !== -1 ) {
+            descriptor = srcset.slice( 0, descpos );
+            srcset = srcset.slice( descpos + 1 );
+          } else {
+            descriptor = srcset;
+            srcset = "";
+          }
+        }
+      } else {
+        url = srcset;
+        srcset = "";
       }
 
-      if (matches.length) {
-        // Grab the most appropriate (last) match.
-        var match = matches.pop();
-        var srcset = match.getAttribute('srcset');
+      // 7. Add url to raw candidates, associated with descriptors.
+      if ( url || descriptor ) {
+        candidates.push({
+          url: url,
+          descriptor: descriptor
+        });
+      }
+    }
+    return candidates;
+  };
+
+  pf.parseDescriptor = function( descriptor, sizesattr ) {
+    // 11. Descriptor parser: Let candidates be an initially empty source set. The order of entries in the list
+    // is the order in which entries are added to the list.
+    var sizes = sizesattr || "100vw",
+      sizeDescriptor = descriptor && descriptor.replace( /(^\s+|\s+$)/g, "" ),
+      widthInCssPixels = pf.findWidthFromSourceSize( sizes ),
+      resCandidate;
 
-        // Find any existing img element in the picture element.
-        picImg = ps[i].getElementsByTagName('img')[0];
+      if ( sizeDescriptor ) {
+        var splitDescriptor = sizeDescriptor.split(" ");
 
-        // Add a new img element if one doesn't exists.
-        if (!picImg) {
-          picImg = w.document.createElement('img');
-          picImg.alt = ps[i].getAttribute('alt');
-          ps[i].appendChild(picImg);
-        }
+        for (var i = splitDescriptor.length - 1; i >= 0; i--) {
+          var curr = splitDescriptor[ i ],
+            lastchar = curr && curr.slice( curr.length - 1 );
 
-        // Source element uses a srcset.
-        if (srcset) {
-          var screenRes = w.devicePixelRatio || 1;
-          // Split comma-separated `srcset` sources into an array.
-          sources = srcset.split(', ');
-
-          // Loop through each source/resolution in srcset.
-          for (var res = sources.length, r = res - 1; r >= 0; r--) {
-            // Remove any leading whitespace, then split on spaces.
-            var source = sources[ r ].replace(/^\s*/, '').replace(/\s*$/, '').split(' ');
-            // Parse out the resolution for each source in `srcset`.
-            var resMatch = parseFloat(source[1], 10);
-
-            if (screenRes >= resMatch) {
-              if (picImg.getAttribute('src') !== source[0]) {
-                var newImg = document.createElement('img');
-
-                newImg.src = source[0];
-                // When the image is loaded, set a width equal to that of the
-                // original’s intrinsic width divided by the screen resolution.
-                newImg.onload = function () {
-                  // Clone the original image into memory so the width is
-                  // unaffected by page styles.
-                  var w = this.cloneNode(true).width;
-                  if (w > 0) {
-                    this.width = (w / resMatch);
-                  }
-                };
-                // Copy width and height from the source tag to the img element.
-                _copyAttributes(match, newImg);
-                picImg.parentNode.replaceChild(newImg, picImg);
-              }
-              // We’ve matched, so bail out of the loop here.
-              break;
-            }
+          if ( ( lastchar === "h" || lastchar === "w" ) && !pf.sizesSupported ) {
+            resCandidate = parseFloat( ( parseInt( curr, 10 ) / widthInCssPixels ) );
+          } else if ( lastchar === "x" ) {
+            var res = curr && parseFloat( curr, 10 );
+            resCandidate = res && !isNaN( res ) ? res : 1;
           }
-        } else {
-          // No srcset used, so just use the 'src' value.
-          picImg.src = match.getAttribute('src');
-          // Copy width and height from the source tag to the img element.
-          _copyAttributes(match, picImg);
+        }
+      }
+    return resCandidate || 1;
+  };
+
+  /**
+   * Takes a srcset in the form of url/
+   * ex. "images/pic-medium.png 1x, images/pic-medium-2x.png 2x" or
+   *     "images/pic-medium.png 400w, images/pic-medium-2x.png 800w" or
+   *     "images/pic-small.png"
+   * Get an array of image candidates in the form of
+   *      {url: "/foo/bar.png", resolution: 1}
+   * where resolution is http://dev.w3.org/csswg/css-values-3/#resolution-value
+   * If sizes is specified, resolution is calculated
+   */
+  pf.getCandidatesFromSourceSet = function( srcset, sizes ) {
+    var candidates = pf.parseSrcset( srcset ),
+      formattedCandidates = [];
+
+    for ( var i = 0, len = candidates.length; i < len; i++ ) {
+      var candidate = candidates[ i ];
+
+      formattedCandidates.push({
+        url: candidate.url,
+        resolution: pf.parseDescriptor( candidate.descriptor, sizes )
+      });
+    }
+    return formattedCandidates;
+  };
+
+  /**
+   * if it's an img element and it has a srcset property,
+   * we need to remove the attribute so we can manipulate src
+   * (the property's existence infers native srcset support, and a srcset-supporting browser will prioritize srcset's value over our winning picture candidate)
+   * this moves srcset's value to memory for later use and removes the attr
+   */
+  pf.dodgeSrcset = function( img ) {
+    if ( img.srcset ) {
+      img[ pf.ns ].srcset = img.srcset;
+      img.removeAttribute( "srcset" );
+    }
+  };
+
+  // Accept a source or img element and process its srcset and sizes attrs
+  pf.processSourceSet = function( el ) {
+    var srcset = el.getAttribute( "srcset" ),
+      sizes = el.getAttribute( "sizes" ),
+      candidates = [];
+
+    // if it's an img element, use the cached srcset property (defined or not)
+    if ( el.nodeName.toUpperCase() === "IMG" && el[ pf.ns ] && el[ pf.ns ].srcset ) {
+      srcset = el[ pf.ns ].srcset;
+    }
+
+    if ( srcset ) {
+      candidates = pf.getCandidatesFromSourceSet( srcset, sizes );
+    }
+    return candidates;
+  };
+
+  pf.applyBestCandidate = function( candidates, picImg ) {
+    var candidate,
+      length,
+      bestCandidate;
+
+    candidates.sort( pf.ascendingSort );
+
+    length = candidates.length;
+    bestCandidate = candidates[ length - 1 ];
+
+    for ( var i = 0; i < length; i++ ) {
+      candidate = candidates[ i ];
+      if ( candidate.resolution >= pf.getDpr() ) {
+        bestCandidate = candidate;
+        break;
+      }
+    }
+
+    if ( bestCandidate && !pf.endsWith( picImg.src, bestCandidate.url ) ) {
+      if ( pf.restrictsMixedContent() && bestCandidate.url.substr(0, "http:".length).toLowerCase() === "http:" ) {
+        if ( typeof console !== undefined ) {
+          console.warn( "Blocked mixed content image " + bestCandidate.url );
+        }
+      } else {
+        picImg.src = bestCandidate.url;
+        // currentSrc attribute and property to match
+        // http://picture.responsiveimages.org/#the-img-element
+        picImg.currentSrc = picImg.src;
+
+        var style = picImg.style || {},
+          WebkitBackfaceVisibility = "webkitBackfaceVisibility" in style,
+          currentZoom = style.zoom;
+
+        if (WebkitBackfaceVisibility) { // See: https://github.com/scottjehl/picturefill/issues/332
+          style.zoom = ".999";
+
+          WebkitBackfaceVisibility = picImg.offsetWidth;
+
+          style.zoom = currentZoom;
+        }
+      }
+    }
+  };
+
+  pf.ascendingSort = function( a, b ) {
+    return a.resolution - b.resolution;
+  };
+
+  /**
+   * In IE9, <source> elements get removed if they aren't children of
+   * video elements. Thus, we conditionally wrap source elements
+   * using <!--[if IE 9]><video style="display: none;"><![endif]-->
+   * and must account for that here by moving those source elements
+   * back into the picture element.
+   */
+  pf.removeVideoShim = function( picture ) {
+    var videos = picture.getElementsByTagName( "video" );
+    if ( videos.length ) {
+      var video = videos[ 0 ],
+        vsources = video.getElementsByTagName( "source" );
+      while ( vsources.length ) {
+        picture.insertBefore( vsources[ 0 ], video );
+      }
+      // Remove the video element once we're finished removing its children
+      video.parentNode.removeChild( video );
+    }
+  };
+
+  /**
+   * Find all `img` elements, and add them to the candidate list if they have
+   * a `picture` parent, a `sizes` attribute in basic `srcset` supporting browsers,
+   * a `srcset` attribute at all, and they haven’t been evaluated already.
+   */
+  pf.getAllElements = function() {
+    var elems = [],
+      imgs = doc.getElementsByTagName( "img" );
+
+    for ( var h = 0, len = imgs.length; h < len; h++ ) {
+      var currImg = imgs[ h ];
+
+      if ( currImg.parentNode.nodeName.toUpperCase() === "PICTURE" ||
+      ( currImg.getAttribute( "srcset" ) !== null ) || currImg[ pf.ns ] && currImg[ pf.ns ].srcset !== null ) {
+        elems.push( currImg );
+      }
+    }
+    return elems;
+  };
+
+  pf.getMatch = function( img, picture ) {
+    var sources = picture.childNodes,
+      match;
+
+    // Go through each child, and if they have media queries, evaluate them
+    for ( var j = 0, slen = sources.length; j < slen; j++ ) {
+      var source = sources[ j ];
+
+      // ignore non-element nodes
+      if ( source.nodeType !== 1 ) {
+        continue;
+      }
+
+      // Hitting the `img` element that started everything stops the search for `sources`.
+      // If no previous `source` matches, the `img` itself is evaluated later.
+      if ( source === img ) {
+        return match;
+      }
+
+      // ignore non-`source` nodes
+      if ( source.nodeName.toUpperCase() !== "SOURCE" ) {
+        continue;
+      }
+      // if it's a source element that has the `src` property set, throw a warning in the console
+      if ( source.getAttribute( "src" ) !== null && typeof console !== undefined ) {
+        console.warn("The `src` attribute is invalid on `picture` `source` element; instead, use `srcset`.");
+      }
+
+      var media = source.getAttribute( "media" );
+
+      // if source does not have a srcset attribute, skip
+      if ( !source.getAttribute( "srcset" ) ) {
+        continue;
+      }
+
+      // if there's no media specified, OR w.matchMedia is supported
+      if ( ( !media || pf.matchesMedia( media ) ) ) {
+        var typeSupported = pf.verifyTypeSupport( source );
+
+        if ( typeSupported === true ) {
+          match = source;
+          break;
+        } else if ( typeSupported === "pending" ) {
+          return false;
         }
       }
     }
+
+    return match;
   };
 
-  // Run on resize and domready (w.load as a fallback)
-  if (w.addEventListener) {
-    w.addEventListener('resize', w.picturefill, false);
-    w.addEventListener('DOMContentLoaded', function () {
-      w.picturefill();
-      // Run once only.
-      w.removeEventListener('load', w.picturefill, false);
-    }, false);
-    w.addEventListener('load', w.picturefill, false);
+  function picturefill( opt ) {
+    var elements,
+      element,
+      parent,
+      firstMatch,
+      candidates,
+      options = opt || {};
+
+    elements = options.elements || pf.getAllElements();
+
+    // Loop through all elements
+    for ( var i = 0, plen = elements.length; i < plen; i++ ) {
+      element = elements[ i ];
+      parent = element.parentNode;
+      firstMatch = undefined;
+      candidates = undefined;
+
+      // immediately skip non-`img` nodes
+      if ( element.nodeName.toUpperCase() !== "IMG" ) {
+        continue;
+      }
+
+      // expando for caching data on the img
+      if ( !element[ pf.ns ] ) {
+        element[ pf.ns ] = {};
+      }
+
+      // if the element has already been evaluated, skip it unless
+      // `options.reevaluate` is set to true ( this, for example,
+      // is set to true when running `picturefill` on `resize` ).
+      if ( !options.reevaluate && element[ pf.ns ].evaluated ) {
+        continue;
+      }
+
+      // if `img` is in a `picture` element
+      if ( parent.nodeName.toUpperCase() === "PICTURE" ) {
+
+        // IE9 video workaround
+        pf.removeVideoShim( parent );
+
+        // return the first match which might undefined
+        // returns false if there is a pending source
+        // TODO the return type here is brutal, cleanup
+        firstMatch = pf.getMatch( element, parent );
+
+        // if any sources are pending in this picture due to async type test(s)
+        // remove the evaluated attr and skip for now ( the pending test will
+        // rerun picturefill on this element when complete)
+        if ( firstMatch === false ) {
+          continue;
+        }
+      } else {
+        firstMatch = undefined;
+      }
+
+      // Cache and remove `srcset` if present and we’re going to be doing `picture`/`srcset`/`sizes` polyfilling to it.
+      if ( parent.nodeName.toUpperCase() === "PICTURE" ||
+      ( element.srcset && !pf.srcsetSupported ) ||
+      ( !pf.sizesSupported && ( element.srcset && element.srcset.indexOf("w") > -1 ) ) ) {
+        pf.dodgeSrcset( element );
+      }
+
+      if ( firstMatch ) {
+        candidates = pf.processSourceSet( firstMatch );
+        pf.applyBestCandidate( candidates, element );
+      } else {
+        // No sources matched, so we’re down to processing the inner `img` as a source.
+        candidates = pf.processSourceSet( element );
+
+        if ( element.srcset === undefined || element[ pf.ns ].srcset ) {
+          // Either `srcset` is completely unsupported, or we need to polyfill `sizes` functionality.
+          pf.applyBestCandidate( candidates, element );
+        } // Else, resolution-only `srcset` is supported natively.
+      }
+
+      // set evaluated to true to avoid unnecessary reparsing
+      element[ pf.ns ].evaluated = true;
+    }
+  }
+
+  /**
+   * Sets up picture polyfill by polling the document and running
+   * the polyfill every 250ms until the document is ready.
+   * Also attaches picturefill on resize
+   */
+  function runPicturefill() {
+    picturefill();
+    var intervalId = setInterval( function() {
+      // When the document has finished loading, stop checking for new images
+      // https://github.com/ded/domready/blob/master/ready.js#L15
+      picturefill();
+      if ( /^loaded|^i|^c/.test( doc.readyState ) ) {
+        clearInterval( intervalId );
+        return;
+      }
+    }, 250 );
+
+    function checkResize() {
+      var resizeThrottle;
+
+      if ( !w._picturefillWorking ) {
+        w._picturefillWorking = true;
+        w.clearTimeout( resizeThrottle );
+        resizeThrottle = w.setTimeout( function() {
+          picturefill({ reevaluate: true });
+          w._picturefillWorking = false;
+        }, 60 );
+      }
+    }
+
+    if ( w.addEventListener ) {
+      w.addEventListener( "resize", checkResize, false );
+    } else if ( w.attachEvent ) {
+      w.attachEvent( "onresize", checkResize );
+    }
   }
-  else if (w.attachEvent) {
-    w.attachEvent('onload', w.picturefill);
+
+  runPicturefill();
+
+  /* expose methods for testing */
+  picturefill._ = pf;
+
+  /* expose picturefill */
+  if ( typeof module === "object" && typeof module.exports === "object" ) {
+    // CommonJS, just export
+    module.exports = picturefill;
+  } else if ( typeof define === "function" && define.amd ) {
+    // AMD support
+    define( function() { return picturefill; } );
+  } else if ( typeof w === "object" ) {
+    // If no AMD and we are in the browser, attach to window
+    w.picturefill = picturefill;
   }
-})(this);
+
+} )( this, this.document, new this.Image() );
diff --git a/core/core.libraries.yml b/core/core.libraries.yml
index 825ed77..525b6c1 100644
--- a/core/core.libraries.yml
+++ b/core/core.libraries.yml
@@ -834,12 +834,10 @@ normalize:
 
 picturefill:
   remote: https://github.com/scottjehl/picturefill
-  # @todo Contribute upstream and/or replace with upstream version.
-  # @see https://drupal.org/node/1775530
-  version: VERSION
+  version: 2.2.0
   license:
     name: MIT
-    url: https://github.com/scottjehl/picturefill/blob/master/LICENSE
+    url: https://github.com/scottjehl/picturefill/blob/2.2.0/LICENSE
     gpl-compatible: true
   js:
     assets/vendor/picturefill/picturefill.js: { weight: -10 }
diff --git a/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php b/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
index a8a46f9..83986eb 100644
--- a/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
+++ b/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
@@ -33,54 +33,54 @@ protected function setUp() {
   public function testThemeBreakpoints() {
     // Verify the breakpoint group for breakpoint_theme_test was created.
     $expected_breakpoints = array(
-        'breakpoint_theme_test.mobile' => array(
-          'label' => 'mobile',
-          'mediaQuery' => '(min-width: 0px)',
-          'weight' => 0,
-          'multipliers' => array(
-            '1x',
-          ),
-          'provider' => 'breakpoint_theme_test',
-          'id' => 'breakpoint_theme_test.mobile',
-          'group' => 'breakpoint_theme_test',
-          'class' => 'Drupal\\breakpoint\\Breakpoint',
+      'breakpoint_theme_test.tv' => array(
+        'label' => 'tv',
+        'mediaQuery' => 'only screen and (min-width: 1220px)',
+        'weight' => 0,
+        'multipliers' => array(
+          '1x',
         ),
-        'breakpoint_theme_test.narrow' => array(
-          'label' => 'narrow',
-          'mediaQuery' => '(min-width: 560px)',
-          'weight' => 1,
-          'multipliers' => array(
-            '1x',
-          ),
-          'provider' => 'breakpoint_theme_test',
-          'id' => 'breakpoint_theme_test.narrow',
-          'group' => 'breakpoint_theme_test',
-          'class' => 'Drupal\\breakpoint\\Breakpoint',
+        'provider' => 'breakpoint_theme_test',
+        'id' => 'breakpoint_theme_test.tv',
+        'group' => 'breakpoint_theme_test',
+        'class' => 'Drupal\\breakpoint\\Breakpoint',
+      ),
+      'breakpoint_theme_test.wide' => array(
+        'label' => 'wide',
+        'mediaQuery' => '(min-width: 851px)',
+        'weight' => 1,
+        'multipliers' => array(
+          '1x',
         ),
-        'breakpoint_theme_test.wide' => array(
-          'label' => 'wide',
-          'mediaQuery' => '(min-width: 851px)',
-          'weight' => 2,
-          'multipliers' => array(
-            '1x',
-          ),
-          'provider' => 'breakpoint_theme_test',
-          'id' => 'breakpoint_theme_test.wide',
-          'group' => 'breakpoint_theme_test',
-          'class' => 'Drupal\\breakpoint\\Breakpoint',
+        'provider' => 'breakpoint_theme_test',
+        'id' => 'breakpoint_theme_test.wide',
+        'group' => 'breakpoint_theme_test',
+        'class' => 'Drupal\\breakpoint\\Breakpoint',
+      ),
+      'breakpoint_theme_test.narrow' => array(
+        'label' => 'narrow',
+        'mediaQuery' => '(min-width: 560px)',
+        'weight' => 2,
+        'multipliers' => array(
+          '1x',
         ),
-        'breakpoint_theme_test.tv' => array(
-          'label' => 'tv',
-          'mediaQuery' => 'only screen and (min-width: 3456px)',
-          'weight' => 3,
-          'multipliers' => array(
-            '1x',
-          ),
-          'provider' => 'breakpoint_theme_test',
-          'id' => 'breakpoint_theme_test.tv',
-          'group' => 'breakpoint_theme_test',
-          'class' => 'Drupal\\breakpoint\\Breakpoint',
+        'provider' => 'breakpoint_theme_test',
+        'id' => 'breakpoint_theme_test.narrow',
+        'group' => 'breakpoint_theme_test',
+        'class' => 'Drupal\\breakpoint\\Breakpoint',
+      ),
+      'breakpoint_theme_test.mobile' => array(
+        'label' => 'mobile',
+        'mediaQuery' => '(min-width: 0px)',
+        'weight' => 3,
+        'multipliers' => array(
+          '1x',
         ),
+        'provider' => 'breakpoint_theme_test',
+        'id' => 'breakpoint_theme_test.mobile',
+        'group' => 'breakpoint_theme_test',
+        'class' => 'Drupal\\breakpoint\\Breakpoint',
+      ),
     );
 
     $breakpoints = \Drupal::service('breakpoint.manager')->getBreakpointsByGroup('breakpoint_theme_test');
@@ -101,7 +101,7 @@ public function testCustomBreakpointGroups () {
       'breakpoint_theme_test.group2.narrow' => array(
         'label' => 'narrow',
         'mediaQuery' => '(min-width: 560px)',
-        'weight' => 1,
+        'weight' => 2,
         'multipliers' => array(
           '1x',
           '2x',
@@ -114,7 +114,7 @@ public function testCustomBreakpointGroups () {
       'breakpoint_theme_test.group2.wide' => array(
         'label' => 'wide',
         'mediaQuery' => '(min-width: 851px)',
-        'weight' => 2,
+        'weight' => 1,
         'multipliers' => array(
           '1x',
           '2x',
@@ -127,7 +127,7 @@ public function testCustomBreakpointGroups () {
       'breakpoint_module_test.breakpoint_theme_test.group2.tv' => array(
         'label' => 'tv',
         'mediaQuery' => '(min-width: 6000px)',
-        'weight' => 3,
+        'weight' => 0,
         'multipliers' => array(
           '1x',
         ),
@@ -152,7 +152,7 @@ public function testModuleBreakpoints() {
       'breakpoint_module_test.mobile' => array(
         'label' => 'mobile',
         'mediaQuery' => '(min-width: 0px)',
-        'weight' => 0,
+        'weight' => 1,
         'multipliers' => array(
           '1x',
         ),
@@ -164,7 +164,7 @@ public function testModuleBreakpoints() {
       'breakpoint_module_test.standard' => array(
         'label' => 'standard',
         'mediaQuery' => '(min-width: 560px)',
-        'weight' => 1,
+        'weight' => 0,
         'multipliers' => array(
           '1x',
           '2x',
diff --git a/core/modules/breakpoint/tests/modules/breakpoint_module_test/breakpoint_module_test.breakpoints.yml b/core/modules/breakpoint/tests/modules/breakpoint_module_test/breakpoint_module_test.breakpoints.yml
index 0c24709..e1edeb2 100644
--- a/core/modules/breakpoint/tests/modules/breakpoint_module_test/breakpoint_module_test.breakpoints.yml
+++ b/core/modules/breakpoint/tests/modules/breakpoint_module_test/breakpoint_module_test.breakpoints.yml
@@ -1,12 +1,12 @@
 breakpoint_module_test.mobile:
   label: mobile
   mediaQuery: '(min-width: 0px)'
-  weight: 0
+  weight: 1
   # Don't include multipliers. A 1x multiplier this will be enforced by default.
 breakpoint_module_test.standard:
   label: standard
   mediaQuery: '(min-width: 560px)'
-  weight: 1
+  weight: 0
   # Don't include a 1x multiplier this will be enforced by default.
   multipliers:
     - 2x
@@ -15,7 +15,7 @@ breakpoint_module_test.standard:
 breakpoint_module_test.breakpoint_theme_test.group2.tv:
   label: tv
   mediaQuery: '(min-width: 6000px)'
-  weight: 3
+  weight: 0
   multipliers:
     - 1x
   group: breakpoint_theme_test.group2
diff --git a/core/modules/breakpoint/tests/src/Unit/BreakpointTest.php b/core/modules/breakpoint/tests/src/Unit/BreakpointTest.php
index f4b2a0b..fea2d8f 100644
--- a/core/modules/breakpoint/tests/src/Unit/BreakpointTest.php
+++ b/core/modules/breakpoint/tests/src/Unit/BreakpointTest.php
@@ -87,9 +87,9 @@ public function testGetWeight() {
    * @covers ::getMediaQuery
    */
   public function testGetMediaQuery() {
-    $this->pluginDefinition['mediaQuery'] = 'only screen and (min-width: 3456px)';
+    $this->pluginDefinition['mediaQuery'] = 'only screen and (min-width: 1220px)';
     $this->setupBreakpoint();
-    $this->assertEquals('only screen and (min-width: 3456px)', $this->breakpoint->getMediaQuery());
+    $this->assertEquals('only screen and (min-width: 1220px)', $this->breakpoint->getMediaQuery());
   }
 
   /**
diff --git a/core/modules/breakpoint/tests/themes/breakpoint_theme_test/breakpoint_theme_test.breakpoints.yml b/core/modules/breakpoint/tests/themes/breakpoint_theme_test/breakpoint_theme_test.breakpoints.yml
index 6d34ec7..3e565e1 100644
--- a/core/modules/breakpoint/tests/themes/breakpoint_theme_test/breakpoint_theme_test.breakpoints.yml
+++ b/core/modules/breakpoint/tests/themes/breakpoint_theme_test/breakpoint_theme_test.breakpoints.yml
@@ -1,32 +1,32 @@
 breakpoint_theme_test.mobile:
   label: mobile
   mediaQuery: '(min-width: 0px)'
-  weight: 0
+  weight: 3
   multipliers:
     - 1x
 breakpoint_theme_test.narrow:
   label: narrow
   mediaQuery: '(min-width: 560px)'
-  weight: 1
+  weight: 2
   multipliers:
     - 1x
 # Out of order breakpoint to test sorting.
 breakpoint_theme_test.tv:
   label: tv
-  mediaQuery: 'only screen and (min-width: 3456px)'
-  weight: 3
+  mediaQuery: 'only screen and (min-width: 1220px)'
+  weight: 0
   multipliers:
     - 1x
 breakpoint_theme_test.wide:
   label: wide
   mediaQuery: '(min-width: 851px)'
-  weight: 2
+  weight: 1
   multipliers:
     - 1x
 breakpoint_theme_test.group2.narrow:
   label: narrow
   mediaQuery: '(min-width: 560px)'
-  weight: 1
+  weight: 2
   multipliers:
     - 1x
     - 2x
@@ -34,7 +34,7 @@ breakpoint_theme_test.group2.narrow:
 breakpoint_theme_test.group2.wide:
   label: wide
   mediaQuery: '(min-width: 851px)'
-  weight: 2
+  weight: 1
   multipliers:
     - 1x
     - 2x
diff --git a/core/modules/responsive_image/config/schema/responsive_image.schema.yml b/core/modules/responsive_image/config/schema/responsive_image.schema.yml
index f953796..c6a33c6 100644
--- a/core/modules/responsive_image/config/schema/responsive_image.schema.yml
+++ b/core/modules/responsive_image/config/schema/responsive_image.schema.yml
@@ -10,26 +10,50 @@ responsive_image.mappings.*:
     label:
       type: label
       label: 'Label'
-    mappings:
+    mapping_definitions:
       type: sequence
-      label: 'Mappings'
+      label: 'Mapping definitions'
       sequence:
         - type: mapping
-          label: 'Mapping'
+          label: 'Mapping definition'
           mapping:
+            # Image mapping type. Either 'sizes' (using the 'sizes' attribute)
+            # or 'image_style' (using a single image style to map to this
+            # breakpoint).
+            image_mapping_type:
+              type: string
+              label: 'Responsive image mapping type'
+            image_mapping:
+              type: responsive_image.image_mapping_type.[%parent.image_mapping_type]
             breakpoint_id:
               type: string
               label: 'Breakpoint ID'
             multiplier:
               type: string
               label: 'Multiplier'
-            image_style:
-              type: string
-              label: 'Image style'
-    breakpointGroup:
+    breakpoint_group:
       type: string
       label: 'Breakpoint group'
 
+responsive_image.image_mapping_type.image_style:
+  type: string
+  label: 'Image style'
+
+responsive_image.image_mapping_type.sizes:
+  type: mapping
+  mapping:
+    # The value for the sizes attribute as described in the spec:
+    # http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-sizes
+    sizes:
+      type: string
+      label: 'Sizes attribute'
+    sizes_image_styles:
+      type: sequence
+      label: 'Image styles to be used when using the ''sizes'' attribute'
+      sequence:
+        - type: string
+          label: 'Image style'
+
 field.formatter.settings.responsive_image:
   type: mapping
   label: 'Responsive image list format settings'
diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module
index 7a48c0d..c383d4d 100644
--- a/core/modules/responsive_image/responsive_image.module
+++ b/core/modules/responsive_image/responsive_image.module
@@ -9,8 +9,11 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Routing\RouteMatchInterface;
 use \Drupal\Core\Template\Attribute;
+use Drupal\image\Entity\ImageStyle;
 use Drupal\Core\Url;
 use Drupal\responsive_image\Entity\ResponsiveImageMapping;
+use Drupal\Core\Image\ImageInterface;
+use Drupal\Component\Utility\String;
 
 /**
  * The machine name for the empty image breakpoint image style option.
@@ -83,7 +86,6 @@ function responsive_image_theme() {
         'attributes' => array(),
         'mapping_id' => array(),
       ),
-      'function' => 'theme_responsive_image',
     ),
     'responsive_image_formatter' => array(
       'variables' => array(
@@ -94,15 +96,6 @@ function responsive_image_theme() {
       ),
       'function' => 'theme_responsive_image_formatter',
     ),
-    'responsive_image_source' => array(
-      'variables' => array(
-        'src' => NULL,
-        'srcset' => NULL,
-        'dimensions' => NULL,
-        'media' => NULL,
-      ),
-      'function' => 'theme_responsive_image_source',
-    ),
   );
 }
 
@@ -113,8 +106,8 @@ function responsive_image_theme() {
  *   An associative array containing:
  *   - item: An ImageItem object.
  *   - image_style: An optional image style.
- *   - path: An optional array containing the link 'path' and link 'options'.
  *   - mapping_id: The ID of the responsive image mapping.
+ *   - url: An optional \Drupal\Core\Url object.
  *
  * @ingroup themeable
  */
@@ -148,19 +141,17 @@ function theme_responsive_image_formatter($variables) {
   if (Unicode::strlen($item->title) != 0) {
     $responsive_image['#title'] = $item->title;
   }
-  // @todo Add support for route names.
-  if (isset($variables['path']['path'])) {
-    $path = $variables['path']['path'];
-    $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();
-    $options['html'] = TRUE;
-    return \Drupal::l($responsive_image, Url::fromUri($path, $options));
+  if (isset($variables['url'])) {
+    return \Drupal::l($responsive_image, $variables['url']->setOption('html', TRUE));
   }
 
   return drupal_render($responsive_image);
 }
 
 /**
- * Returns HTML for a responsive image.
+ * Prepares variables for a responsive image.
+ *
+ * Default template: responsive-image.html.twig.
  *
  * @param $variables
  *   An associative array containing:
@@ -171,12 +162,13 @@ function theme_responsive_image_formatter($variables) {
  *   - alt: The alternative text for text-based browsers.
  *   - title: The title text is displayed when the image is hovered in some
  *     popular browsers.
+ *   - attributes: Associative array of attributes to be placed in the img tag.
  *   - style_name: The name of the style to be used as a fallback image.
  *   - mapping_id: The ID of the responsive image mapping.
  *
  * @ingroup themeable
  */
-function theme_responsive_image($variables) {
+function template_preprocess_responsive_image(&$variables) {
   // Make sure that width and height are proper values
   // If they exists we'll output them
   // @see http://www.w3.org/community/respimg/2012/06/18/florians-compromise/
@@ -189,114 +181,230 @@ function theme_responsive_image($variables) {
     unset($variables['height']);
   }
 
-  $sources = array();
-
-  // Fallback image, output as source with media query.
-  $sources[] = array(
-    'src' => _responsive_image_image_style_url($variables['style_name'], $variables['uri']),
-    'dimensions' => responsive_image_get_image_dimensions($variables),
+  $image = \Drupal::service('image.factory')->get($variables['uri']);
+  $variables['sources'] = responsive_image_build_source_attributes($image, $variables);
+  // Prepare the fallback image. Use srcset in the fallback image to avoid
+  // unnecessary preloading of images in older browsers. See
+  // http://scottjehl.github.io/picturefill/#using-picture and
+  // http://scottjehl.github.io/picturefill/#gotchas for more information.
+  $variables['img_element'] = array(
+    '#theme' => 'image',
+    '#srcset' => array(
+      array(
+        'uri' => _responsive_image_image_style_url($variables['style_name'], $image->getSource()),
+      ),
+    ),
   );
+  foreach (array('alt', 'title', 'attributes') as $key) {
+    if (isset($variables[$key])) {
+      $variables['img_element']["#$key"] = $variables[$key];
+      unset($variables[$key]);
+    }
+  }
+}
+
+/**
+ * Helper function for template_preprocess_responsive_image().
+ *
+ * Builds an array of attributes for <source> tags to be used in a <picture>
+ * tag. In other words, this function provides the attributes for each <source>
+ * tag in a <picture> tag.
+ *
+ * In a responsive image mapping, each breakpoint has a mapping definition for
+ * each of its multipliers. A mapping definition can be either of two types:
+ * 'sizes' (meaning it will output a <source> tag with the 'sizes' attribute) or
+ * 'image_style' (meaning it will output a <source> tag based on the selected
+ * image style for this breakpoint and multiplier). A responsive image mapping
+ * can contain mapping definitions of mixed types (both 'image_style' and
+ * 'sizes'). For example:
+ * @code
+ * $responsive_img_mapping = ResponsiveImageMapping::create(array(
+ *   'id' => 'mapping_one',
+ *   'label' => 'Mapping One',
+ *   'breakpoint_group' => 'responsive_image_test_module',
+ * ));
+ * $responsive_img_mapping->addMappingDefinition('responsive_image_test_module.mobile', '1x', array(
+ *   'image_mapping_type' => 'image_style',
+ *   'image_mapping' => 'thumbnail',
+ * ))
+ * ->addMappingDefinition('responsive_image_test_module.narrow', '1x', array(
+ *   'image_mapping_type' => 'sizes',
+ *   'image_mapping' => array(
+ *     'sizes' => '(min-width: 700px) 700px, 100vw',
+ *     'sizes_image_styles' => array(
+ *       'large' => 'large',
+ *       'medium' => 'medium',
+ *     ),
+ *   ),
+ * ))
+ * ->save();
+ * @endcode
+ * The above responsive image mapping will result in a <picture> tag like this:
+ * @code
+ * <picture>
+ *   <source media="(min-width: 0px)" srcset="sites/default/files/styles/thumbnail/image.jpeg" />
+ *   <source media="(min-width: 560px)" sizes="(min-width: 700px) 700px, 100vw" srcset="sites/default/files/styles/large/image.jpeg 480w, sites/default/files/styles/medium/image.jpeg 220w" />
+ *   <img srcset="fallback.jpeg" />
+ * </picture>
+ * @endcode
+ *
+ * When all the images in the 'srcset' attribute of a <source> tag have the same
+ * MIME type, the source tag will get a 'mime-type' attribute as well. This way
+ * we can gain some front-end performance because browsers can select which
+ * image (<source> tag) to load based on the MIME types they support (which, for
+ * instance, can be beneficial for browsers supporting WebP).
+ * For example:
+ * A <source> tag can contain multiple images:
+ * @code
+ * <source [...] srcset="image1.jpeg 1x, image2.jpeg 2x, image3.jpeg 3x" />
+ * @endcode
+ * In the above example we can add the 'mime-type' attribute ('image/jpeg')
+ * since all images in the 'srcset' attribute of the <source> tag have the same
+ * MIME type.
+ * If a <source> tag were to look like this:
+ * @code
+ * <source [...] srcset="image1.jpeg 1x, image2.webp 2x, image3.jpeg 3x" />
+ * @endcode
+ * We can't add the 'mime-type' attribute ('image/jpeg' vs 'image/webp'). So in
+ * order to add the 'mime-type' attribute to the <source> tag all images in the
+ * 'srcset' attribute of the <source> tag need to be of the same MIME type. This
+ * way, a <picture> tag could look like this:
+ * @code
+ * <picture>
+ *   <source [...] mime-type="image/webp" srcset="image1.webp 1x, image2.webp 2x, image3.webp 3x"/>
+ *   <source [...] mime-type="image/jpeg" srcset="image1.jpeg 1x, image2.jpeg 2x, image3.jpeg 3x"/>
+ *   <img srcset="fallback.jpeg" />
+ * </picture>
+ * @endcode
+ * This way a browser can decide which <source> tag is preferred based on the
+ * MIME type. In other words, the MIME types of all images in one <source> tag
+ * need to be the same in order to set the 'mime-type' attribute but not all
+ * MIME types within the <picture> tag need to be the same.
+ *
+ * For mappings of the type 'sizes', a width descriptor is added to each source.
+ * For example:
+ * @code
+ * <source media="(min-width: 0px)" srcset="image1.jpeg 100w" />
+ * @endcode
+ * The width descriptor here is "100w". This way the browser knows this image is
+ * 100px wide without having to load it. According to the spec, a multiplier can
+ * not be present if a width descriptor is.
+ * For example:
+ * Valid:
+ * @code
+ * <source media="(min-width:0px)" srcset="img1.jpeg 50w, img2.jpeg=100w" />
+ * @endcode
+ * Invalid:
+ * @code
+ * <source media="(min-width:0px)" srcset="img1.jpeg 50w 1x, img2.jpeg=100w 1x" />
+ * @endcode
+ *
+ * Note: Since the specs do not allow width descriptors and multipliers combined
+ * inside one 'srcset' attribute, we either have to use something like
+ * @code
+ * <source [...] srcset="image1.jpeg 1x, image2.webp 2x, image3.jpeg 3x" />
+ * @endcode
+ * to support multipliers or
+ * @code
+ * <source [...] sizes"(min-width: 40em) 80vw, 100vw" srcset="image1.jpeg 300w, image2.webp 600w, image3.jpeg 1200w" />
+ * @endcode
+ * to support the 'sizes' attribute.
+ *
+ * In theory people could add a mapping for the same breakpoint (but different
+ * multiplier) so the array contains an entry for breakpointA.1x and
+ * breakpointA.2x. If we would output those we will end up with something like
+ * @code
+ * <source [...] sizes="(min-width: 40em) 80vw, 100vw" srcset="a1.jpeg 300w 1x, a2.jpeg 600w 1x, a3.jpeg 1200w 1x, b1.jpeg 250w 2x, b2.jpeg 680w 2x, b3.jpeg 1240w 2x" />
+ * @endcode
+ * which is illegal. So the solution is to merge both arrays into one and
+ * disregard the multiplier. Which, in this case, would output
+ * @code
+ * <source [...] sizes="(min-width: 40em) 80vw, 100vw" srcset="b1.jpeg 250w, a1.jpeg 300w, a2.jpeg 600w, b2.jpeg 680w, a3.jpeg 1200w,  b3.jpeg 1240w" />
+ * @endcode
+ * See http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#image-candidate-string
+ * for further information.
+ *
+ * @param \Drupal\Core\Image\ImageInterface $image
+ *   The image to build the <source> tags for.
+ * @param array $variables
+ *   An array with the following keys:
+ *     - mapping_id: The \Drupal\responsive_image\Entity\ResponsiveImageMapping
+ *       ID.
+ *     - width: The width of the image (if known).
+ *     - height: The height of the image (if known).
+ */
+function responsive_image_build_source_attributes(ImageInterface $image, $variables) {
+  $sources = array();
+  $width = isset($variables['width']) && !empty($variables['width']) ? $variables['width'] : $image->getWidth();
+  $height = isset($variables['height']) && !empty($variables['height']) ? $variables['height'] : $image->getHeight();
+  $extension = pathinfo($image->getSource(), PATHINFO_EXTENSION);
   $responsive_image_mapping = ResponsiveImageMapping::load($variables['mapping_id']);
   // All breakpoints and multipliers.
   $breakpoints = \Drupal::service('breakpoint.manager')->getBreakpointsByGroup($responsive_image_mapping->getBreakpointGroup());
-  foreach ($responsive_image_mapping->getKeyedMappings() as $breakpoint_id => $multipliers) {
+  foreach ($responsive_image_mapping->getKeyedMappingDefinitions() as $breakpoint_id => $multipliers) {
     if (isset($breakpoints[$breakpoint_id])) {
       $breakpoint = $breakpoints[$breakpoint_id];
-      $new_sources = array();
-      foreach ($multipliers as $multiplier => $image_style) {
-        $new_source = $variables;
-        $new_source['style_name'] = $image_style;
-        $new_source['#multiplier'] = $multiplier;
-        $new_sources[] = $new_source;
-      }
+      $sizes = array();
+      $srcset = array();
+      $derivative_mime_types = array();
+      foreach ($multipliers as $multiplier => $mapping_definition) {
+        switch ($mapping_definition['image_mapping_type']) {
+          // Create a <source> tag with the 'sizes' attribute.
+          case 'sizes':
+            // Loop through the image styles for this breakpoint and multiplier.
+            foreach ($mapping_definition['image_mapping']['sizes_image_styles'] as $image_style_name) {
+              // Get the dimensions.
+              $dimensions = responsive_image_get_image_dimensions($image_style_name, array('width' => $width, 'height' => $height));
+              // Get MIME type.
+              $derivative_mime_type = responsive_image_get_mime_type($image_style_name, $extension);
+              $derivative_mime_types[] = $derivative_mime_type;
 
-      // Only one image, use src.
-      if (count($new_sources) == 1) {
-        $sources[] = array(
-          'src' => _responsive_image_image_style_url($new_sources[0]['style_name'], $new_sources[0]['uri']),
-          'dimensions' => responsive_image_get_image_dimensions($new_sources[0]),
-          'media' => $breakpoint->getMediaQuery(),
-        );
-      }
-      else {
-        // Multiple images, use srcset.
-        $srcset = array();
-        foreach ($new_sources as $new_source) {
-          $srcset[] = _responsive_image_image_style_url($new_source['style_name'], $new_source['uri']) . ' ' . $new_source['#multiplier'];
+              // Add the image source with its width descriptor. When a width
+              // descriptor is used in a srcset, we can't add a multiplier to
+              // it. Because of this, the image styles for all multipliers of
+              // this breakpoint should be merged into one srcset and the sizes
+              // attribute should be merged as well.
+              if (is_null($dimensions['width'])) {
+                throw new \LogicException(String::format('Could not determine image width for @file using image style with ID: @image_style_name. This image style can not be used for a responsive image mapping definition using the \'sizes\' attribute.', array('@file' => $image->getSource(), '@image_style_name' => $image_style_name)));
+              }
+              $srcset[floatval($dimensions['width'])] = file_create_url(_responsive_image_image_style_url($image_style_name, $image->getSource())) . ' ' . $dimensions['width'] . 'w';
+              $sizes = array_merge(explode(',', $mapping_definition['image_mapping']['sizes']), $sizes);
+            }
+            break;
+
+          case 'image_style':
+            // Get MIME type.
+            $derivative_mime_type = responsive_image_get_mime_type($mapping_definition['image_mapping'], $extension);
+            $derivative_mime_types[] = $derivative_mime_type;
+            // Add the image source with its multiplier.
+            $srcset[floatval(Unicode::substr($multiplier, 0, -1))] = file_create_url(_responsive_image_image_style_url($mapping_definition['image_mapping'], $image->getSource())) . ' ' . $multiplier;
+            break;
         }
-        $sources[] = array(
-          'srcset' => implode(', ', $srcset),
-          'dimensions' => responsive_image_get_image_dimensions($new_sources[0]),
-          'media' => $breakpoint->getMediaQuery(),
-        );
       }
-    }
-  }
-
-  if (!empty($sources)) {
-    $output = array();
-    $output[] = '<picture>';
-
-    // Add source tags to the output.
-    foreach ($sources as $source) {
-      $responsive_image_source = array(
-        '#theme' => 'responsive_image_source',
-        '#src' => $source['src'],
-        '#dimensions' => $source['dimensions'],
-      );
-      if (isset($source['media'])) {
-        $responsive_image_source['#media'] = $source['media'];
+      ksort($srcset);
+      $source_attributes = new \Drupal\Core\Template\Attribute(array(
+        'srcset' => implode(', ', array_unique($srcset)),
+        'media' => $breakpoint->getMediaQuery(),
+      ));
+      if (count(array_unique($derivative_mime_types)) == 1) {
+        $source_attributes->setAttribute('type', $derivative_mime_types[0]);
       }
-      if (isset($source['srcset'])) {
-        $responsive_image_source['#srcset'] = $source['srcset'];
+      if (!empty($sizes)) {
+        $source_attributes->setAttribute('sizes', implode(',', array_unique($sizes)));
       }
-      $output[] = drupal_render($responsive_image_source);
-    }
-
-    $output[] = '</picture>';
-    return SafeMarkup::set(implode("\n", $output));
-  }
-}
-
-/**
- * Returns HTML for a source tag.
- *
- * @param array $variables
- *   An associative array containing:
- *   - media: The media query to use.
- *   - srcset: The srcset containing the path of the image file or a full
- *     URL and optionally multipliers.
- *   - src: Either the path of the image file (relative to base_path()) or a
- *     full URL.
- *   - dimensions: The width and height of the image (if known).
- *
- * @ingroup themeable
- */
-function theme_responsive_image_source($variables) {
-  $output = array();
-  if (isset($variables['media']) && !empty($variables['media'])) {
-    if (!isset($variables['srcset'])) {
-      $output[] = '<!-- <source media="' . $variables['media'] . '" src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
-      $output[] = '<source media="' . $variables['media'] . '" src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . '/>';
+      $sources[] = $source_attributes;
     }
-    elseif (!isset($variables['src'])) {
-      $output[] = '<!-- <source media="' . $variables['media'] . '" srcset="' . $variables['srcset'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
-      $output[] = '<source media="' . $variables['media'] . '" srcset="' . $variables['srcset'] . '" ' . new Attribute($variables['dimensions']) . ' />';
-    }
-  }
-  else {
-    $output[] = '<!-- <source src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . ' /> -->';
-    $output[] = '<source src="' . $variables['src'] . '" ' . new Attribute($variables['dimensions']) . '/>';
   }
-  return implode("\n", $output);
+  return $sources;
 }
 
 /**
  * Determines the dimensions of an image.
  *
- * @param $variables
+ * @param string $image_style_name
+ *   The name of the style to be used to alter the original image.
+ * @param array $dimensions
  *   An associative array containing:
- *   - style_name: The name of the style to be used to alter the original image.
  *   - width: The width of the source image (if known).
  *   - height: The height of the source image (if known).
  *
@@ -304,21 +412,16 @@ function theme_responsive_image_source($variables) {
  *   Dimensions to be modified - an array with components width and height, in
  *   pixels.
  */
-function responsive_image_get_image_dimensions($variables) {
+function responsive_image_get_image_dimensions($image_style_name, array $dimensions) {
   // Determine the dimensions of the styled image.
-  $dimensions = array(
-    'width' => $variables['width'],
-    'height' => $variables['height'],
-  );
-
-  if ($variables['style_name'] == RESPONSIVE_IMAGE_EMPTY_IMAGE) {
+  if ($image_style_name == RESPONSIVE_IMAGE_EMPTY_IMAGE) {
     $dimensions = array(
       'width' => 1,
       'height' => 1,
     );
   }
   else {
-    $entity = entity_load('image_style', $variables['style_name']);
+    $entity = ImageStyle::load($image_style_name);
     if ($entity instanceof Drupal\image\Entity\ImageStyle) {
       $entity->transformDimensions($dimensions);
     }
@@ -328,17 +431,38 @@ function responsive_image_get_image_dimensions($variables) {
 }
 
 /**
+ * Determines the MIME type of an image.
+ *
+ * @param string $image_style_name
+ *   The image style that will be applied to the image.
+ * @param string $extension
+ *   The original extension of the image (without the leading dot).
+ *
+ * @return string
+ *   The MIME type of the image after the image style is applied.
+ */
+function responsive_image_get_mime_type($image_style_name, $extension) {
+  if ($image_style_name == RESPONSIVE_IMAGE_EMPTY_IMAGE) {
+    return 'image/gif';
+  }
+  // The MIME type guesser needs a full path, not just an extension, but the
+  // file doesn't have to exist.
+  $fake_path = 'responsive_image.' . ImageStyle::load($image_style_name)->getDerivativeExtension($extension);
+  return Drupal::service('file.mime_type.guesser.extension')->guess($fake_path);
+}
+
+/**
  * Wrapper around image_style_url() so we can return an empty image.
  */
 function _responsive_image_image_style_url($style_name, $path) {
   if ($style_name == RESPONSIVE_IMAGE_EMPTY_IMAGE) {
     // The smallest data URI for a 1px square transparent GIF image.
-    return 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
+    // http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
+    return 'data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
   }
-  $entity = entity_load('image_style', $style_name);
+  $entity = ImageStyle::load($style_name);
   if ($entity instanceof Drupal\image\Entity\ImageStyle) {
     return $entity->buildUrl($path);
   }
   return file_create_url($path);
 }
-
diff --git a/core/modules/responsive_image/responsive_image.routing.yml b/core/modules/responsive_image/responsive_image.routing.yml
index 2cf9926..0ea9e75 100644
--- a/core/modules/responsive_image/responsive_image.routing.yml
+++ b/core/modules/responsive_image/responsive_image.routing.yml
@@ -30,7 +30,7 @@ entity.responsive_image_mapping.duplicate_form:
   requirements:
     _permission: 'administer responsive images'
 
-responsive_image.mapping_action_confirm:
+entity.responsive_image_mapping.delete_form:
   path: '/admin/config/media/responsive-image-mapping/{responsive_image_mapping}/delete'
   defaults:
     _entity_form: 'responsive_image_mapping.delete'
diff --git a/core/modules/responsive_image/src/Entity/ResponsiveImageMapping.php b/core/modules/responsive_image/src/Entity/ResponsiveImageMapping.php
index 62215d8..0e9dc14 100644
--- a/core/modules/responsive_image/src/Entity/ResponsiveImageMapping.php
+++ b/core/modules/responsive_image/src/Entity/ResponsiveImageMapping.php
@@ -35,6 +35,7 @@
  *   links = {
  *     "edit-form" = "/admin/config/media/responsive-image-mapping/{responsive_image_mapping}",
  *     "duplicate-form" = "/admin/config/media/responsive-image-mapping/{responsive_image_mapping}/duplicate",
+ *     "delete-form" = "/admin/config/media/responsive-image-mapping/{responsive_image_mapping}/delete",
  *     "collection" = "/admin/config/media/responsive-image-mapping",
  *   }
  * )
@@ -56,28 +57,36 @@ class ResponsiveImageMapping extends ConfigEntityBase implements ResponsiveImage
   protected $label;
 
   /**
-   * The responsive image mappings.
+   * The responsive image mapping definitions.
    *
-   * Each responsive mapping array contains the following keys:
-   * - breakpoint_id
-   * - multiplier
-   * - image_style
+   * Each mapping definition array contains the following keys:
+   *   - image_mapping_type: Either 'image_style' or 'sizes'.
+   *   - image_mapping:
+   *     - If image_mapping_type is 'image_style', the image style ID (a
+   *       string).
+   *     - If image_mapping_type is 'sizes', an array with following keys:
+   *       - sizes: If image_mapping_type is 'sizes', the value for the 'sizes'
+   *         attribute.
+   *       - sizes_image_styles: The image styles to use for the 'srcset'
+   *         attribute.
+   *   - breakpoint_id: The breakpoint ID for this mapping definition.
+   *   - multiplier: The multiplier for this mapping definition.
    *
    * @var array
    */
-  protected $mappings = array();
+  protected $mapping_definitions = array();
 
   /**
    * @var array
    */
-  protected $keyedMappings;
+  protected $keyedMappingDefinitions;
 
   /**
    * The responsive image breakpoint group.
    *
    * @var string
    */
-  protected $breakpointGroup = '';
+  protected $breakpoint_group = '';
 
   /**
    * {@inheritdoc}
@@ -89,68 +98,65 @@ public function __construct(array $values, $entity_type_id = 'responsive_image_m
   /**
    * {@inheritdoc}
    */
-  public function addMapping($breakpoint_id, $multiplier, $image_style) {
-    foreach ($this->mappings as &$mapping) {
+  public function addMappingDefinition($breakpoint_id, $multiplier, array $mapping_definition) {
+    // If there is an existing mapping, overwrite it.
+    foreach ($this->mapping_definitions as &$mapping) {
       if ($mapping['breakpoint_id'] === $breakpoint_id && $mapping['multiplier'] === $multiplier) {
-        $mapping['image_style'] = $image_style;
+        $mapping = array(
+          'breakpoint_id' => $breakpoint_id,
+          'multiplier' => $multiplier,
+        ) + $mapping_definition;
         return $this;
       }
     }
-    $this->mappings[] = array(
+    $this->mapping_definitions[] = array(
       'breakpoint_id' => $breakpoint_id,
       'multiplier' => $multiplier,
-      'image_style' => $image_style,
-    );
-    $this->keyedMappings = NULL;
+    ) + $mapping_definition;
+    $this->keyedMappingDefinitions = NULL;
     return $this;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function hasMappings() {
-    return !empty($this->mappings);
+  public function hasMappingDefinitions() {
+    $mappings = $this->getKeyedMappingDefinitions();
+    return !empty($mappings);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getKeyedMappings() {
-    if (!$this->keyedMappings) {
-      $this->keyedMappings = array();
-      foreach($this->mappings as $mapping) {
-        $this->keyedMappings[$mapping['breakpoint_id']][$mapping['multiplier']] = $mapping['image_style'];
+  public function getKeyedMappingDefinitions() {
+    if (!$this->keyedMappingDefinitions) {
+      $this->keyedMappingDefinitions = array();
+      foreach($this->mapping_definitions as $mapping) {
+        if (!$this->isEmptyMappingDefinition($mapping)) {
+          $this->keyedMappingDefinitions[$mapping['breakpoint_id']][$mapping['multiplier']] = $mapping;
+        }
       }
     }
-    return $this->keyedMappings;
+    return $this->keyedMappingDefinitions;
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getImageStyle($breakpoint_id, $multiplier) {
-    $map = $this->getKeyedMappings();
-    if (isset($map[$breakpoint_id][$multiplier])) {
-      return $map[$breakpoint_id][$multiplier];
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getMappings() {
-    return $this->get('mappings');
+  public function getMappingDefinitions() {
+    return $this->get('mapping_definitions');
   }
 
   /**
    * {@inheritdoc}
    */
   public function setBreakpointGroup($breakpoint_group) {
-    // If the breakpoint group is changed then the mappings are invalid.
-    if ($breakpoint_group !== $this->breakpointGroup) {
-      $this->removeMappings();
+    // If the breakpoint group is changed then the mapping definitions are
+    // invalid.
+    if ($breakpoint_group !== $this->breakpoint_group) {
+      $this->removeMappingDefinitions();
     }
-    $this->set('breakpointGroup', $breakpoint_group);
+    $this->set('breakpoint_group', $breakpoint_group);
     return $this;
   }
 
@@ -158,15 +164,15 @@ public function setBreakpointGroup($breakpoint_group) {
    * {@inheritdoc}
    */
   public function getBreakpointGroup() {
-    return $this->get('breakpointGroup');
+    return $this->get('breakpoint_group');
   }
 
   /**
    * {@inheritdoc}
    */
-  public function removeMappings() {
-    $this->mappings = array();
-    $this->keyedMappings = NULL;
+  public function removeMappingDefinitions() {
+    $this->mapping_definitions = array();
+    $this->keyedMappingDefinitions = NULL;
     return $this;
   }
 
@@ -175,11 +181,45 @@ public function removeMappings() {
    */
   public function calculateDependencies() {
     parent::calculateDependencies();
-    $providers = \Drupal::service('breakpoint.manager')->getGroupProviders($this->breakpointGroup);
+    $providers = \Drupal::service('breakpoint.manager')->getGroupProviders($this->breakpoint_group);
     foreach ($providers as $provider => $type) {
       $this->addDependency($type, $provider);
     }
     return $this->dependencies;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function isEmptyMappingDefinition(array $mapping_definition) {
+    if (!empty($mapping_definition)) {
+      switch ($mapping_definition['image_mapping_type']) {
+        case 'sizes':
+          // The mapping definition must have a sizes attribute defined and one
+          // or more image styles selected.
+          if ($mapping_definition['image_mapping']['sizes'] && $mapping_definition['image_mapping']['sizes_image_styles']) {
+            return FALSE;
+          }
+          break;
+        case 'image_style':
+          // The mapping definition must have an image style selected.
+          if ($mapping_definition['image_mapping']) {
+            return FALSE;
+          }
+          break;
+      }
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMappingDefinition($breakpoint_id, $multiplier) {
+    $map = $this->getKeyedMappingDefinitions();
+    if (isset($map[$breakpoint_id][$multiplier])) {
+      return $map[$breakpoint_id][$multiplier];
+    }
+  }
+
 }
diff --git a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php
index ed6bcf0..d491614 100644
--- a/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php
+++ b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php
@@ -16,6 +16,8 @@
 use Drupal\Core\Url;
 use Drupal\image\Plugin\Field\FieldFormatter\ImageFormatterBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\responsive_image\Entity\ResponsiveImageMapping;
+use Drupal\image\Entity\ImageStyle;
 
 /**
  * Plugin for responsive image formatter.
@@ -96,7 +98,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
     $responsive_image_mappings = $this->responsiveImageMappingStorage->loadMultiple();
     if ($responsive_image_mappings && !empty($responsive_image_mappings)) {
       foreach ($responsive_image_mappings as $machine_name => $responsive_image_mapping) {
-        if ($responsive_image_mapping->hasMappings()) {
+        if ($responsive_image_mapping->hasMappingDefinitions()) {
           $responsive_image_options[$machine_name] = $responsive_image_mapping->label();
         }
       }
@@ -193,26 +195,45 @@ public function viewElements(FieldItemListInterface $items) {
     // Collect cache tags to be added for each item in the field.
     $responsive_image_mapping = $this->responsiveImageMappingStorage->load($this->getSetting('responsive_image_mapping'));
     $image_styles_to_load = array();
-    if ($fallback_image_style) {
-      $image_styles_to_load[] = $fallback_image_style;
-    }
     $cache_tags = [];
     if ($responsive_image_mapping) {
       $cache_tags = Cache::mergeTags($cache_tags, $responsive_image_mapping->getCacheTags());
-      foreach ($responsive_image_mapping->getMappings() as $mapping) {
-        // First mapping found is used as fallback.
-        if (empty($fallback_image_style)) {
-          $fallback_image_style = $mapping['image_style'];
+      foreach ($responsive_image_mapping->getMappingDefinitions() as $mapping) {
+        // Only image styles of non-empty mappings should be loaded.
+        if (!$responsive_image_mapping::isEmptyMappingDefinition($mapping)) {
+
+          if ($mapping['image_mapping_type'] == 'image_style') {
+            // This mapping has one image style, add it.
+            $image_styles_to_load[] = $mapping['image_mapping'];
+          }
+          else {
+            // This mapping has multiple image styles, merge them.
+            $mapping['image_mapping']['sizes_image_styles'] = array_filter($mapping['image_mapping']['sizes_image_styles']);
+            $image_styles_to_load = array_merge($image_styles_to_load, $mapping['image_mapping']['sizes_image_styles']);
+          }
         }
-        $image_styles_to_load[] = $mapping['image_style'];
       }
     }
-    $image_styles = entity_load_multiple('image_style', $image_styles_to_load);
+
+    // If there is a fallback image style, add it to the image styles to load.
+    if ($fallback_image_style) {
+      $image_styles_to_load[] = $fallback_image_style;
+    }
+    else {
+      // The <picture> element uses the first matching breakpoint (see
+      // http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#update-the-source-set
+      // points 2 and 3). Meaning the breakpoints are sorted from large to
+      // small. With mobile-first in mind, the fallback image should be the one
+      // selected for the smallest screen.
+      $fallback_image_style = end($image_styles_to_load);
+    }
+    $image_styles = ImageStyle::loadMultiple($image_styles_to_load);
     foreach ($image_styles as $image_style) {
       $cache_tags = Cache::mergeTags($cache_tags, $image_style->getCacheTags());
     }
 
     foreach ($items as $delta => $item) {
+      // Link the <picture> element to the original file.
       if (isset($link_file)) {
         $url = Url::fromUri(file_create_url($item->entity->getFileUri()));
       }
diff --git a/core/modules/responsive_image/src/ResponsiveImageMappingForm.php b/core/modules/responsive_image/src/ResponsiveImageMappingForm.php
index 1f47865..c668953 100644
--- a/core/modules/responsive_image/src/ResponsiveImageMappingForm.php
+++ b/core/modules/responsive_image/src/ResponsiveImageMappingForm.php
@@ -89,7 +89,7 @@ public function form(array $form, FormStateInterface $form_state) {
     else {
       $description = $this->t('Select a breakpoint group from the installed themes.');
     }
-    $form['breakpointGroup'] = array(
+    $form['breakpoint_group'] = array(
       '#type' => 'select',
       '#title' => $this->t('Breakpoint group'),
       '#default_value' => $responsive_image_mapping->getBreakpointGroup(),
@@ -105,10 +105,21 @@ public function form(array $form, FormStateInterface $form_state) {
       foreach ($breakpoint->getMultipliers() as $multiplier) {
         $label = $multiplier . ' ' . $breakpoint->getLabel() . ' [' . $breakpoint->getMediaQuery() . ']';
         $form['keyed_mappings'][$breakpoint_id][$multiplier] = array(
+          '#type' => 'container',
+        );
+        $mapping_definition = $responsive_image_mapping->getMappingDefinition($breakpoint_id, $multiplier);
+        // @todo The image_mapping_type is only temporarily hardcoded, until
+        // support for the other responsive image mapping type ('sizes') is
+        // added in https://www.drupal.org/node/2334387.
+        $form['keyed_mappings'][$breakpoint_id][$multiplier]['image_mapping_type'] = array(
+          '#type' => 'value',
+          '#value' => 'image_style',
+        );
+        $form['keyed_mappings'][$breakpoint_id][$multiplier]['image_mapping'] = array(
           '#type' => 'select',
           '#title' => $label,
           '#options' => $image_styles,
-          '#default_value' => $responsive_image_mapping->getImageStyle($breakpoint_id, $multiplier),
+          '#default_value' => isset($mapping_definition['image_mapping']) ? $mapping_definition['image_mapping'] : array(),
           '#description' => $this->t('Select an image style for this breakpoint.'),
         );
       }
@@ -126,10 +137,13 @@ public function validate(array $form, FormStateInterface $form_state) {
     // Only validate on edit.
     if ($form_state->hasValue('keyed_mappings')) {
       // Check if another breakpoint group is selected.
-      if ($form_state->getValue('breakpointGroup') != $form_state->getCompleteForm()['breakpointGroup']['#default_value']) {
+      if ($form_state->getValue('breakpoint_group') != $form_state->getCompleteForm()['breakpoint_group']['#default_value']) {
         // Remove the mappings since the breakpoint ID has changed.
         $form_state->unsetValue('keyed_mappings');
       }
+      // @todo Filter 'sizes_image_styles' to a normal array in
+      // https://www.drupal.org/node/2334387. For an example see
+      // \Drupal\Core\Block\BlockBase::validateConfigurationForm().
     }
   }
 
@@ -140,11 +154,11 @@ public function save(array $form, FormStateInterface $form_state) {
     /** @var \Drupal\responsive_image\ResponsiveImageMappingInterface $responsive_image_mapping */
     $responsive_image_mapping = $this->entity;
     // Remove all the existing mappings and replace with submitted values.
-    $responsive_image_mapping->removeMappings();
+    $responsive_image_mapping->removeMappingDefinitions();
     if ($form_state->hasValue('keyed_mappings')) {
       foreach ($form_state->getValue('keyed_mappings') as $breakpoint_id => $multipliers) {
-        foreach ($multipliers as $multiplier => $image_style) {
-          $responsive_image_mapping->addMapping($breakpoint_id, $multiplier, $image_style);
+        foreach ($multipliers as $multiplier => $mapping) {
+          $responsive_image_mapping->addMappingDefinition($breakpoint_id, $multiplier, $mapping);
         }
       }
     }
@@ -155,7 +169,7 @@ public function save(array $form, FormStateInterface $form_state) {
 
     // Redirect to edit form after creating a new mapping or after selecting
     // another breakpoint group.
-    if (!$responsive_image_mapping->hasMappings()) {
+    if (!$responsive_image_mapping->hasMappingDefinitions()) {
       $form_state->setRedirect(
         'entity.responsive_image_mapping.edit_form',
         array('responsive_image_mapping' => $responsive_image_mapping->id())
diff --git a/core/modules/responsive_image/src/ResponsiveImageMappingInterface.php b/core/modules/responsive_image/src/ResponsiveImageMappingInterface.php
index fd9b28b..dd6aa54 100644
--- a/core/modules/responsive_image/src/ResponsiveImageMappingInterface.php
+++ b/core/modules/responsive_image/src/ResponsiveImageMappingInterface.php
@@ -20,16 +20,26 @@
    * return bool
    *   Whether the entity has any responsive image mappings.
    */
-  public function hasMappings();
+  public function hasMappingDefinitions();
 
   /**
    * Returns the mappings of breakpoint ID and multiplier to image style.
    *
    * @return array[]
    *   The responsive image mappings. Keyed by breakpoint ID then multiplier.
-   *   The value is the image style ID.
+   *   The value is the mapping definition array with following keys:
+   *     - image_mapping_type: Either 'image_style' or 'sizes'.
+   *     - image_mapping:
+   *       - If image_mapping_type is 'image_style', the image style ID.
+   *       - If image_mapping_type is 'sizes', an array with following keys:
+   *         - sizes: If image_mapping_type is 'sizes', the value for the
+   *           'sizes' attribute.
+   *         - sizes_image_styles: The image styles to use for the 'srcset'
+   *           attribute.
+   *     - breakpoint_id: The breakpoint ID for this mapping.
+   *     - multiplier: The multiplier for this mapping.
    */
-  public function getKeyedMappings();
+  public function getKeyedMappingDefinitions();
 
   /**
    * Returns the mappings for the responsive image mapping.
@@ -39,9 +49,10 @@ public function getKeyedMappings();
    *   contains the following keys:
    *   - breakpoint_id
    *   - multiplier
-   *   - image_style
+   *   - image_mapping_type
+   *   - image_mapping
    */
-  public function getMappings();
+  public function getMappingDefinitions();
 
   /**
    * Sets the breakpoint group for the responsive_image mapping.
@@ -62,17 +73,39 @@ public function setBreakpointGroup($breakpoint_group);
   public function getBreakpointGroup();
 
   /**
-   * Gets the image style ID for a breakpoint ID and multiplier.
+   * Gets the mapping definition for a breakpoint ID and multiplier.
    *
    * @param string $breakpoint_id
    *   The breakpoint ID.
    * @param string $multiplier
    *   The multiplier.
    *
-   * @return string|null
-   *   The image style ID. Null if the mapping does not exist.
+   * @return array|null
+   *   The mapping definition. NULL if the mapping does not exist.
+   *   The mapping definition has following keys:
+   *     - image_mapping_type: Either 'image_style' or 'sizes'.
+   *     - image_mapping:
+   *       - If image_mapping_type is 'image_style', the image style ID.
+   *       - If image_mapping_type is 'sizes', an array with following keys:
+   *         - sizes: If image_mapping_type is 'sizes', the value for the
+   *           'sizes' attribute.
+   *         - sizes_image_styles: The image styles to use for the 'srcset'
+   *           attribute.
+   *     - breakpoint_id: The breakpoint ID for this mapping definition.
+   *     - multiplier: The multiplier for this mapping definition.
    */
-  public function getImageStyle($breakpoint_id, $multiplier);
+  public function getMappingDefinition($breakpoint_id, $multiplier);
+
+  /**
+   * Checks if there is at least one mapping definition defined.
+   *
+   * @param array $mapping_definition
+   *   The mapping definition.
+   *
+   * @return bool
+   *   Whether the mapping definition is empty.
+   */
+  public static function isEmptyMappingDefinition(array $mapping_definition);
 
   /**
    * Adds a mapping to the responsive image configuration entity.
@@ -81,18 +114,18 @@ public function getImageStyle($breakpoint_id, $multiplier);
    *   The breakpoint ID.
    * @param string $multiplier
    *   The multiplier.
-   * @param string $image_style
-   *   The image style ID.
+   * @param array $mapping_definition
+   *   The mapping definition.
    *
    * @return $this
    */
-  public function addMapping($breakpoint_id, $multiplier, $image_style);
+  public function addMappingDefinition($breakpoint_id, $multiplier, array $mapping_definition);
 
   /**
    * Removes all mappings from the responsive image configuration entity.
    *
    * @return $this
    */
-  public function removeMappings();
+  public function removeMappingDefinitions();
 
 }
diff --git a/core/modules/responsive_image/src/Tests/ResponsiveImageAdminUITest.php b/core/modules/responsive_image/src/Tests/ResponsiveImageAdminUITest.php
index 5935ee0..38cee5d 100644
--- a/core/modules/responsive_image/src/Tests/ResponsiveImageAdminUITest.php
+++ b/core/modules/responsive_image/src/Tests/ResponsiveImageAdminUITest.php
@@ -44,13 +44,13 @@ public function testResponsiveImageAdmin() {
 
     // Add a new responsive image mapping, our breakpoint set should be selected.
     $this->drupalGet('admin/config/media/responsive-image-mapping/add');
-    $this->assertFieldByName('breakpointGroup', 'responsive_image_test_module');
+    $this->assertFieldByName('breakpoint_group', 'responsive_image_test_module');
 
     // Create a new group.
     $edit = array(
       'label' => 'Mapping One',
       'id' => 'mapping_one',
-      'breakpointGroup' => 'responsive_image_test_module',
+      'breakpoint_group' => 'responsive_image_test_module',
     );
     $this->drupalPostForm('admin/config/media/responsive-image-mapping/add', $edit, t('Save'));
 
@@ -64,32 +64,44 @@ public function testResponsiveImageAdmin() {
     // Edit the group.
     $this->drupalGet('admin/config/media/responsive-image-mapping/mapping_one');
     $this->assertFieldByName('label', 'Mapping One');
-    $this->assertFieldByName('breakpointGroup', 'responsive_image_test_module');
+    $this->assertFieldByName('breakpoint_group', 'responsive_image_test_module');
+
+    $cases = array(
+      array('mobile', '1x'),
+      array('mobile', '2x'),
+      array('narrow', '1x'),
+      array('narrow', '2x'),
+      array('wide', '1x'),
+      array('wide', '2x'),
+    );
 
-    // Check if the dropdowns are present for the mappings.
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.mobile][1x]', '');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.mobile][2x]', '');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.narrow][1x]', '');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.narrow][2x]', '');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.wide][1x]', '');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.wide][2x]', '');
+    foreach ($cases as $case) {
+      // Check if the radio buttons are present.
+      $this->assertFieldByName('keyed_mappings[responsive_image_test_module.' . $case[0] . '][' . $case[1] . '][image_mapping]', '');
+    }
 
     // Save mappings for 1x variant only.
     $edit = array(
       'label' => 'Mapping One',
-      'breakpointGroup' => 'responsive_image_test_module',
-      'keyed_mappings[responsive_image_test_module.mobile][1x]' => 'thumbnail',
-      'keyed_mappings[responsive_image_test_module.narrow][1x]' => 'medium',
-      'keyed_mappings[responsive_image_test_module.wide][1x]' => 'large',
+      'breakpoint_group' => 'responsive_image_test_module',
+      'keyed_mappings[responsive_image_test_module.mobile][1x][image_mapping]' => 'thumbnail',
+      'keyed_mappings[responsive_image_test_module.narrow][1x][image_mapping]' => 'medium',
+      'keyed_mappings[responsive_image_test_module.wide][1x][image_mapping]' => 'large',
     );
     $this->drupalPostForm('admin/config/media/responsive-image-mapping/mapping_one', $edit, t('Save'));
     $this->drupalGet('admin/config/media/responsive-image-mapping/mapping_one');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.mobile][1x]', 'thumbnail');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.mobile][2x]', '');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.narrow][1x]', 'medium');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.narrow][2x]', '');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.wide][1x]', 'large');
-    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.wide][2x]', '');
+
+    // Check the mapping for multipliers 1x and 2x for the mobile breakpoint.
+    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.mobile][1x][image_mapping]', 'thumbnail');
+    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.mobile][2x][image_mapping]', '');
+
+    // Check the mapping for multipliers 1x and 2x for the narrow breakpoint.
+    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.narrow][1x][image_mapping]', 'medium');
+    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.narrow][2x][image_mapping]', '');
+
+    // Check the mapping for multipliers 1x and 2x for the wide breakpoint.
+    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.wide][1x][image_mapping]', 'large');
+    $this->assertFieldByName('keyed_mappings[responsive_image_test_module.wide][2x][image_mapping]', '');
 
     // Delete the mapping.
     $this->drupalGet('admin/config/media/responsive-image-mapping/mapping_one/delete');
diff --git a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
index b09a9e5..cf7eba3 100644
--- a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
+++ b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
@@ -9,6 +9,9 @@
 
 use Drupal\Component\Utility\Unicode;
 use Drupal\image\Tests\ImageFieldTestBase;
+use Drupal\image\Entity\ImageStyle;
+use Drupal\node\Entity\Node;
+use Drupal\file\Entity\File;
 
 /**
  * Tests responsive image display formatter.
@@ -58,12 +61,12 @@ protected function setUp() {
     $this->responsiveImgMapping = entity_create('responsive_image_mapping', array(
       'id' => 'mapping_one',
       'label' => 'Mapping One',
-      'breakpointGroup' => 'responsive_image_test_module',
+      'breakpoint_group' => 'responsive_image_test_module',
     ));
   }
 
   /**
-   * Test responsive image formatters on node display for public files.
+   * Tests responsive image formatters on node display for public files.
    */
   public function testResponsiveImageFieldFormattersPublic() {
     $this->addTestMappings();
@@ -71,7 +74,7 @@ public function testResponsiveImageFieldFormattersPublic() {
   }
 
   /**
-   * Test responsive image formatters on node display for private files.
+   * Tests responsive image formatters on node display for private files.
    */
   public function testResponsiveImageFieldFormattersPrivate() {
     $this->addTestMappings();
@@ -97,16 +100,46 @@ public function testResponsiveImageFieldFormattersEmptyStyle() {
   protected function addTestMappings($empty_styles = FALSE) {
     if ($empty_styles) {
       $this->responsiveImgMapping
-        ->addMapping('responsive_image_test_module.mobile', '1x', '')
-        ->addMapping('responsive_image_test_module.narrow', '1x', '')
-        ->addMapping('responsive_image_test_module.wide', '1x', '')
+        ->addMappingDefinition('responsive_image_test_module.mobile', '1x', array(
+          'image_mapping_type' => 'image_style',
+          'image_mapping' => '',
+        ))
+        ->addMappingDefinition('responsive_image_test_module.narrow', '1x', array(
+          'image_mapping_type' => 'sizes',
+          'image_mapping' => array(
+            'sizes' => '(min-width: 700px) 700px, 100vw',
+            'sizes_image_styles' => array(),
+          ),
+        ))
+        ->addMappingDefinition('responsive_image_test_module.wide', '1x', array(
+          'image_mapping_type' => 'image_style',
+          'image_mapping' => '',
+        ))
         ->save();
     }
     else {
       $this->responsiveImgMapping
-        ->addMapping('responsive_image_test_module.mobile', '1x', 'thumbnail')
-        ->addMapping('responsive_image_test_module.narrow', '1x', 'medium')
-        ->addMapping('responsive_image_test_module.wide', '1x', 'large')
+        // Test the output of an empty image.
+        ->addMappingDefinition('responsive_image_test_module.mobile', '1x', array(
+          'image_mapping_type' => 'image_style',
+          'image_mapping' => RESPONSIVE_IMAGE_EMPTY_IMAGE,
+        ))
+        // Test the output of the 'sizes' attribute.
+        ->addMappingDefinition('responsive_image_test_module.narrow', '1x', array(
+          'image_mapping_type' => 'sizes',
+          'image_mapping' => array(
+            'sizes' => '(min-width: 700px) 700px, 100vw',
+            'sizes_image_styles' => array(
+              'large' => 'large',
+              'medium' => 'medium',
+            ),
+          ),
+        ))
+        // Test the normal output of mapping to an image style.
+        ->addMappingDefinition('responsive_image_test_module.wide', '1x', array(
+          'image_mapping_type' => 'image_style',
+          'image_mapping' => 'large',
+        ))
         ->save();
     }
   }
@@ -133,7 +166,7 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles =
     $node = $node_storage->load($nid);
 
     // Test that the default formatter is being used.
-    $image_uri = file_load($node->{$field_name}->target_id)->getFileUri();
+    $image_uri = File::load($node->{$field_name}->target_id)->getFileUri();
     $image = array(
       '#theme' => 'image',
       '#uri' => $image_uri,
@@ -191,42 +224,118 @@ protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles =
     $display->setComponent($field_name, $display_options)
       ->save();
 
+    // Create a derivative so at least one MIME type will be known.
+    $large_style = ImageStyle::load('large');
+    $large_style->createDerivative($image_uri, $large_style->buildUri($image_uri));
+
     // Output should contain all image styles and all breakpoints.
     $this->drupalGet('node/' . $nid);
     if (!$empty_styles) {
-      $this->assertRaw('/styles/thumbnail/');
       $this->assertRaw('/styles/medium/');
+      // Make sure the IE9 workaround is present.
+      $this->assertRaw('<!--[if IE 9]><video style="display: none;"><![endif]-->');
+      $this->assertRaw('<!--[if IE 9]></video><![endif]-->');
+      // Assert the empty image is present.
+      $this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
+      $this->assertRaw('/styles/medium/');
+      // Assert the output of the breakpoints.
+      $this->assertRaw('media="(min-width: 0px)"');
+      $this->assertRaw('media="(min-width: 560px)"');
+      // Assert the output of the 'sizes' attribute.
+      $this->assertRaw('sizes="(min-width: 700px) 700px, 100vw"');
+      $this->assertPattern('/media="\(min-width: 560px\)".+?sizes="\(min-width: 700px\) 700px, 100vw"/');
+      $this->assertRaw('media="(min-width: 851px)"');
     }
     $this->assertRaw('/styles/large/');
-    $this->assertRaw('media="(min-width: 0px)"');
-    $this->assertRaw('media="(min-width: 560px)"');
-    $this->assertRaw('media="(min-width: 851px)"');
     $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
     $this->assertTrue(in_array('config:responsive_image.mappings.mapping_one', $cache_tags));
     if (!$empty_styles) {
-      $this->assertTrue(in_array('config:image.style.thumbnail', $cache_tags));
       $this->assertTrue(in_array('config:image.style.medium', $cache_tags));
+      $this->assertRaw('type="image/png"');
     }
     $this->assertTrue(in_array('config:image.style.large', $cache_tags));
 
     // Test the fallback image style.
-    $large_style = entity_load('image_style', 'large');
+    $image = \Drupal::service('image.factory')->get($image_uri);
     $fallback_image = array(
-      '#theme' => 'responsive_image_source',
-      '#src' => $large_style->buildUrl($image_uri),
-      '#dimensions' => array('width' => 40, 'height' => 20),
+      '#theme' => 'image',
+      '#srcset' => array(
+        array(
+          'uri' => $large_style->buildUrl($image->getSource()),
+        ),
+      ),
     );
     $default_output = drupal_render($fallback_image);
-    $this->assertRaw($default_output, 'Image style thumbnail formatter displaying correctly on full node view.');
+    $this->assertRaw($default_output, 'Image style large formatter displaying correctly on full node view.');
 
     if ($scheme == 'private') {
       // Log out and try to access the file.
       $this->drupalLogout();
       $this->drupalGet($large_style->buildUrl($image_uri));
-      $this->assertResponse('403', 'Access denied to image style thumbnail as anonymous user.');
+      $this->assertResponse('403', 'Access denied to image style large as anonymous user.');
       $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
       $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
     }
   }
 
+  /**
+   * Tests responsive image formatters on node display linked to the file.
+   */
+  public function testResponsiveImageFieldFormattersLinkToFile() {
+    $this->addTestMappings();
+    $this->assertResponsiveImageFieldFormattersLink('file');
+  }
+
+  /**
+   * Tests responsive image formatters on node display linked to the node.
+   */
+  public function testResponsiveImageFieldFormattersLinkToNode() {
+    $this->addTestMappings();
+    $this->assertResponsiveImageFieldFormattersLink('content');
+  }
+
+  /**
+   * Tests responsive image formatters linked to the file or node.
+   */
+  private function assertResponsiveImageFieldFormattersLink($link_type) {
+    $field_name = Unicode::strtolower($this->randomMachineName());
+    $this->createImageField($field_name, 'article', array('uri_scheme' => 'public'));
+    // Create a new node with an image attached.
+    $test_image = current($this->drupalGetTestFiles('image'));
+    $nid = $this->uploadNodeImage($test_image, $field_name, 'article');
+    $this->container->get('entity.manager')->getStorage('node')->resetCache(array($nid));
+    $node = Node::load($nid);
+
+    // Use the responsive image formatter linked to file formatter.
+    $display_options = array(
+      'type' => 'responsive_image',
+      'settings' => array(
+        'image_link' => $link_type,
+        'responsive_image_mapping' => 'mapping_one',
+        'fallback_image_style' => 'large',
+      ),
+    );
+    entity_get_display('node', 'article', 'default')
+      ->setComponent($field_name, $display_options)
+      ->save();
+
+    // Create a derivative so at least one MIME type will be known.
+    $large_style = ImageStyle::load('large');
+    $image_uri = File::load($node->{$field_name}->target_id)->getFileUri();
+    $large_style->createDerivative($image_uri, $large_style->buildUri($image_uri));
+
+    // Output should contain all image styles and all breakpoints.
+    $this->drupalGet('node/' . $nid);
+    switch ($link_type) {
+      case 'file':
+        // Make sure the link to the file is present.
+        $this->assertPattern('/<a(.*?)href="' . preg_quote(file_create_url($image_uri), '/') . '"(.*?)><picture/');
+        break;
+
+      case 'content':
+        // Make sure the link to the node is present.
+        $this->assertPattern('/<a(.*?)href="' . preg_quote($node->url(), '/') . '"(.*?)><picture/');
+        break;
+    }
+  }
 }
diff --git a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldUiTest.php b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldUiTest.php
new file mode 100644
index 0000000..76ef6b6
--- /dev/null
+++ b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldUiTest.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\responsive_image\Tests\ResponsiveImageFieldUiTest.
+ */
+
+namespace Drupal\responsive_image\Tests;
+
+
+use Drupal\field_ui\Tests\FieldUiTestTrait;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the "Responsive Image" formatter settings form.
+ *
+ * @group responsive_image
+ */
+class ResponsiveImageFieldUiTest extends WebTestBase {
+
+  use FieldUiTestTrait;
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = array('node', 'field_ui', 'image', 'responsive_image', 'responsive_image_test_module', 'block');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->drupalPlaceBlock('system_breadcrumb_block');
+    // Create a test user.
+    $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'bypass node access'));
+    $this->drupalLogin($admin_user);
+
+    // Create content type, with underscores.
+    $type_name = strtolower($this->randomMachineName(8)) . '_test';
+    $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name));
+    $this->type = $type->id();
+  }
+
+  /**
+   * Tests formatter settings.
+   */
+  function testResponsiveImageFormatterUI() {
+    $manage_fields = 'admin/structure/types/manage/' . $this->type;
+    $manage_display = $manage_fields . '/display';
+
+    // Create a field, and a node with some data for the field.
+    $this->fieldUIAddNewField($manage_fields, 'image', 'Image field', 'image');
+    // Display the "Manage display".
+    $this->drupalGet($manage_display);
+
+    // Change the formatter and check that the summary is updated.
+    $edit = array('fields[field_image][type]' => 'responsive_image', 'refresh_rows' => 'field_image');
+    $this->drupalPostAjaxForm(NULL, $edit, array('op' => t('Refresh')));
+    $this->assertText("Select a responsive image mapping.", 'The expected summary is displayed.');
+
+    // Submit the form.
+    $this->drupalPostForm(NULL, array(), t('Save'));
+    $this->assertText("Select a responsive image mapping.", 'The expected summary is displayed.');
+
+    // Create responsive image mappings.
+    $responsive_image_mapping = entity_create('responsive_image_mapping', array(
+      'id' => 'mapping_one',
+      'label' => 'Mapping One',
+      'breakpoint_group' => 'responsive_image_test_module',
+    ));
+    $responsive_image_mapping
+      ->addMappingDefinition('responsive_image_test_module.mobile', '1x', array(
+        'image_mapping_type' => 'image_style',
+        'image_mapping' => 'thumbnail',
+      ))
+      ->addMappingDefinition('responsive_image_test_module.narrow', '1x', array(
+        'image_mapping_type' => 'image_style',
+        'image_mapping' => 'medium'
+      ))
+      // Test the normal output of mapping to an image style.
+      ->addMappingDefinition('responsive_image_test_module.wide', '1x', array(
+        'image_mapping_type' => 'image_style',
+        'image_mapping' => 'large',
+      ))
+      ->save();
+    \Drupal::entityManager()->clearCachedFieldDefinitions();
+    // Refresh the page.
+    $this->drupalGet($manage_display);
+    $this->assertText("Select a responsive image mapping.", 'The expected summary is displayed.');
+
+    // Click on the formatter settings button to open the formatter settings
+    // form.
+    $this->drupalPostAjaxForm(NULL, array(), "field_image_settings_edit");
+
+    // Assert that the correct fields are present.
+    $fieldnames = array(
+      'fields[field_image][settings_edit_form][settings][responsive_image_mapping]',
+      'fields[field_image][settings_edit_form][settings][fallback_image_style]',
+      'fields[field_image][settings_edit_form][settings][image_link]',
+    );
+    foreach ($fieldnames as $fieldname) {
+      $this->assertField($fieldname);
+    }
+    $edit = array(
+      'fields[field_image][settings_edit_form][settings][responsive_image_mapping]' => 'mapping_one',
+      'fields[field_image][settings_edit_form][settings][fallback_image_style]' => 'thumbnail',
+      'fields[field_image][settings_edit_form][settings][image_link]' => 'content',
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, "field_image_plugin_settings_update");
+
+    // Save the form to save the settings.
+    $this->drupalPostForm(NULL, array(), t('Save'));
+    $this->assertText('Responsive image mapping: Mapping One');
+    $this->assertText('Fallback Image style: Thumbnail (100×100)');
+    $this->assertText('Linked to content');
+
+    // Click on the formatter settings button to open the formatter settings
+    // form.
+    $this->drupalPostAjaxForm(NULL, array(), "field_image_settings_edit");
+    $edit = array(
+      'fields[field_image][settings_edit_form][settings][responsive_image_mapping]' => 'mapping_one',
+      'fields[field_image][settings_edit_form][settings][fallback_image_style]' => '',
+      'fields[field_image][settings_edit_form][settings][image_link]' => 'file',
+    );
+    $this->drupalPostAjaxForm(NULL, $edit, "field_image_plugin_settings_update");
+
+    // Save the form to save the third party settings.
+    $this->drupalPostForm(NULL, array(), t('Save'));
+    $this->assertText('Responsive image mapping: Mapping One');
+    $this->assertText('Automatic fallback');
+    $this->assertText('Linked to file');
+  }
+
+}
diff --git a/core/modules/responsive_image/templates/responsive-image.html.twig b/core/modules/responsive_image/templates/responsive-image.html.twig
new file mode 100644
index 0000000..7e6099c
--- /dev/null
+++ b/core/modules/responsive_image/templates/responsive-image.html.twig
@@ -0,0 +1,29 @@
+{#
+/**
+ * @file
+ * Default theme implementation of a responsive image.
+ *
+ * Available variables:
+ * - sources: The attributes of the <source> tags for this <picture> tag.
+ * - fallback_image: The fallback <img> tag to use for this <picture> tag.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_responsive_image()
+ *
+ * @ingroup themeable
+ */
+#}
+<picture>
+  {% if sources %}
+    {#
+    Internet Explorer 9 doesn't recognise source elements that are wrapped in
+    picture tags. See http://scottjehl.github.io/picturefill/#ie9
+    #}
+    <!--[if IE 9]><video style="display: none;"><![endif]-->
+    {% for source_attributes in sources %}
+      <source{{ source_attributes }}/>
+    {% endfor %}
+    <!--[if IE 9]></video><![endif]-->
+  {% endif %}
+  {{ img_element }}
+</picture>
diff --git a/core/modules/responsive_image/tests/src/Unit/ResponsiveImageMappingConfigEntityUnitTest.php b/core/modules/responsive_image/tests/src/Unit/ResponsiveImageMappingConfigEntityUnitTest.php
index df8a0ab..a8373c5 100644
--- a/core/modules/responsive_image/tests/src/Unit/ResponsiveImageMappingConfigEntityUnitTest.php
+++ b/core/modules/responsive_image/tests/src/Unit/ResponsiveImageMappingConfigEntityUnitTest.php
@@ -65,7 +65,7 @@ protected function setUp() {
    * @covers ::calculateDependencies
    */
   public function testCalculateDependencies() {
-    $entity = new ResponsiveImageMapping(array('breakpointGroup' => 'test_group'));
+    $entity = new ResponsiveImageMapping(array('breakpoint_group' => 'test_group'));
     $entity->setBreakpointGroup('test_group');
 
     $this->breakpointManager->expects($this->any())
@@ -79,99 +79,226 @@ public function testCalculateDependencies() {
   }
 
   /**
-   * @covers ::addMapping
-   * @covers ::hasMappings
+   * @covers ::addMappingDefinition
+   * @covers ::hasMappingDefinitions
    */
-  public function testHasMappings() {
+  public function testHasMappingDefinitions() {
     $entity = new ResponsiveImageMapping(array());
-    $this->assertFalse($entity->hasMappings());
-    $entity->addMapping('test_breakpoint', '1x', 'test_style');
-    $this->assertTrue($entity->hasMappings());
+    $this->assertFalse($entity->hasMappingDefinitions());
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+        'image_mapping_type' => 'image_style',
+        'image_mapping' => '',
+    ));
+    $this->assertFalse($entity->hasMappingDefinitions());
+    $entity->removeMappingDefinitions();
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+        'image_mapping_type' => 'sizes',
+        'image_mapping' => array(
+          'sizes' => '(min-width:700px) 700px, 100vw',
+          'sizes_image_styles' => array(),
+        ),
+    ));
+    $this->assertFalse($entity->hasMappingDefinitions());
+    $entity->removeMappingDefinitions();
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+        'image_mapping_type' => 'sizes',
+        'image_mapping' => array(
+          'sizes' => '',
+          'sizes_image_styles' => array(
+            'large' => 'large',
+          ),
+        ),
+    ));
+    $this->assertFalse($entity->hasMappingDefinitions());
+    $entity->removeMappingDefinitions();
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+        'image_mapping_type' => 'image_style',
+        'image_mapping' => 'large',
+    ));
+    $this->assertTrue($entity->hasMappingDefinitions());
+    $entity->removeMappingDefinitions();
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+        'image_mapping_type' => 'sizes',
+        'image_mapping' => array(
+          'sizes' => '(min-width:700px) 700px, 100vw',
+          'sizes_image_styles' => array(
+            'large' => 'large',
+          ),
+        ),
+    ));
+    $this->assertTrue($entity->hasMappingDefinitions());
   }
 
   /**
-   * @covers ::addMapping
-   * @covers ::getImageStyle
+   * @covers ::addMappingDefinition
+   * @covers ::getMappingDefinition
    */
-  public function testGetImageStyle() {
+  public function testGetMappingDefinition() {
     $entity = new ResponsiveImageMapping(array(''));
-    $entity->addMapping('test_breakpoint', '1x', 'test_style');
-    $this->assertEquals('test_style', $entity->getImageStyle('test_breakpoint', '1x'));
-    $this->assertNull($entity->getImageStyle('test_unknown_breakpoint', '1x'));
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'large',
+    ));
+    $expected = array(
+      'breakpoint_id' => 'test_breakpoint',
+      'multiplier' => '1x',
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'large',
+    );
+    $this->assertEquals($expected, $entity->getMappingDefinition('test_breakpoint', '1x'));
+    $this->assertNull($entity->getMappingDefinition('test_unknown_breakpoint', '1x'));
   }
 
   /**
-   * @covers ::addMapping
-   * @covers ::getMappings
+   * @covers ::addMappingDefinition
+   * @covers ::getKeyedMappingDefinitions
    */
-  public function testGetKeyedMappings() {
+  public function testGetKeyedMappingDefinitions() {
     $entity = new ResponsiveImageMapping(array(''));
-    $entity->addMapping('test_breakpoint', '1x', 'test_style');
-    $entity->addMapping('test_breakpoint', '2x', 'test_style2');
-    $entity->addMapping('test_breakpoint2', '1x', 'test_style3');
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'large',
+    ));
+    $entity->addMappingDefinition('test_breakpoint', '2x', array(
+      'image_mapping_type' => 'sizes',
+      'image_mapping' => array(
+        'sizes' => '(min-width:700px) 700px, 100vw',
+        'sizes_image_styles' => array(
+          'large' => 'large',
+        ),
+      ),
+    ));
+    $entity->addMappingDefinition('test_breakpoint2', '1x',  array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'thumbnail',
+    ));
 
     $expected = array(
       'test_breakpoint' => array(
-        '1x' => 'test_style',
-        '2x' => 'test_style2',
+        '1x' => array(
+          'breakpoint_id' => 'test_breakpoint',
+          'multiplier' => '1x',
+          'image_mapping_type' => 'image_style',
+          'image_mapping' => 'large',
+        ),
+        '2x' => array(
+          'breakpoint_id' => 'test_breakpoint',
+          'multiplier' => '2x',
+          'image_mapping_type' => 'sizes',
+          'image_mapping' => array(
+            'sizes' => '(min-width:700px) 700px, 100vw',
+            'sizes_image_styles' => array(
+              'large' => 'large',
+            ),
+          ),
+        ),
       ),
       'test_breakpoint2' => array(
-        '1x' => 'test_style3',
+        '1x' => array(
+          'breakpoint_id' => 'test_breakpoint2',
+          'multiplier' => '1x',
+          'image_mapping_type' => 'image_style',
+          'image_mapping' => 'thumbnail',
+        ),
       )
     );
-    $this->assertEquals($expected, $entity->getKeyedMappings());
+    $this->assertEquals($expected, $entity->getKeyedMappingDefinitions());
 
     // Add another mapping to ensure keyed mapping static cache is rebuilt.
-    $entity->addMapping('test_breakpoint2', '2x', 'test_style4');
-    $expected['test_breakpoint2']['2x'] = 'test_style4';
-    $this->assertEquals($expected, $entity->getKeyedMappings());
+    $entity->addMappingDefinition('test_breakpoint2', '2x', array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'medium',
+    ));
+    $expected['test_breakpoint2']['2x'] = array(
+      'breakpoint_id' => 'test_breakpoint2',
+      'multiplier' => '2x',
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'medium',
+    );
+    $this->assertEquals($expected, $entity->getKeyedMappingDefinitions());
   }
 
   /**
-   * @covers ::addMapping
-   * @covers ::getMappings
+   * @covers ::addMappingDefinition
+   * @covers ::getMappingDefinitions
    */
-  public function testGetMappings() {
+  public function testGetMappingDefinitions() {
     $entity = new ResponsiveImageMapping(array(''));
-    $entity->addMapping('test_breakpoint', '1x', 'test_style');
-    $entity->addMapping('test_breakpoint', '2x', 'test_style2');
-    $entity->addMapping('test_breakpoint2', '1x', 'test_style3');
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'large',
+    ));
+    $entity->addMappingDefinition('test_breakpoint', '2x', array(
+      'image_mapping_type' => 'sizes',
+      'image_mapping' => array(
+        'sizes' => '(min-width:700px) 700px, 100vw',
+        'sizes_image_styles' => array(
+          'large' => 'large',
+        ),
+      ),
+    ));
+    $entity->addMappingDefinition('test_breakpoint2', '1x',  array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'thumbnail',
+    ));
 
     $expected = array(
       array(
         'breakpoint_id' => 'test_breakpoint',
         'multiplier' => '1x',
-        'image_style' => 'test_style',
+        'image_mapping_type' => 'image_style',
+        'image_mapping' => 'large',
       ),
       array(
         'breakpoint_id' => 'test_breakpoint',
         'multiplier' => '2x',
-        'image_style' => 'test_style2',
+        'image_mapping_type' => 'sizes',
+        'image_mapping' => array(
+          'sizes' => '(min-width:700px) 700px, 100vw',
+          'sizes_image_styles' => array(
+            'large' => 'large',
+          ),
+        ),
       ),
       array(
         'breakpoint_id' => 'test_breakpoint2',
         'multiplier' => '1x',
-        'image_style' => 'test_style3',
+        'image_mapping_type' => 'image_style',
+        'image_mapping' => 'thumbnail',
       ),
     );
-    $this->assertEquals($expected, $entity->getMappings());
+    $this->assertEquals($expected, $entity->getMappingDefinitions());
   }
 
   /**
-   * @covers ::addMapping
-   * @covers ::removeMappings
+   * @covers ::addMappingDefinition
+   * @covers ::removeMappingDefinitions
    */
-  public function testRemoveMappings() {
+  public function testRemoveMappingDefinitions() {
     $entity = new ResponsiveImageMapping(array(''));
-    $entity->addMapping('test_breakpoint', '1x', 'test_style');
-    $entity->addMapping('test_breakpoint', '2x', 'test_style2');
-    $entity->addMapping('test_breakpoint2', '1x', 'test_style3');
-
-    $this->assertTrue($entity->hasMappings());
-    $entity->removeMappings();
-    $this->assertEmpty($entity->getMappings());
-    $this->assertEmpty($entity->getKeyedMappings());
-    $this->assertFalse($entity->hasMappings());
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'large',
+    ));
+    $entity->addMappingDefinition('test_breakpoint', '2x', array(
+      'image_mapping_type' => 'sizes',
+      'image_mapping' => array(
+        'sizes' => '(min-width:700px) 700px, 100vw',
+        'sizes_image_styles' => array(
+          'large' => 'large',
+        ),
+      ),
+    ));
+    $entity->addMappingDefinition('test_breakpoint2', '1x',  array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'thumbnail',
+    ));
+
+    $this->assertTrue($entity->hasMappingDefinitions());
+    $entity->removeMappingDefinitions();
+    $this->assertEmpty($entity->getMappingDefinitions());
+    $this->assertEmpty($entity->getKeyedMappingDefinitions());
+    $this->assertFalse($entity->hasMappingDefinitions());
   }
 
   /**
@@ -179,20 +306,34 @@ public function testRemoveMappings() {
    * @covers ::getBreakpointGroup
    */
   public function testSetBreakpointGroup() {
-    $entity = new ResponsiveImageMapping(array('breakpointGroup' => 'test_group'));
-    $entity->addMapping('test_breakpoint', '1x', 'test_style');
-    $entity->addMapping('test_breakpoint', '2x', 'test_style2');
-    $entity->addMapping('test_breakpoint2', '1x', 'test_style3');
+    $entity = new ResponsiveImageMapping(array('breakpoint_group' => 'test_group'));
+    $entity->addMappingDefinition('test_breakpoint', '1x', array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'large',
+    ));
+    $entity->addMappingDefinition('test_breakpoint', '2x', array(
+      'image_mapping_type' => 'sizes',
+      'image_mapping' => array(
+        'sizes' => '(min-width:700px) 700px, 100vw',
+        'sizes_image_styles' => array(
+          'large' => 'large',
+        ),
+      ),
+    ));
+    $entity->addMappingDefinition('test_breakpoint2', '1x',  array(
+      'image_mapping_type' => 'image_style',
+      'image_mapping' => 'thumbnail',
+    ));
 
     // Ensure that setting to same group does not remove mappings.
     $entity->setBreakpointGroup('test_group');
-    $this->assertTrue($entity->hasMappings());
+    $this->assertTrue($entity->hasMappingDefinitions());
     $this->assertEquals('test_group', $entity->getBreakpointGroup());
 
     // Ensure that changing the group removes mappings.
     $entity->setBreakpointGroup('test_group2');
     $this->assertEquals('test_group2', $entity->getBreakpointGroup());
-    $this->assertFalse($entity->hasMappings());
+    $this->assertFalse($entity->hasMappingDefinitions());
   }
 
 }
diff --git a/core/modules/toolbar/toolbar.breakpoints.yml b/core/modules/toolbar/toolbar.breakpoints.yml
index a68784a..df9f61b 100644
--- a/core/modules/toolbar/toolbar.breakpoints.yml
+++ b/core/modules/toolbar/toolbar.breakpoints.yml
@@ -1,7 +1,7 @@
 toolbar.narrow:
   label: narrow
   mediaQuery: 'only screen and (min-width: 16.5em)'
-  weight: 0
+  weight: 2
   multipliers:
     - 1x
 toolbar.standard:
@@ -13,6 +13,6 @@ toolbar.standard:
 toolbar.wide:
   label: wide
   mediaQuery: 'only screen and (min-width: 61em)'
-  weight: 2
+  weight: 0
   multipliers:
     - 1x
diff --git a/core/themes/bartik/bartik.breakpoints.yml b/core/themes/bartik/bartik.breakpoints.yml
index 17b9fc8..8e420b0 100644
--- a/core/themes/bartik/bartik.breakpoints.yml
+++ b/core/themes/bartik/bartik.breakpoints.yml
@@ -1,7 +1,7 @@
 bartik.mobile:
   label: mobile
   mediaQuery: '(min-width: 0px)'
-  weight: 0
+  weight: 2
   multipliers:
     - 1x
 bartik.narrow:
@@ -13,6 +13,6 @@ bartik.narrow:
 bartik.wide:
   label: wide
   mediaQuery: 'all and (min-width: 851px)'
-  weight: 2
+  weight: 0
   multipliers:
     - 1x
diff --git a/core/themes/seven/seven.breakpoints.yml b/core/themes/seven/seven.breakpoints.yml
index 1b6bd2f..9cc1fda 100644
--- a/core/themes/seven/seven.breakpoints.yml
+++ b/core/themes/seven/seven.breakpoints.yml
@@ -1,12 +1,12 @@
 seven.mobile:
   label: mobile
   mediaQuery: '(min-width: 0em)'
-  weight: 0
+  weight: 1
   multipliers:
     - 1x
 seven.wide:
   label: wide
   mediaQuery: 'screen and (min-width: 40em)'
-  weight: 1
+  weight: 0
   multipliers:
     - 1x
diff --git a/core/themes/stark/stark.breakpoints.yml b/core/themes/stark/stark.breakpoints.yml
index d1cdd9b..92d33b2 100644
--- a/core/themes/stark/stark.breakpoints.yml
+++ b/core/themes/stark/stark.breakpoints.yml
@@ -1,7 +1,7 @@
 stark.mobile:
   label: mobile
   mediaQuery: '(min-width: 0px)'
-  weight: 0
+  weight: 2
   multipliers:
    - 1x
 stark.narrow:
@@ -13,6 +13,6 @@ stark.narrow:
 stark.wide:
   label: wide
   mediaQuery: 'all and (min-width: 960px)'
-  weight: 2
+  weight: 0
   multipliers:
     - 1x
