diff --git a/app/code/Magento/PageCache/view/frontend/requirejs-config.js b/app/code/Magento/PageCache/view/frontend/requirejs-config.js
index 3bbfd7563f4eb18d657a38123d6057febccddaeb..ee4f512e0b7800ed5f71c7e568d7b1e41f798612 100644
--- a/app/code/Magento/PageCache/view/frontend/requirejs-config.js
+++ b/app/code/Magento/PageCache/view/frontend/requirejs-config.js
@@ -6,12 +6,7 @@
 var config = {
     map: {
         '*': {
-            formKey:    'Magento_PageCache/js/form-key',
             pageCache:  'Magento_PageCache/js/page-cache'
         }
-    },
-    deps: [
-        'Magento_PageCache/js/form-key',
-        'Magento_PageCache/js/msg-box'
-    ]
-};
\ No newline at end of file
+    }
+};
diff --git a/app/code/Magento/PageCache/view/frontend/templates/javascript.phtml b/app/code/Magento/PageCache/view/frontend/templates/javascript.phtml
index 2a3100b1acee5ff75c8e48ddff4086f8d589668e..25502193ff8ecd7271750666c9d1764ce3775fe6 100644
--- a/app/code/Magento/PageCache/view/frontend/templates/javascript.phtml
+++ b/app/code/Magento/PageCache/view/frontend/templates/javascript.phtml
@@ -7,15 +7,12 @@
 <?php /** @var \Magento\PageCache\Block\Javascript $this */ ?>
 <script>
 require([
-	"jquery",
-	"mage/mage"
-], function(jQuery){
-
-    //<![CDATA[
-    jQuery(function () {
-        jQuery('body').mage('pageCache', <?php echo $this->getScriptOptions(); ?>);
-    });
-    //]]>
+    'jquery',
+    'pageCache',
+    'domReady!'
+], function($){
+    'use strict';
 
+    $('body').pageCache(<?php echo $this->getScriptOptions(); ?>);
 });
 </script>
diff --git a/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js b/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js
index b5106032d00444ec20e831de901ab19b606b5c5f..16552af15c93d8e25fd955b2c7a3b125787b6642 100644
--- a/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js
+++ b/app/code/Magento/PageCache/view/frontend/web/js/page-cache.js
@@ -4,15 +4,109 @@
  * Copyright © 2015 Magento. All rights reserved.
  * See COPYING.txt for license details.
  */
-/*jshint browser:true jquery:true expr:true*/
+
 define([
-    "jquery",
-    "jquery/ui",
-    "mage/cookies",
-    "Magento_PageCache/js/comments"
-], function($){
-    "use strict";
-    
+    'jquery',
+    'domReady',
+    'jquery/ui',
+    'mage/cookies'
+], function ($, domReady) {
+    'use strict';
+
+    /**
+     * Nodes tree to flat list converter
+     * @returns {Array}
+     */
+    $.fn.comments = function () {
+        var elements = [];
+
+        /**
+         * @param {jQuery} element - Comment holder
+         */
+        (function lookup(element) {
+            $(element).contents().each(function (index, el) {
+                switch (el.nodeType) {
+                    case 1: // ELEMENT_NODE
+                        lookup(el);
+                        break;
+
+                    case 8: // COMMENT_NODE
+                        elements.push(el);
+                        break;
+
+                    case 9: // DOCUMENT_NODE
+                        var hostName = window.location.hostname,
+                            iFrameHostName = $('<a>')
+                                .prop('href', element.prop('src'))
+                                .prop('hostname');
+
+                        if (hostName === iFrameHostName) {
+                            lookup($(el).find('body'));
+                        }
+                        break;
+                }
+            });
+        })(this);
+
+        return elements;
+    };
+
+    /**
+     * MsgBox Widget checks if message box is displayed and sets cookie
+     */
+    $.widget('mage.msgBox', {
+        options: {
+            msgBoxCookieName: 'message_box_display',
+            msgBoxSelector: '.main div.messages'
+        },
+
+        /**
+         * Creates widget 'mage.msgBox'
+         * @private
+         */
+        _create: function () {
+            if ($.mage.cookies.get(this.options.msgBoxCookieName)) {
+                $.mage.cookies.clear(this.options.msgBoxCookieName);
+            } else {
+                $(this.options.msgBoxSelector).hide();
+            }
+        }
+    });
+
+    /**
+     * FormKey Widget - this widget is generating from key, saves it to cookie and
+     */
+    $.widget('mage.formKey', {
+        options: {
+            inputSelector: 'input[name="form_key"]',
+            allowedCharacters: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
+            length: 16
+        },
+
+        /**
+         * Creates widget 'mage.formKey'
+         * @private
+         */
+        _create: function () {
+            var date,
+                formKey = $.mage.cookies.get('form_key');
+
+            if (!formKey) {
+                formKey = generateRandomString(this.options.allowedCharacters, this.options.length);
+                date = new Date();
+                date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
+                $.mage.cookies.set('form_key', formKey, {
+                    expires: date,
+                    path: '/'
+                });
+            }
+            $(this.options.inputSelector).val(formKey);
+        }
+    });
+
+    /**
+     * PageCache Widget
+     */
     $.widget('mage.pageCache', {
         options: {
             url: '/',
@@ -21,26 +115,46 @@ define([
             versionCookieName: 'private_content_version',
             handles: []
         },
+
+        /**
+         * Creates widget 'mage.pageCache'
+         * @private
+         */
         _create: function () {
-            var version = $.mage.cookies.get(this.options.versionCookieName);
+            var placeholders,
+                version = $.mage.cookies.get(this.options.versionCookieName);
+
             if (!version) {
-                return ;
+                return;
             }
-            var placeholders = this._searchPlaceholders(this.element.comments());
-            if (placeholders.length) {
+            placeholders = this._searchPlaceholders(this.element.comments());
+
+            if (placeholders && placeholders.length) {
                 this._ajax(placeholders, version);
             }
         },
+
+        /**
+         * Parse page for placeholders.
+         * @param {Array} elements
+         * @returns {Array}
+         * @private
+         */
         _searchPlaceholders: function (elements) {
             var placeholders = [],
-                tmp = {};
-            if (!elements.length) {
+                tmp = {},
+                ii,
+                len,
+                el, matches, name;
+
+            if (!(elements && elements.length)) {
                 return placeholders;
             }
-            for (var i = 0; i < elements.length; i++) {
-                var el = elements[i],
-                    matches = this.options.patternPlaceholderOpen.exec(el.nodeValue),
-                    name = null;
+
+            for (ii = 0, len = elements.length; ii < len; ii++) {
+                el = elements[ii];
+                matches = this.options.patternPlaceholderOpen.exec(el.nodeValue);
+                name = null;
 
                 if (matches) {
                     name = matches[1];
@@ -50,8 +164,10 @@ define([
                     };
                 } else {
                     matches = this.options.patternPlaceholderClose.exec(el.nodeValue);
+
                     if (matches) {
                         name = matches[1];
+
                         if (tmp[name]) {
                             tmp[name].closeElement = el;
                             placeholders.push(tmp[name]);
@@ -60,44 +176,74 @@ define([
                     }
                 }
             }
+
             return placeholders;
         },
-        _replacePlaceholder: function(placeholder, html) {
+
+        /**
+         * Parse for page and replace placeholders
+         * @param {Object} placeholder
+         * @param {Object} html
+         * @protected
+         */
+        _replacePlaceholder: function (placeholder, html) {
+            if (!placeholder || !html) {
+                return;
+            }
+
             var parent = $(placeholder.openElement).parent(),
                 contents = parent.contents(),
                 startReplacing = false,
-                prevSibling = null;
-            for (var y = 0; y < contents.length; y++) {
-                var element = contents[y];
+                prevSibling = null,
+                yy,
+                len,
+                element;
+
+            for (yy = 0, len = contents.length; yy < len; yy++) {
+                element = contents[yy];
+
                 if (element == placeholder.openElement) {
                     startReplacing = true;
                 }
+
                 if (startReplacing) {
                     $(element).remove();
                 } else if (element.nodeType != 8) {
                     //due to comment tag doesn't have siblings we try to find it manually
                     prevSibling = element;
                 }
+
                 if (element == placeholder.closeElement) {
                     break;
                 }
             }
+
             if (prevSibling) {
                 $(prevSibling).after(html);
             } else {
                 $(parent).prepend(html);
             }
+
             // trigger event to use mage-data-init attribute
             $(parent).trigger('contentUpdated');
         },
+
+        /**
+         * AJAX helper
+         * @param {Object} placeholders
+         * @param {String} version
+         * @private
+         */
         _ajax: function (placeholders, version) {
-            var data = {
-                blocks: [],
-                handles: this.options.handles,
-                version: version
-            };
-            for (var i = 0; i < placeholders.length; i++) {
-                data.blocks.push(placeholders[i].name);
+            var ii,
+                data = {
+                    blocks: [],
+                    handles: this.options.handles,
+                    version: version
+                };
+
+            for (ii = 0; ii < placeholders.length; ii++) {
+                data.blocks.push(placeholders[ii].name);
             }
             data.blocks = JSON.stringify(data.blocks.sort());
             data.handles = JSON.stringify(data.handles);
@@ -108,18 +254,54 @@ define([
                 cache: true,
                 dataType: 'json',
                 context: this,
+
+                /**
+                 * Response handler
+                 * @param {Object} response
+                 */
                 success: function (response) {
-                    for (var i = 0; i < placeholders.length; i++) {
-                        var placeholder = placeholders[i];
-                        if (!response.hasOwnProperty(placeholder.name)) {
-                            continue;
+                    var placeholder, i;
+
+                    for (i = 0; i < placeholders.length; i++) {
+                        placeholder = placeholders[i];
+
+                        if (response.hasOwnProperty(placeholder.name)) {
+                            this._replacePlaceholder(placeholder, response[placeholder.name]);
                         }
-                        this._replacePlaceholder(placeholder, response[placeholder.name]);
                     }
                 }
             });
         }
     });
-    
-    return $.mage.pageCache;
+
+    domReady(function () {
+        $('body')
+            .pageCache()
+            .msgBox()
+            .formKey();
+    });
+
+    return {
+        'pageCache': $.mage.pageCache,
+        'formKey': $.mage.formKey,
+        'msgBox': $.mage.msgBox
+    };
+
+    /**
+     * Helper. Generate random string
+     * TODO: Merge with mage/utils
+     * @param {String} chars - list of symbols
+     * @param {Number} length - length for need string
+     * @returns {String}
+     */
+    function generateRandomString(chars, length) {
+        var result = '';
+        length = length > 0 && Number.isFinite(length) ? length : 1;
+
+        while (length--) {
+            result += chars[Math.round(Math.random() * (chars.length - 1))];
+        }
+
+        return result;
+    }
 });
diff --git a/dev/tests/js/spec/integration/Magento/PageCache/frontend/js/pageCacheSpec.js b/dev/tests/js/spec/integration/Magento/PageCache/frontend/js/pageCacheSpec.js
new file mode 100644
index 0000000000000000000000000000000000000000..a1c8a63fab3f1ad8a09b036c5a5c5898afc8dee3
--- /dev/null
+++ b/dev/tests/js/spec/integration/Magento/PageCache/frontend/js/pageCacheSpec.js
@@ -0,0 +1,340 @@
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+/*global describe*/
+/*global beforeEach*/
+/*global afterEach*/
+/*global it*/
+/*global expect*/
+/*global jasmine*/
+/*global spyOn*/
+define([
+    'jquery',
+    'Magento_PageCache/js/page-cache'
+], function ($) {
+    'use strict';
+
+    if (!Function.prototype.bind) {
+        /**
+         * @param   {Object} bind
+         * @returns {Function}
+         */
+        Function.prototype.bind = function (bind) {
+            var self = this;
+
+            /**
+             * @returns {Function}
+             */
+            return function () {
+                var args = Array.prototype.slice.call(arguments);
+
+                return self.apply(bind || null, args);
+            };
+        };
+    }
+
+    describe('Testing html-comments-parser $.fn.comments behavior', function () {
+        var element,
+            iframe,
+            comment,
+            host;
+
+        beforeEach(function () {
+            element = $('<div />');
+            iframe = $('<iframe />');
+            comment = '<!--COMMENT CONTENT-->';
+            host = window.location.hostname;
+
+            $('body')
+                .append(element)
+                .append(iframe);
+        });
+
+        afterEach(function () {
+            $(element).remove();
+            $(iframe).remove();
+        });
+
+        it('comments fn exists', function () {
+            expect($.fn.comments).toBeDefined();
+            expect($.fn.comments()).toEqual([]);
+        });
+
+        it('on empty node comments() returns empty Array', function () {
+            expect($(element).comments()).toEqual([]);
+            expect($(iframe).insertAfter('body').comments()).toEqual([]);
+        });
+
+        it('on non-empty node comments() returns empty Array with nodes', function () {
+            element.html(comment);
+            expect($(element).comments().length).toEqual(1);
+            expect($(element).comments()[0].nodeType).toEqual(8);
+            expect($(element).comments()[0].nodeValue).toEqual('COMMENT CONTENT');
+        });
+
+        it('on iframe from same host returns Array with nodes', function () {
+            iframe.contents().find('body').html(comment);
+            iframe.attr('src', '//' + host + '/');
+
+            expect(iframe.comments().length).toEqual(1);
+            expect(iframe.comments()[0].nodeType).toEqual(8);
+            expect(iframe.comments()[0].nodeValue).toEqual('COMMENT CONTENT');
+        });
+
+        it('on iframe from other host returns empty Array', function () {
+            iframe.contents().find('body').html(comment);
+            iframe.attr('src', '//' + host + '.otherHost/');
+
+            expect(iframe.comments().length).toEqual(0);
+        });
+    });
+
+    describe('Testing msgBox Widget', function () {
+        var wdContainer,
+            msgCookieName,
+            msgContainer;
+
+        beforeEach(function () {
+            wdContainer = $('<div />');
+            msgContainer = $('<div />');
+            msgCookieName = 'FAKE_COOKIE';
+        });
+
+        afterEach(function () {
+            $(wdContainer).remove();
+            $(msgContainer).remove();
+        });
+
+        it('widget extends jQuery object', function () {
+            expect($.fn.msgBox).toBeDefined();
+        });
+
+        it('widget gets options', function () {
+            wdContainer.msgBox({
+                'msgBoxCookieName': msgCookieName
+            });
+            expect(wdContainer.msgBox('option', 'msgBoxCookieName')).toBe('FAKE_COOKIE');
+        });
+
+        it('widget disables cookie if it exist', function () {
+            spyOn($.mage.cookies, 'get').and.returnValue('FAKE_MAGE_COOKIE');
+            spyOn($.mage.cookies, 'clear');
+
+            wdContainer.msgBox({
+                'msgBoxSelector': msgContainer
+            });
+
+            expect($.mage.cookies.get).toHaveBeenCalled();
+            expect($.mage.cookies.clear).toHaveBeenCalled();
+        });
+
+        it('widget disables messageBox if cookie not exist', function () {
+            spyOn($.mage.cookies, 'get');
+
+            wdContainer.msgBox({
+                'msgBoxSelector': msgContainer
+            });
+
+            expect($.mage.cookies.get).toHaveBeenCalled();
+            expect(msgContainer.is(':hidden')).toBeTruthy();
+        });
+
+        it('widget exist on load on body', function (done) {
+            $(function () {
+                expect($('body').data('mageMsgBox')).toBeDefined();
+                done();
+            });
+        });
+    });
+
+    describe('Testing FormKey Widget', function () {
+        var wdContainer,
+            msgCookieName,
+            inputContainer;
+
+        beforeEach(function () {
+            wdContainer = $('<div />');
+            inputContainer = $('<input />');
+            msgCookieName = 'FAKE_COOKIE';
+        });
+
+        afterEach(function () {
+            $(wdContainer).remove();
+            $(inputContainer).remove();
+        });
+
+        it('widget extends jQuery object', function () {
+            expect($.fn.formKey).toBeDefined();
+        });
+
+        it('widget set value to input[form_key]', function () {
+            spyOn($.mage.cookies, 'get').and.returnValue('FAKE_COOKIE');
+
+            wdContainer.formKey({
+                'inputSelector': inputContainer
+            });
+
+            expect($.mage.cookies.get).toHaveBeenCalled();
+            expect(inputContainer.val()).toBe('FAKE_COOKIE');
+        });
+
+        it('widget set value to input[form_key]', function () {
+            spyOn($.mage.cookies, 'set');
+            spyOn($.mage.cookies, 'get');
+
+            wdContainer.formKey({
+                'inputSelector': inputContainer
+            });
+
+            expect($.mage.cookies.get).toHaveBeenCalled();
+            expect($.mage.cookies.set).toHaveBeenCalled();
+            expect(inputContainer.val()).toEqual(jasmine.any(String));
+        });
+
+        it('widget exist on load on body', function (done) {
+            $(function () {
+                expect($('body').data('mageFormKey')).toBeDefined();
+                done();
+            });
+        });
+    });
+
+    describe('Testing PageCache Widget', function () {
+        var wdContainer,
+            versionCookieName,
+            pageBlockContainer;
+
+        beforeEach(function () {
+            wdContainer = $('<div />');
+            pageBlockContainer = $('<div />');
+            versionCookieName = 'FAKE_COOKIE';
+        });
+
+        afterEach(function () {
+            $(wdContainer).remove();
+            $(pageBlockContainer).remove();
+        });
+
+        it('widget extends jQuery object', function () {
+            expect($.fn.pageCache).toBeDefined();
+        });
+
+        it('widget breaks if no private_content_version cookie', function () {
+            spyOn($.mage.cookies, 'get');
+            spyOn($.fn, 'comments');
+
+            wdContainer.pageCache();
+
+            expect($.mage.cookies.get).toHaveBeenCalled();
+            expect($.fn.comments).not.toHaveBeenCalled();
+        });
+
+        it('_searchPlaceholders called only when HTML_COMMENTS', function () {
+            var nodes;
+            spyOn($.mage.cookies, 'get').and.returnValue('FAKE_VERSION_COOKIE');
+            spyOn($.mage.pageCache.prototype, '_searchPlaceholders');
+
+            wdContainer
+                .html('<!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->')
+                .pageCache();
+
+            nodes = wdContainer.comments();
+            expect(nodes.length).toEqual(2);
+
+            expect($.mage.cookies.get).toHaveBeenCalled();
+            expect($.mage.pageCache.prototype._searchPlaceholders).toHaveBeenCalled();
+            expect($.mage.pageCache.prototype._searchPlaceholders).toHaveBeenCalledWith(nodes);
+        });
+
+        it('_searchPlaceholders return Array of blocks', function () {
+            var nodes,
+                searches;
+            spyOn($.mage.cookies, 'get').and.returnValue('FAKE_VERSION_COOKIE');
+
+            wdContainer
+                .html('<!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->')
+                .pageCache();
+
+            nodes = wdContainer.comments();
+            searches = wdContainer.data('pageCache')._searchPlaceholders(nodes);
+            expect(wdContainer.data('pageCache')._searchPlaceholders()).toEqual([]);
+            expect(searches[0]).toEqual(jasmine.objectContaining({
+                name: 'FAKE_BLOCK'
+            }));
+            expect(searches[0].openElement.nodeType).toBeDefined();
+            expect(searches[0].closeElement.nodeType).toBeDefined();
+        });
+
+        it('_searchPlaceholders called only when HTML_COMMENTS', function () {
+            var nodes;
+            spyOn($.mage.cookies, 'get').and.returnValue('FAKE_VERSION_COOKIE');
+            spyOn($.mage.pageCache.prototype, '_searchPlaceholders');
+
+            wdContainer
+                .html('<!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->')
+                .pageCache();
+
+            nodes = wdContainer.comments();
+            expect(nodes.length).toEqual(2);
+
+            expect($.mage.cookies.get).toHaveBeenCalled();
+            expect($.mage.pageCache.prototype._searchPlaceholders).toHaveBeenCalled();
+            expect($.mage.pageCache.prototype._searchPlaceholders).toHaveBeenCalledWith(nodes);
+        });
+
+        it('_replacePlaceholder append HTML after sibling node', function () {
+            var replacer,
+                searcher,
+                placeholders,
+                context;
+
+            context = {
+                options: {
+                    patternPlaceholderOpen: /^ BLOCK (.+) $/,
+                    patternPlaceholderClose: /^ \/BLOCK (.+) $/
+                }
+            };
+            replacer = $.mage.pageCache.prototype._replacePlaceholder.bind(context);
+            searcher = $.mage.pageCache.prototype._searchPlaceholders.bind(context);
+
+            wdContainer
+                .html('<span></span><!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->');
+            placeholders = searcher(wdContainer.comments());
+            replacer(placeholders[0], '<span>FAKE_HTML</span>');
+
+            expect(wdContainer.html()).toEqual('<span></span><span>FAKE_HTML</span>');
+        });
+
+        it('_replacePlaceholder prepend HTML if no sibling', function () {
+            var replacer,
+                searcher,
+                placeholders,
+                context;
+
+            context = {
+                options: {
+                    patternPlaceholderOpen: /^ BLOCK (.+) $/,
+                    patternPlaceholderClose: /^ \/BLOCK (.+) $/
+                }
+            };
+            replacer = $.mage.pageCache.prototype._replacePlaceholder.bind(context);
+            searcher = $.mage.pageCache.prototype._searchPlaceholders.bind(context);
+
+            wdContainer
+                .html('<!-- BLOCK FAKE_BLOCK -->FAKE_TEXT<!-- /BLOCK FAKE_BLOCK -->');
+            placeholders = searcher(wdContainer.comments());
+            replacer(placeholders[0], '<span>FAKE_HTML</span>');
+
+            expect(wdContainer.html()).toEqual('<span>FAKE_HTML</span>');
+        });
+
+        it('widget exist on load on body', function (done) {
+            $(function () {
+                expect($('body').data('magePageCache')).toBeDefined();
+                done();
+            });
+        });
+    });
+});