diff --git a/www/js/app.js b/www/js/app.js index 2362fd0e..6c917d62 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -29,15 +29,6 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFilesystemAccess'], function($, zimArchiveLoader, util, uiUtil, cookies, abstractFilesystemAccess) { - /*/ Disable any eval() call in jQuery : it's disabled by CSP in any packaged application - // It happens on some wiktionary archives, because there is some javascript inside the html article - // Cf http://forum.jquery.com/topic/jquery-ajax-disable-script-eval - jQuery.globalEval = function (code) { - // jQuery believes the javascript has been executed, but we did nothing - // In any case, that would have been blocked by CSP for package applications - console.log("jQuery tried to run some javascript with eval(), which is not allowed in packaged applications"); - }; */ - /** * Maximum number of articles to display in a search * @type Integer @@ -772,6 +763,9 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles // Since late 2014, all ZIM files should use relative URLs var regexpImageUrl = /^(?:\.\.\/|\/)+(I\/.*)$/; var regexpMetadataUrl = /^(?:\.\.\/|\/)+(-\/.*)$/; + // This regular expression matches the href of all tags containing rel="stylesheet" in raw HTML + var regexpSheetHref = /(]*rel\s*=\s*["']stylesheet)[^>]*href\s*=\s*["'])([^"']+)(["'][^>]*>)/ig; + /** * Display the the given HTML article in the web page, @@ -781,160 +775,178 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles * @param {String} htmlArticle */ function displayArticleInForm(dirEntry, htmlArticle) { - $("#readingArticle").hide(); - $("#articleContent").show(); - // Scroll the iframe to its top - $("#articleContent").contents().scrollTop(0); - // Display the article inside the web page. - //Fast-replace img with data-img and hide image [kiwix-js #272] - htmlArticle = htmlArticle.replace(/(]*)src(\s*=)/ig, + + //Fast-replace img src with data-kiwixsrc and hide image [kiwix-js #272] + htmlArticle = htmlArticle.replace(/(]*\b)src(\s*=)/ig, "$1style=\"display: none;\" onload=\"this.style.display='inline'\" data-kiwixsrc$2"); - $('#articleContent').contents().find('body').html(htmlArticle); - - // If the ServiceWorker is not useable, we need to fallback to parse the DOM - // to inject math images, and replace some links with javascript calls - if (contentInjectionMode === 'jquery') { + //Preload stylesheets [kiwix-js @149] + //Set up blobArray of promises + var cssArray = htmlArticle.match(regexpSheetHref); + var blobArray = []; + getBLOB(cssArray); - // Convert links into javascript calls - $('#articleContent').contents().find('body').find('a').each(function() { - // Store current link's url - var url = $(this).attr("href"); - if (url === null || url === undefined) { - return; + //Extract CSS URLs from given array of links + function getBLOB(arr) { + for (var i = 0; i < arr.length; i++) { + var linkArray = regexpSheetHref.exec(arr[i]); + regexpSheetHref.lastIndex = 0; //Reset start position for next loop + if (regexpMetadataUrl.test(linkArray[2])) { //It's a CSS file contained in ZIM + var linkURL = uiUtil.removeUrlParameters(decodeURIComponent(linkArray[2].match(regexpMetadataUrl)[1])); + console.log("Attempting to resolve CSS link #" + i + "..."); + var linkBLOB = resolveCSS(linkURL, i); //Pass link and index + } else { + blobArray[i] = linkArray[2]; //If CSS not in ZIM, store URL in blobArray + injectCSS(); //Ensure this is called even if none of CSS links are in ZIM } - var lowerCaseUrl = url.toLowerCase(); - var cssClass = $(this).attr("class"); + } + } - if (cssClass === "new") { - // It's a link to a missing article : display a message - $(this).on('click', function(e) { - alert("Missing article in Wikipedia"); - return false; - }); + function resolveCSS(title, index) { + selectedArchive.getDirEntryByTitle(title).then( + function (dirEntry) { + selectedArchive.readBinaryFile(dirEntry, function (readableTitle, content) { + //var cssContent = util.uintToString(content); + var cssBlob = new Blob([content], { type: 'text/css' }); + var newURL = URL.createObjectURL(cssBlob); + //return URL.createObjectURL(cssBlob); + blobArray[index] = newURL; + injectCSS(); + }); + }).fail(function (e) { + console.error("could not find DirEntry for CSS : " + title, e); + blobArray[index] = "Error"; + injectCSS(); + }); + } + + function injectCSS() { + if (blobArray.length === cssArray.length) { //If all promised values have been obtained + for (var i in cssArray) { + cssArray[i] = cssArray[i].replace(/(href\s*=\s*["'])([^"']+)/ig, "$1" + blobArray[i]); } - else if (url.slice(0, 1) === "#") { - // It's an anchor link : do nothing - } - else if (url.substring(0, 4) === "http") { - // It's an external link : open in a new tab - $(this).attr("target", "_blank"); - } - else if (url.match(regexpImageLink) - && (util.endsWith(lowerCaseUrl, ".png") - || util.endsWith(lowerCaseUrl, ".svg") - || util.endsWith(lowerCaseUrl, ".jpg") - || util.endsWith(lowerCaseUrl, ".jpeg"))) { - // It's a link to a file of Wikipedia : change the URL to the online version and open in a new tab - var onlineWikipediaUrl = url.replace(regexpImageLink, "https://" + selectedArchive._language + ".wikipedia.org/wiki/File:$1"); - $(this).attr("href", onlineWikipediaUrl); - $(this).attr("target", "_blank"); - } - else { - // It's a link to another article - // Add an onclick event to go to this article - // instead of following the link - - if (url.substring(0, 2) === "./") { - url = url.substring(2); + htmlArticle = htmlArticle.replace(regexpSheetHref, ""); //Void existing stylesheets + var cssArray$ = "\r\n" + cssArray.join("\r\n") + "\r\n"; + htmlArticle = htmlArticle.replace(/\s*(<\/head>)/i, cssArray$ + "$1"); + injectHTML(htmlArticle); //This passes the revised HTML to the image and JS subroutine... + } else { + console.log("Waiting for " + (cssArray.length - blobArray.length) + " out of " + cssArray.length + " to resolve...") + } + } + //End of preload stylesheets code + + function injectHTML(htmlContent) { + $("#readingArticle").hide(); + $("#articleContent").show(); + // Scroll the iframe to its top + $("#articleContent").contents().scrollTop(0); + $('#articleContent').contents().find('body').html(htmlContent); + + // If the ServiceWorker is not useable, we need to fallback to parse the DOM + // to inject math images, and replace some links with javascript calls + if (contentInjectionMode === 'jquery') { + + // Convert links into javascript calls + $('#articleContent').contents().find('body').find('a').each(function () { + // Store current link's url + var url = $(this).attr("href"); + if (url === null || url === undefined) { + return; } - // Remove the initial slash if it's an absolute URL - else if (url.substring(0, 1) === "/") { - url = url.substring(1); + var lowerCaseUrl = url.toLowerCase(); + var cssClass = $(this).attr("class"); + + if (cssClass === "new") { + // It's a link to a missing article : display a message + $(this).on('click', function (e) { + alert("Missing article in Wikipedia"); + return false; + }); } - $(this).on('click', function(e) { - var decodedURL = decodeURIComponent(url); - pushBrowserHistoryState(decodedURL); - goToArticle(decodedURL); - return false; - }); - } - }); + else if (url.slice(0, 1) === "#") { + // It's an anchor link : do nothing + } + else if (url.substring(0, 4) === "http") { + // It's an external link : open in a new tab + $(this).attr("target", "_blank"); + } + else if (url.match(regexpImageLink) + && (util.endsWith(lowerCaseUrl, ".png") + || util.endsWith(lowerCaseUrl, ".svg") + || util.endsWith(lowerCaseUrl, ".jpg") + || util.endsWith(lowerCaseUrl, ".jpeg"))) { + // It's a link to a file of Wikipedia : change the URL to the online version and open in a new tab + var onlineWikipediaUrl = url.replace(regexpImageLink, "https://" + selectedArchive._language + ".wikipedia.org/wiki/File:$1"); + $(this).attr("href", onlineWikipediaUrl); + $(this).attr("target", "_blank"); + } + else { + // It's a link to another article + // Add an onclick event to go to this article + // instead of following the link - // Load images - $('#articleContent').contents().find('body').find('img').each(function() { - var image = $(this); - // It's a standard image contained in the ZIM file - // We try to find its name (from an absolute or relative URL) - var imageMatch = image.attr('data-kiwixsrc').match(regexpImageUrl); //kiwix-js #272 - if (imageMatch) { - var title = decodeURIComponent(imageMatch[1]); - selectedArchive.getDirEntryByTitle(title).then(function(dirEntry) { - selectedArchive.readBinaryFile(dirEntry, function (readableTitle, content) { - // TODO : use the complete MIME-type of the image (as read from the ZIM file) - uiUtil.feedNodeWithBlob(image, 'src', content, 'image'); + if (url.substring(0, 2) === "./") { + url = url.substring(2); + } + // Remove the initial slash if it's an absolute URL + else if (url.substring(0, 1) === "/") { + url = url.substring(1); + } + $(this).on('click', function (e) { + var decodedURL = decodeURIComponent(url); + pushBrowserHistoryState(decodedURL); + goToArticle(decodedURL); + return false; }); - }).fail(function (e) { - console.error("could not find DirEntry for image:" + title, e); - }); - } - }); + } + }); - // Load CSS content - $('#articleContent').contents().find('link[rel=stylesheet]').each(function() { - var link = $(this); - // We try to find its name (from an absolute or relative URL) - var hrefMatch = link.attr("href").match(regexpMetadataUrl); - if (hrefMatch) { - // It's a CSS file contained in the ZIM file - var title = uiUtil.removeUrlParameters(decodeURIComponent(hrefMatch[1])); - selectedArchive.getDirEntryByTitle(title).then(function(dirEntry) { - selectedArchive.readBinaryFile(dirEntry, function (readableTitle, content) { - var cssContent = util.uintToString(content); - // For some reason, Firefox OS does not accept the syntax - // So we replace the tag with a - // while copying some attributes of the original tag - // Cf http://jonraasch.com/blog/javascript-style-node - var cssElement = document.createElement('style'); - cssElement.type = 'text/css'; - - if (cssElement.styleSheet) { - cssElement.styleSheet.cssText = cssContent; - } else { - cssElement.appendChild(document.createTextNode(cssContent)); - } - var mediaAttributeValue = link.attr('media'); - if (mediaAttributeValue) { - cssElement.media = mediaAttributeValue; - } - var disabledAttributeValue = link.attr('media'); - if (disabledAttributeValue) { - cssElement.disabled = disabledAttributeValue; - } - link.replaceWith(cssElement); - }); - }).fail(function (e) { - console.error("could not find DirEntry for CSS : " + title, e); - }); - } - }); - - // Load Javascript content - $('#articleContent').contents().find('script').each(function() { - var script = $(this); - // We try to find its name (from an absolute or relative URL) - var srcMatch = script.attr("src").match(regexpMetadataUrl); - // TODO check that the type of the script is text/javascript or application/javascript - if (srcMatch) { - // It's a Javascript file contained in the ZIM file - var title = uiUtil.removeUrlParameters(decodeURIComponent(srcMatch[1])); - selectedArchive.getDirEntryByTitle(title).then(function(dirEntry) { - if (dirEntry === null) - console.log("Error: js file not found: " + title); - else + // Load images + $('#articleContent').contents().find('body').find('img').each(function () { + var image = $(this); + // It's a standard image contained in the ZIM file + // We try to find its name (from an absolute or relative URL) + var imageMatch = image.attr('data-kiwixsrc').match(regexpImageUrl); //kiwix-js #272 + if (imageMatch) { + var title = decodeURIComponent(imageMatch[1]); + selectedArchive.getDirEntryByTitle(title).then(function (dirEntry) { selectedArchive.readBinaryFile(dirEntry, function (readableTitle, content) { - // TODO : I have to disable javascript for now - // var jsContent = encodeURIComponent(util.uintToString(content)); - //script.attr("src", 'data:text/javascript;charset=UTF-8,' + jsContent); + // TODO : use the complete MIME-type of the image (as read from the ZIM file) + uiUtil.feedNodeWithBlob(image, 'src', content, 'image'); }); - }).fail(function (e) { - console.error("could not find DirEntry for javascript : " + title, e); - }); - } - }); + }).fail(function (e) { + console.error("could not find DirEntry for image:" + title, e); + }); + } + }); - } + // Load Javascript content + $('#articleContent').contents().find('script').each(function () { + var script = $(this); + // We try to find its name (from an absolute or relative URL) + var srcMatch = script.attr("src").match(regexpMetadataUrl); + // TODO check that the type of the script is text/javascript or application/javascript + if (srcMatch) { + // It's a Javascript file contained in the ZIM file + var title = uiUtil.removeUrlParameters(decodeURIComponent(srcMatch[1])); + selectedArchive.getDirEntryByTitle(title).then(function (dirEntry) { + if (dirEntry === null) + console.log("Error: js file not found: " + title); + else + selectedArchive.readBinaryFile(dirEntry, function (readableTitle, content) { + // TODO : I have to disable javascript for now + // var jsContent = encodeURIComponent(util.uintToString(content)); + //script.attr("src", 'data:text/javascript;charset=UTF-8,' + jsContent); + }); + }).fail(function (e) { + console.error("could not find DirEntry for javascript : " + title, e); + }); + } + }); + + } + } } /**