next-csrf
Version:
CSRF mitigation library for Next.js
109 lines (97 loc) • 3.31 kB
JavaScript
/**
* Copyright 2018 The AMP HTML Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
;
const {createElement, nextNode, insertAfter, firstChildByTag} = require('../NodeUtils');
const {findMetaViewport, skipNodeAndChildren} = require('../HtmlDomHelper');
// Maximum number of images that will be preloaded.
const MAX_PRELOADED_IMAGES = 5;
/**
* PreloadImages - Adds preload instructions to the first 5 amp-img tags on the page, that don't use srcset.
*
* AMP requires the usage of `amp-img` for images instead of the regular `img` tag. Since
* `amp-img` tags are custom elements, the AMP Runtime needs to be loaded before the images
* are requested from the server.
*
* By issuing preload instructions, browsers will start downloading the images before the AMP
* runtime is loaded, resulting on an earlier complete render.
*
* This transformer supports the following option:
*
* * `imagePreloadCount`: specifies the maxinum number of images to preload. The default is 5.
*/
class PreloadImages {
transform(root, params) {
const imagePreloadCount = params.imagePreloadCount || MAX_PRELOADED_IMAGES;
const html = firstChildByTag(root, 'html');
const head = firstChildByTag(html, 'head');
const body = firstChildByTag(html, 'body');
const preloadImageMap = new Map();
let node = body;
while (node !== null) {
// We've hit the maximum number of preloads.
if (preloadImageMap.size >= imagePreloadCount) {
break;
}
if (node.tagName === 'template') {
node = skipNodeAndChildren(node);
} else {
this.addImage(preloadImageMap, node);
node = nextNode(node);
}
}
let referenceNode = findMetaViewport(head);
for (const preload of preloadImageMap.values()) {
insertAfter(head, preload, referenceNode);
referenceNode = preload;
}
}
addImage(preloadImageMap, node) {
const imageUrl = this.extractImageUrl(node);
if (!imageUrl) {
return;
}
// If srcset is used, skip preloading as we don't know which image will be used.
if (node.attribs.srcset) {
return;
}
preloadImageMap.set(imageUrl, this.createPreload(imageUrl, node.attribs.media));
}
extractImageUrl(node) {
if (!node.attribs) {
return null;
}
if (node.tagName === 'amp-img') {
return node.attribs.src;
}
if (node.tagName === 'amp-video') {
return node.attribs.poster;
}
return null;
}
createPreload(href, media) {
const preload = createElement('link', {
rel: 'preload',
href: href,
as: 'image',
});
if (media) {
preload.attribs.media = media;
}
return preload;
}
}
/** @module PreloadImages */
module.exports = PreloadImages;