--- a
+++ b/content.js
@@ -0,0 +1,2693 @@
+/************************************************************************/
+/* */
+/* Save Page WE - Generic WebExtension - Content Pages */
+/* */
+/* Javascript for Content Pages */
+/* */
+/* Last Edit - 27 Oct 2017 */
+/* */
+/* Copyright (C) 2016-2017 DW-dev */
+/* */
+/* Distributed under the GNU General Public License version 2 */
+/* See LICENCE.txt file and http://www.gnu.org/licenses/ */
+/* */
+/************************************************************************/
+
+/************************************************************************/
+/* */
+/* Refer to Google Chrome developer documentation: */
+/* */
+/* https://developer.chrome.com/extensions/content_scripts */
+/* https://developer.chrome.com/extensions/messaging */
+/* https://developer.chrome.com/extensions/xhr */
+/* */
+/* https://developer.chrome.com/extensions/match_patterns */
+/* */
+/* https://developer.chrome.com/extensions/extension */
+/* https://developer.chrome.com/extensions/runtime */
+/* https://developer.chrome.com/extensions/storage */
+/* */
+/************************************************************************/
+
+"use strict";
+
+/************************************************************************/
+
+/* Global variables */
+
+var isFirefox;
+
+var showWarning,showURLList,prefixFileName,suffixFileName;
+var saveHTMLAudioVideo,saveHTMLObjectEmbed,saveHTMLImagesAll;
+var saveCSSImagesAll,saveCSSFontsWoff,saveScripts;
+var usePageLoader,removeUnsavedURLs,includeInfoBar,includeSummary;
+var maxFrameDepth;
+var maxResourceSize;
+
+var savedPage; /* page was saved by Save Page WE */
+var savedPageLoader; /* page contains page loader script */
+
+var menuAction;
+var passNumber;
+var iconFound;
+
+var resourceCount;
+
+var resourceLocation = new Array();
+var resourceMimeType = new Array();
+var resourceCharSet = new Array();
+var resourceContent = new Array();
+var resourceStatus = new Array();
+var resourceRemembered = new Array();
+var resourceReplaced = new Array();
+
+var htmlStrings = new Array();
+
+var timeStart = new Array();
+var timeFinish = new Array();
+
+var pageLoader;
+
+/************************************************************************/
+
+/* Initialize on script load */
+
+chrome.storage.local.get(null,
+function(object)
+{
+ /* Load environment */
+
+ isFirefox = object["environment-isfirefox"];
+
+ /* Load options */
+
+ showWarning = object["options-showwarning"];
+ showURLList = object["options-showurllist"];
+ prefixFileName = object["options-prefixfilename"];
+ suffixFileName = object["options-suffixfilename"];
+
+ saveHTMLAudioVideo = object["options-savehtmlaudiovideo"];
+ saveHTMLObjectEmbed = object["options-savehtmlobjectembed"];
+ saveHTMLImagesAll = object["options-savehtmlimagesall"];
+ saveCSSImagesAll = object["options-savecssimagesall"];
+ saveCSSFontsWoff = object["options-savecssfontswoff"];
+ saveScripts = object["options-savescripts"];
+
+ usePageLoader = object["options-usepageloader"];
+ removeUnsavedURLs = object["options-removeunsavedurls"];
+ includeInfoBar = object["options-includeinfobar"];
+ includeSummary = object["options-includesummary"];
+
+ maxFrameDepth = object["options-maxframedepth"];
+
+ maxResourceSize = object["options-maxresourcesize"];
+
+ /* Set saved page flags */
+
+ savedPage = (document.head.querySelector("meta[name='savepage-url']") != null);
+
+ savedPageLoader = (document.head.querySelector("script[id='savepage-pageloader']") != null);
+
+ /* Add listeners */
+
+ addListeners();
+});
+
+/************************************************************************/
+
+console.log("SAVEPAGE: window.addEventListener(load)");
+
+window.addEventListener("load",
+ function(event)
+ {
+ console.log("SAVEPAGE: document.readyState " + document.readyState);
+ if (document.readyState == "complete") gatherStyleSheets();
+ },false);
+
+/* Add listeners */
+
+function addListeners()
+{
+ /* Storage changed listener */
+
+ chrome.storage.onChanged.addListener(
+ function(changes,areaName)
+ {
+ chrome.storage.local.get(null,
+ function(object)
+ {
+ showWarning = object["options-showwarning"];
+ showURLList = object["options-showurllist"];
+ prefixFileName = object["options-prefixfilename"];
+ suffixFileName = object["options-suffixfilename"];
+
+ saveHTMLAudioVideo = object["options-savehtmlaudiovideo"];
+ saveHTMLObjectEmbed = object["options-savehtmlobjectembed"];
+ saveHTMLImagesAll = object["options-savehtmlimagesall"];
+ saveCSSImagesAll = object["options-savecssimagesall"];
+ saveCSSFontsWoff = object["options-savecssfontswoff"];
+ saveScripts = object["options-savescripts"];
+
+ usePageLoader = object["options-usepageloader"];
+ removeUnsavedURLs = object["options-removeunsavedurls"];
+ includeInfoBar = object["options-includeinfobar"];
+ includeSummary = object["options-includesummary"];
+
+ maxFrameDepth = object["options-maxframedepth"];
+
+ maxResourceSize = object["options-maxresourcesize"];
+ });
+ });
+
+ /* Message received listener */
+
+ chrome.runtime.onMessage.addListener(
+ function(message,sender,sendResponse)
+ {
+ var panel;
+
+ switch (message.type)
+ {
+ /* Messages from background page */
+
+ case "performAction":
+
+ sendResponse({ }); /* to confirm content script has been loaded */
+
+ menuAction = message.menuaction;
+
+ /* Close page info panel if open */
+
+ panel = document.getElementById("savepage-pageinfo-container");
+
+ if (panel != null) document.body.removeChild(panel);
+
+ /* Wait for page to complete loading */
+
+ if (document.readyState == "complete")
+ {
+ window.setTimeout(
+ function()
+ {
+ performAction(message.srcurl);
+ },50);
+ }
+ else
+ {
+ window.addEventListener("load",
+ function(event)
+ {
+ if (document.readyState == "complete") performAction(message.srcurl);
+ },false);
+ }
+
+ break;
+
+ case "loadSuccess":
+
+ loadSuccess(message.index,message.content,message.contenttype,message.alloworigin);
+
+ break;
+
+ case "loadFailure":
+
+ loadFailure(message.index);
+
+ break;
+ }
+ });
+}
+
+/************************************************************************/
+
+/* Perform action function */
+
+function performAction(srcurl)
+{
+ var script;
+
+ if (menuAction <= 2) /* save page */
+ {
+ if (!savedPageLoader)
+ {
+ /* Initialize resources */
+
+ resourceLocation.length = 0;
+ resourceMimeType.length = 0;
+ resourceCharSet.length = 0;
+ resourceContent.length = 0;
+ resourceStatus.length = 0;
+ resourceRemembered.length = 0;
+ resourceReplaced.length = 0;
+
+ htmlStrings.length = 0;
+
+ htmlStrings[0] = "���"; /* UTF-8 Byte Order Mark (BOM) - 0xEF 0xBB 0xBF */
+
+ gatherStyleSheets();
+ }
+ else alert("This page was loaded using page loader.\n\nCannot perform operation.");
+ }
+ else if (menuAction == 3) /* view saved page info */
+ {
+ if (savedPage) viewSavedPageInfo();
+ else alert("This page was not saved by Save Page WE.\n\nCannot perform operation.");
+ }
+ else if (menuAction == 4) /* remove page loader */
+ {
+ if (savedPage)
+ {
+ if (savedPageLoader) removePageLoader();
+ else alert("This page was not loaded using page loader.\n\nCannot perform operation.");
+ }
+ else alert("This page was not saved by Save Page WE.\n\nCannot perform operation.");
+ }
+ else if (menuAction == 5) /* extract saved page media (image/audio/video) */
+ {
+ if (savedPage) extractSavedPageMedia(srcurl);
+ else alert("This page was not saved by Save Page WE.\n\nCannot perform operation.");
+ }
+}
+
+/************************************************************************/
+
+/* First Pass - to find external style sheets and load into arrays */
+
+function gatherStyleSheets()
+{
+ console.log("SAVEPAGE: gatherStyleSheets. Content type " + document.contentType + " url " + document.location.href);
+ passNumber = 1;
+
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "SAVE", color: "#E00000" });
+
+ timeStart[1] = performance.now();
+
+/* findStyleSheets(0,window,document.documentElement);*/
+
+ timeFinish[1] = performance.now();
+
+ loadResources();
+}
+
+function findStyleSheets(depth,frame,element)
+{
+ var i,baseuri,charset,csstext,regex;
+ var matches = new Array();
+
+ /* External style sheet imported in <style> element */
+
+ if (element.localName == "style")
+ {
+ csstext = element.textContent;
+
+ baseuri = element.ownerDocument.baseURI;
+
+ charset = element.ownerDocument.characterSet;
+
+ regex = /@import\s*(?:url\(\s*)?(?:'|")?(?!data:)([^\s'";)]+)(?:'|")?(?:\s*\))?\s*;/gi; /* @import url() excluding existing data uri */
+
+ while ((matches = regex.exec(csstext)) != null)
+ {
+ rememberURL(matches[1],baseuri,"text/css",charset);
+ }
+ }
+
+ /* External style sheet referenced in <link> element */
+
+ else if (element.localName == "link")
+ {
+ if (element.rel.toLowerCase() == "stylesheet" && element.getAttribute("href") != "" && element.href != "") /* href attribute and property may be different */
+ {
+ if (element.href.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ if (element.charset != "") charset = element.charset;
+ else charset = element.ownerDocument.characterSet;
+
+ rememberURL(element.href,baseuri,"text/css",charset);
+ }
+ }
+ }
+
+ /* Handle nested frames and child elements */
+
+ if (element.localName == "iframe" || element.localName == "frame") /* frame elements */
+ {
+ try
+ {
+ if (element.contentDocument.documentElement != null) /* in case web page not fully loaded before finding */
+ {
+ if (depth < maxFrameDepth)
+ {
+ findStyleSheets(depth+1,element.contentWindow,element.contentDocument.documentElement);
+ }
+ }
+ }
+ catch (e) {} /* attempting cross-domain web page access */
+ }
+ else
+ {
+ for (i = 0; i < element.children.length; i++)
+ if (element.children[i] != null) /* in case web page not fully loaded before finding */
+ findStyleSheets(depth,frame,element.children[i]);
+ }
+}
+
+/************************************************************************/
+
+/* Second Pass - to find other external resources and load into arrays */
+
+function gatherOtherResources()
+{
+ passNumber = 2;
+
+ iconFound = false;
+
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "SAVE", color: "#A000D0" });
+
+ timeStart[2] = performance.now();
+
+ findOtherResources(0,window,document.documentElement);
+
+ timeFinish[2] = performance.now();
+
+ loadResources();
+}
+
+function findOtherResources(depth,frame,element)
+{
+ var i,j,style,displayed,baseuri,charset,csstext,regex,location;
+ var matches = new Array();
+
+ style = frame.getComputedStyle(element);
+
+ displayed = (style != null && style.getPropertyValue("display") != "none");
+
+ /* External images referenced in any element's computed style */
+
+ if ((menuAction == 2 || (menuAction == 1 && !saveCSSImagesAll) || menuAction == 0) && displayed)
+ {
+ csstext = "";
+
+ csstext += style.getPropertyValue("background-image") + " ";
+ csstext += style.getPropertyValue("border-image-source") + " ";
+ csstext += style.getPropertyValue("list-style-image") + " ";
+ csstext += style.getPropertyValue("cursor") + " ";
+ csstext += style.getPropertyValue("filter") + " ";
+ csstext += style.getPropertyValue("clip-path") + " ";
+ csstext += style.getPropertyValue("mask") + " ";
+
+ style = frame.getComputedStyle(element,"before");
+ csstext += style.getPropertyValue("background-image") + " ";
+ csstext += style.getPropertyValue("border-image-source") + " ";
+ csstext += style.getPropertyValue("list-style-image") + " ";
+ csstext += style.getPropertyValue("cursor") + " ";
+ csstext += style.getPropertyValue("content") + " ";
+ csstext += style.getPropertyValue("filter") + " ";
+ csstext += style.getPropertyValue("clip-path") + " ";
+ csstext += style.getPropertyValue("mask") + " ";
+
+ style = frame.getComputedStyle(element,"after");
+ csstext += style.getPropertyValue("background-image") + " ";
+ csstext += style.getPropertyValue("border-image-source") + " ";
+ csstext += style.getPropertyValue("list-style-image") + " ";
+ csstext += style.getPropertyValue("cursor") + " ";
+ csstext += style.getPropertyValue("content") + " ";
+ csstext += style.getPropertyValue("filter") + " ";
+ csstext += style.getPropertyValue("clip-path") + " ";
+ csstext += style.getPropertyValue("mask") + " ";
+
+ style = frame.getComputedStyle(element,"first-letter");
+ csstext += style.getPropertyValue("background-image") + " ";
+ csstext += style.getPropertyValue("border-image-source") + " ";
+
+ style = frame.getComputedStyle(element,"first-line");
+ csstext += style.getPropertyValue("background-image") + " ";
+
+ baseuri = element.ownerDocument.baseURI;
+
+ regex = /url\(\s*(?:'|")?(?!data:)([^\s'")]+)(?:'|")?\s*\)/gi; /* image url() excluding existing data uri */
+
+ while ((matches = regex.exec(csstext)) != null)
+ {
+ rememberURL(matches[1],baseuri,"image/png","");
+ }
+ }
+
+ /* External images referenced in any element's style attribute */
+
+ if (element.hasAttribute("style"))
+ {
+ if (menuAction == 1 && saveCSSImagesAll)
+ {
+ csstext = element.getAttribute("style");
+
+ baseuri = element.ownerDocument.baseURI;
+
+ regex = /url\(\s*(?:'|")?(?!data:)([^\s'")]+)(?:'|")?\s*\)/gi; /* image url() excluding existing data uri */
+
+ while ((matches = regex.exec(csstext)) != null)
+ {
+ rememberURL(matches[1],baseuri,"image/png","");
+ }
+ }
+ }
+
+ /* External script referenced in <script> element */
+
+ if (element.localName == "script")
+ {
+ if (element.src != "")
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveScripts))
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ if (element.charset != "") charset = element.charset;
+ else charset = element.ownerDocument.characterSet;
+
+ rememberURL(element.src,baseuri,"application/javascript",charset);
+ }
+ }
+ }
+ }
+
+ /* External images or fonts referenced in <style> element */
+
+ else if (element.localName == "style")
+ {
+ csstext = element.textContent;
+
+ baseuri = element.ownerDocument.baseURI;
+
+ findCSSURLsInStyleSheet(csstext,baseuri);
+ }
+
+ /* External images or fonts referenced in <link> element */
+ /* External icon referenced in <link> element */
+
+ else if (element.localName == "link")
+ {
+ if (element.rel.toLowerCase() == "stylesheet" && element.getAttribute("href") != "" && element.href != "") /* href attribute and property may be different */
+ {
+ if (element.href.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ if (baseuri != null)
+ {
+ location = resolveURL(element.href,baseuri);
+
+ if (location != null)
+ {
+ for (i = 0; i < resourceLocation.length; i++)
+ if (resourceLocation[i] == location && resourceStatus[i] == "success") break;
+
+ if (i < resourceLocation.length) /* style sheet found */
+ {
+ csstext = resourceContent[i];
+
+ baseuri = element.href;
+
+ findCSSURLsInStyleSheet(csstext,baseuri);
+ }
+ }
+ }
+ }
+ }
+ else if ((element.rel.toLowerCase() == "icon" || element.rel.toLowerCase() == "shortcut icon") && element.href != "")
+ {
+ iconFound = true;
+
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.href,baseuri,"image/vnd.microsoft.icon","");
+ }
+ }
+
+ /* External image referenced in <body> element */
+
+ else if (element.localName == "body")
+ {
+ if (element.background != "")
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLImagesAll) ||
+ ((menuAction == 1 && !saveHTMLImagesAll) || menuAction == 0) && displayed)
+ {
+ if (element.background.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.background,baseuri,"image/png","");
+ }
+ }
+ }
+ }
+
+ /* External image referenced in <img> element */
+
+ else if (element.localName == "img")
+ {
+ if (element.src != "")
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLImagesAll) ||
+ ((menuAction == 1 && !saveHTMLImagesAll) || menuAction == 0) && displayed)
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.src,baseuri,"image/png","");
+ }
+ }
+ }
+ }
+
+ /* External image referenced in <input> element */
+
+ else if (element.localName == "input")
+ {
+ if (element.type.toLowerCase() == "image" && element.src != "")
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLImagesAll) ||
+ ((menuAction == 1 && !saveHTMLImagesAll) || menuAction == 0) && displayed)
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.src,baseuri,"image/png","");
+ }
+ }
+ }
+ }
+
+ /* External audio referenced in <audio> element */
+
+ else if (element.localName == "audio")
+ {
+ if (element.src != "" && element.src == element.currentSrc)
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLAudioVideo))
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.src,baseuri,"audio/mpeg","");
+ }
+ }
+ }
+ }
+
+ /* External video and image referenced in <video> element */
+
+ else if (element.localName == "video")
+ {
+ if (element.src != "" && element.src == element.currentSrc)
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLAudioVideo))
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.src,baseuri,"video/mp4","");
+ }
+ }
+ }
+
+ if (element.poster != "")
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLAudioVideo))
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLImagesAll) ||
+ ((menuAction == 1 && !saveHTMLImagesAll) || menuAction == 0) && displayed)
+ {
+ if (element.poster.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.poster,baseuri,"image/png","");
+ }
+ }
+ }
+ }
+ }
+
+ /* External audio/video referenced in <source> element */
+
+ else if (element.localName == "source")
+ {
+ if (element.src != "" && element.src == element.parentElement.currentSrc)
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLAudioVideo))
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ if (element.parentElement.localName == "audio") rememberURL(element.src,baseuri,"audio/mpeg","");
+ else if (element.parentElement.localName == "video") rememberURL(element.src,baseuri,"video/mp4","");
+ }
+ }
+ }
+ }
+
+ /* External subtitles referenced in <track> element */
+
+ else if (element.localName == "track")
+ {
+ if (element.src != "")
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLAudioVideo))
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ charset = element.ownerDocument.characterSet;
+
+ rememberURL(element.src,baseuri,"text/vtt",charset);
+ }
+ }
+ }
+ }
+
+ /* External data referenced in <object> element */
+
+ else if (element.localName == "object")
+ {
+ if (element.data != "")
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLObjectEmbed))
+ {
+ if (element.data.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.data,baseuri,"application/octet-stream","");
+ }
+ }
+ }
+ }
+
+ /* External data referenced in <embed> element */
+
+ else if (element.localName == "embed")
+ {
+ if (element.src != "")
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveHTMLObjectEmbed))
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL(element.src,baseuri,"application/octet-stream","");
+ }
+ }
+ }
+ }
+
+ /* Handle nested frames and child elements */
+
+ if (element.localName == "iframe" || element.localName == "frame") /* frame elements */
+ {
+ try
+ {
+ if (element.contentDocument.documentElement != null) /* in case web page not fully loaded before finding */
+ {
+ if (depth < maxFrameDepth)
+ {
+ findOtherResources(depth+1,element.contentWindow,element.contentDocument.documentElement);
+ }
+ }
+ }
+ catch (e) {} /* attempting cross-domain web page access */
+ }
+ else
+ {
+ for (i = 0; i < element.children.length; i++)
+ if (element.children[i] != null) /* in case web page not fully loaded before finding */
+ findOtherResources(depth,frame,element.children[i]);
+
+ if (element.localName == "head" && depth == 0)
+ {
+ if (!iconFound)
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ rememberURL("/favicon.ico",baseuri,"image/vnd.microsoft.icon","");
+ }
+ }
+ }
+}
+
+function findCSSURLsInStyleSheet(csstext,baseuri)
+{
+ var i,regex,location,fontfamily,fontweight,fontstyle,fontstretch;
+ var fontmatches,includewoff,usedfilefound,wofffilefound,srcregex,urlregex,fontfiletype;
+ var matches = new Array();
+ var propmatches = new Array();
+ var srcmatches = new Array();
+ var urlmatches = new Array();
+
+
+ /* @import url() excluding existing data uri or */
+ /* @font-face rule or */
+ /* image url() excluding existing data uri or */
+ /* avoid matches inside double-quote strings */
+ /* avoid matches inside single-quote strings */
+ /* avoid matches inside comments */
+
+ regex = new RegExp(/(?:@import\s*(?:url\(\s*)?(?:'|")?(?!data:)([^\s'";)]+)(?:'|")?(?:\s*\))?\s*;)|/.source + /* matches[1] */
+ /(?:@font-face\s*({[^}]*}))|/.source + /* matches[2] */
+ /(?:url\(\s*(?:'|")?(?!data:)([^\s'")]+)(?:'|")?\s*\))|/.source + /* matches[3] */
+ /(?:"(?:\\"|[^"])*")|/.source +
+ /(?:'(?:\\'|[^'])*')|/.source +
+ /(?:\/\*(?:\*[^\\]|[^\*])*?\*\/)/.source,
+ "gi");
+
+ while ((matches = regex.exec(csstext)) != null) /* style sheet imported into style sheet */
+ {
+ if (matches[0] && matches[0].substr(0,7).toLowerCase() == "@import") /* @import url() */
+ {
+ if (baseuri != null)
+ {
+ location = resolveURL(matches[1],baseuri);
+
+ if (location != null)
+ {
+ for (i = 0; i < resourceLocation.length; i++)
+ if (resourceLocation[i] == location && resourceStatus[i] == "success") break;
+
+ if (i < resourceLocation.length) /* style sheet found */
+ {
+ findCSSURLsInStyleSheet(resourceContent[i],resourceLocation[i]);
+ }
+ }
+ }
+ }
+ else if (matches[0] && matches[0].substr(0,10).toLowerCase() == "@font-face") /* @font-face rule */
+ {
+ includewoff = (menuAction == 2 || (menuAction == 1 && saveCSSFontsWoff));
+
+ propmatches = matches[2].match(/font-family\s*:\s*(?:'|")?([^'";}]*)(?:'|")?/i);
+ if (propmatches == null) fontfamily = ""; else fontfamily = "\"" + propmatches[1] + "\"";
+
+ propmatches = matches[2].match(/font-weight\s*:\s*([^\s;}]*)/i);
+ if (propmatches == null) fontweight = "normal"; else fontweight = propmatches[1];
+
+ propmatches = matches[2].match(/font-style\s*:\s*([^\s;}]*)/i);
+ if (propmatches == null) fontstyle = "normal"; else fontstyle = propmatches[1];
+
+ propmatches = matches[2].match(/font-stretch\s*:\s*([^\s;}]*)/i);
+ if (propmatches == null) fontstretch = "normal"; else fontstretch = propmatches[1];
+
+ fontmatches = false;
+
+ document.fonts.forEach( /* CSS Font Loading Module */
+ function(font)
+ {
+ if (font.status == "loaded") /* font is being used in this page */
+ {
+ if (font.family == fontfamily && font.weight == fontweight &&
+ font.style == fontstyle && font.stretch == fontstretch) fontmatches = true; /* font matches this @font-face rule */
+ }
+ });
+
+ if (fontmatches)
+ {
+ usedfilefound = false;
+ wofffilefound = false;
+
+ srcregex = /src:([^;}]*)[;}]/gi; /* @font-face src list */
+
+ while ((srcmatches = srcregex.exec(matches[2])) != null) /* src: list of font file URL's */
+ {
+ urlregex = /url\(\s*(?:'|")?(?!data:)([^\s'")]+)(?:'|")?\s*\)/gi; /* font url() excluding existing data uri */
+
+ while ((urlmatches = urlregex.exec(srcmatches[1])) != null) /* font file URL */
+ {
+ if (urlmatches[1].indexOf(".woff2") >= 0) fontfiletype = "woff2"; /* Chrome, Opera & Firefox */
+ else if (urlmatches[1].indexOf(".woff") >= 0 && urlmatches[1].indexOf(".woff2") < 0) fontfiletype = "woff"; /* all browsers */
+ else if (urlmatches[1].indexOf(".ttf") >= 0) fontfiletype = "ttf"; /* all browsers */
+ else if (urlmatches[1].indexOf(".otf") >= 0) fontfiletype = "otf"; /* all browsers */
+ else if (urlmatches[1].indexOf(".svg") >= 0 && !isFirefox) fontfiletype = "svg"; /* Chrome, Opera & Safari */
+ else fontfiletype = "";
+
+ if (fontfiletype != "")
+ {
+ if (!usedfilefound)
+ {
+ usedfilefound = true; /* first font file supported by this browser - should be the one used by this browser */
+
+ if (fontfiletype == "woff") wofffilefound = true;
+
+ rememberURL(urlmatches[1],baseuri,"application/font-woff","");
+ }
+ else if (includewoff && fontfiletype == "woff")
+ {
+ wofffilefound = true; /* woff font file supported by all browsers */
+
+ rememberURL(urlmatches[1],baseuri,"application/font-woff","");
+ }
+ }
+
+ if (wofffilefound || (!includewoff && usedfilefound)) break;
+ }
+
+ if (wofffilefound || (!includewoff && usedfilefound)) break;
+ }
+ }
+ }
+ else if (matches[0].substr(0,4).toLowerCase() == "url(") /* image url() */
+ {
+ if (menuAction == 1 && saveCSSImagesAll)
+ {
+ rememberURL(matches[3],baseuri,"image/png","");
+ }
+ }
+ else if (matches[0].substr(0,1) == "\"") ; /* double-quote string */
+ else if (matches[0].substr(0,1) == "'") ; /* single-quote string */
+ else if (matches[0].substr(0,2) == "/*") ; /* comment */
+ }
+}
+
+function rememberURL(url,baseuri,mimetype,charset)
+{
+ var i,location;
+
+ if (savedPage) return -1; /* ignore new resources when re-saving */
+
+ if (baseuri != null)
+ {
+ location = resolveURL(url,baseuri);
+
+ if (location != null)
+ {
+ for (i = 0; i < resourceLocation.length; i++)
+ if (resourceLocation[i] == location) break;
+
+ if (i == resourceLocation.length) /* new resource */
+ {
+ resourceLocation[i] = location;
+ resourceMimeType[i] = mimetype; /* default if load fails */
+ resourceCharSet[i] = charset; /* default if load fails */
+ resourceContent[i] = ""; /* default if load fails */
+ resourceStatus[i] = "pending";
+ resourceRemembered[i] = 1;
+ resourceReplaced[i] = 0;
+
+ return i;
+ }
+ else resourceRemembered[i]++; /* repeated resource */
+ }
+ }
+
+ return -1;
+}
+
+/************************************************************************/
+
+/* Load resources - after first or second passes */
+
+function loadResources()
+{
+ var i,documentURL;
+
+ resourceCount = 0;
+/*
+ for (i = 0; i < resourceLocation.length; i++)
+ {
+ if (resourceStatus[i] == "pending")
+ {
+ resourceCount++;
+
+ documentURL = new URL(document.baseURI,"about:blank");
+
+ chrome.runtime.sendMessage({ type: "loadResource", index: i, location: resourceLocation[i], pagescheme: documentURL.protocol });
+ }
+ }
+ if (resourceCount <= 0)
+ {
+ if (passNumber == 1) gatherOtherResources();
+ else if (passNumber == 2) (usePageLoader && !savedPage) ? loadPageLoader() : generateHTML();
+ }
+*/
+ generateHTML();
+}
+
+function loadSuccess(index,content,contenttype,alloworigin)
+{
+ var i,mimetype,charset,documentURL,resourceURL,csstext,baseuri,regex;
+ var matches = new Array();
+
+ /* Extract file MIME type and character set */
+
+ matches = contenttype.match(/([^;]+)/i);
+ if (matches != null) mimetype = matches[1].toLowerCase();
+ else mimetype = "";
+ console.log("SAVEPAGE: loadSuccess: MIME " + mimetype)
+
+ matches = contenttype.match(/;charset=([^;]+)/i);
+ if (matches != null) charset = matches[1].toLowerCase();
+ else charset = "";
+
+ /* Process file based on expected MIME type */
+
+ switch (resourceMimeType[index].toLowerCase()) /* expected MIME type */
+ {
+ case "application/font-woff": /* font file */
+
+ documentURL = new URL(document.baseURI,"about:blank");
+ resourceURL = new URL(resourceLocation[index],"about:blank");
+
+ if (resourceURL.origin != documentURL.origin && /* cross-origin */
+ (alloworigin == "" || (alloworigin != "*" && alloworigin != documentURL.origin))) /* either no header or no origin match */
+ {
+ loadFailure(index);
+ return;
+ }
+
+ case "image/png": /* image file */
+ case "image/vnd.microsoft.icon": /* icon file */
+ case "audio/mpeg": /* audio file */
+ case "video/mp4": /* video file */
+ case "application/octet-stream": /* data file */
+
+ if (mimetype != "") resourceMimeType[index] = mimetype;
+
+ resourceContent[index] = content;
+
+ break;
+
+ case "application/javascript": /* javascript file */
+
+ if (mimetype != "application/javascript" && mimetype != "application/x-javascript" && mimetype != "application/ecmascript" &&
+ mimetype != "application/json" && mimetype != "text/javascript" && mimetype != "text/x-javascript" && mimetype != "text/json")
+ {
+ loadFailure(index);
+ return;
+ }
+
+ case "text/vtt": /* subtitles file */
+
+ if (mimetype != "") resourceMimeType[index] = mimetype;
+ if (charset != "") resourceCharSet[index] = charset;
+
+ if (content.charCodeAt(0) == 0xEF && content.charCodeAt(1) == 0xBB && content.charCodeAt(2) == 0xBF) /* BOM */
+ {
+ resourceCharSet[index] = "utf-8";
+ content = content.substr(3);
+ }
+
+ if (resourceCharSet[index].toLowerCase() == "utf-8")
+ {
+ try
+ {
+ resourceContent[index] = convertUTF8ToUTF16(content); /* UTF-8 */
+ }
+ catch (e)
+ {
+ resourceCharSet[index] = "iso-8859-1"; /* assume ISO-8859-1 */
+ resourceContent[index] = content;
+ }
+ }
+ else resourceContent[index] = content; /* ASCII, ANSI, ISO-8859-1, etc */
+
+ break;
+
+ case "text/css": /* css file */
+
+ if (mimetype != "text/css") /* incorrect MIME type */
+ {
+ loadFailure(index);
+ return;
+ }
+
+ matches = content.match(/^@charset "([^"]+)";/i);
+ if (matches != null) resourceCharSet[index] = matches[1];
+
+ if (charset != "") resourceCharSet[index] = charset;
+
+ if (content.charCodeAt(0) == 0xEF && content.charCodeAt(1) == 0xBB && content.charCodeAt(2) == 0xBF) /* BOM */
+ {
+ resourceCharSet[index] = "utf-8";
+ content = content.substr(3);
+ }
+
+ if (resourceCharSet[index].toLowerCase() == "utf-8")
+ {
+ try
+ {
+ resourceContent[index] = convertUTF8ToUTF16(content); /* UTF-8 */
+ }
+ catch (e)
+ {
+ resourceCharSet[index] = "iso-8859-1"; /* assume ISO-8859-1 */
+ resourceContent[index] = content;
+ }
+ }
+ else resourceContent[index] = content; /* ASCII, ANSI, ISO-8859-1, etc */
+
+ /* External style sheets imported in external style sheet */
+
+ csstext = resourceContent[index];
+
+ baseuri = resourceLocation[index];
+
+ regex = /@import\s*(?:url\(\s*)?(?:'|")?(?!data:)([^\s'";)]+)(?:'|")?(?:\s*\))?\s*;/gi; /* @import url() excluding existing data uri */
+
+ while ((matches = regex.exec(csstext)) != null) /* style sheet imported into style sheet */
+ {
+ i = rememberURL(matches[1],baseuri,"text/css",resourceCharSet[index]);
+
+ if (i >= 0)
+ {
+ resourceCount++;
+
+ documentURL = new URL(document.baseURI,"about:blank");
+
+ chrome.runtime.sendMessage({ type: "loadResource", index: i, location: resourceLocation[i], pagescheme: documentURL.protocol });
+ }
+ }
+
+ break;
+ }
+
+ resourceStatus[index] = "success";
+
+ if (--resourceCount <= 0)
+ {
+ if (passNumber == 1) gatherOtherResources();
+ else if (passNumber == 2) (usePageLoader && !savedPage) ? loadPageLoader() : generateHTML();
+ }
+}
+
+function loadFailure(index)
+{
+ resourceStatus[index] = "failure";
+
+ if (--resourceCount <= 0)
+ {
+ if (passNumber == 1) gatherOtherResources();
+ else if (passNumber == 2) (usePageLoader && !savedPage) ? loadPageLoader() : generateHTML();
+ }
+}
+
+function loadPageLoader()
+{
+ var xhr;
+
+ xhr = new XMLHttpRequest();
+ xhr.open("GET",chrome.extension.getURL("pageloader_compressed.js"),true);
+ xhr.onload = complete;
+ xhr.send();
+
+ function complete()
+ {
+ if (xhr.status == 200)
+ {
+ pageLoader = xhr.responseText.substr(xhr.responseText.indexOf('"use strict";'));
+
+ generateHTML();
+ }
+ }
+}
+
+/************************************************************************/
+
+/* Third Pass - to generate HTML and save to file */
+
+function generateHTML()
+{
+ var i,j,dataurisize,skipcount,failcount,count,maxstrsize,totalstrsize,mimetype,charset,htmlBlob,objectURL,documentURL,filename,datestr,link;
+ var skipurllist = new Array();
+ var failurllist = new Array();
+ var pathsegments = new Array();
+ var date = new Date();
+
+ console.log("SAVEPAGE: generateHTML");
+
+ passNumber = 3;
+
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "SAVE", color: "#0000E0" });
+
+ /* Release resources */
+
+ resourceLocation.length = 0;
+ resourceMimeType.length = 0;
+ resourceCharSet.length = 0;
+ resourceContent.length = 0;
+ resourceStatus.length = 0;
+ resourceRemembered.length = 0;
+ resourceReplaced.length = 0;
+
+ /* Save to file using HTML5 download attribute */
+
+ /*htmlBlob = new Blob( htmlStrings, { type: "text/html" });*/
+
+ htmlStrings.length = 0;
+ htmlStrings[0] = document.documentElement.outerHTML;
+ htmlBlob = new Blob(htmlStrings, { type: "text/x-recoll-html" });
+ objectURL = window.URL.createObjectURL(htmlBlob);
+
+ htmlStrings.length = 0;
+
+ documentURL = new URL(document.baseURI,"about:blank");
+
+ if (document.title == "")
+ {
+ pathsegments = documentURL.pathname.split("/");
+ filename = pathsegments.pop();
+ if (filename == "") filename = pathsegments.pop();
+
+ filename = decodeURIComponent(filename);
+
+ i = filename.lastIndexOf(".");
+ if (i < 0) filename = filename + ".html";
+ else filename = filename.substring(0,i) + ".html";
+ }
+ else filename = document.title + ".html";
+
+ if (prefixFileName) filename = "{" + documentURL.hostname + "} " + filename;
+
+ if (suffixFileName)
+ {
+ datestr = new Date(date.getTime()-(date.getTimezoneOffset()*60000)).toISOString();
+ datestr = datestr.substr(2,17);
+ datestr = datestr.replace(/T/," ");
+ datestr = datestr.replace(/:/g,"-");
+
+ i = filename.lastIndexOf(".");
+ if (i < 0) filename = filename + " {" + datestr + "}";
+ else filename = filename.substring(0,i) + " {" + datestr + "}" + filename.substring(i);
+ }
+
+ link = document.createElement("a");
+ link.download = filename;
+ if(document.contentType.match(/(text|html|xml)/i)) {
+ if(document.contentType.match(/(pdf)/i)) {
+ /* Does not work: get WebExtensions context not found */
+ link.href = document.location.href;
+ } else {
+ link.href = objectURL;
+ }
+ console.log("SAVEPAGE: link.href is " + link.href);
+ document.body.appendChild(link);
+ link.addEventListener("click",handleClick,true);
+ link.click(); /* save page as .html file */
+ link.removeEventListener("click",handleClick,true);
+ console.log("SAVEPAGE: clicked link");
+ chrome.runtime.sendMessage({ type: "downloadFile", location: document.location.href, filename: filename });
+ window.setTimeout(
+ function()
+ {
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(objectURL);
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "", color: "#000000" });
+ }, 100);
+ function handleClick(event)
+ {
+ event.stopPropagation();
+ }
+ } else {
+ if (1) {
+ console.log("SAVEPAGE: send download message for " + document.location.href + " filename " + filename);
+ chrome.runtime.sendMessage({ type: "downloadFile", location: document.location.href, filename: filename });
+ } else {
+ resourceLocation[0] = document.location.href;
+ console.log("SAVEPAGE: sendMessage loadResource for " + resourceLocation[0]);
+ chrome.runtime.sendMessage({ type: "loadResource", index: 0, location: resourceLocation[0],
+ pagescheme: documentURL.protocol });
+ }
+ }
+}
+
+function extractHTML(depth,frame,element)
+{
+ var i,j,doctype,startTag,endTag,textContent,baseuri,location,csstext,origurl,datauri,origstr,target,htmltext,startindex,endindex,text,date,state;
+ var voidElements = new Array("area","base","br","col","command","embed","frame","hr","img","input","keygen","link","menuitem","meta","param","source","track","wbr");
+ var htmlFrameStrings = new Array();
+ var matches = new Array();
+
+ /* Extract HTML from DOM and replace external resources with data URI's */
+ console.log("SAVEPAGE: extractHTML");
+
+ startTag = "<" + element.localName;
+ for (i = 0; i < element.attributes.length; i++)
+ {
+ if (element.attributes[i].name != "zoompage-fontsize")
+ {
+ startTag += " " + element.attributes[i].name;
+ startTag += "=\"";
+ startTag += element.attributes[i].value.replace(/"/g,""");
+ startTag += "\"";
+ }
+ }
+ startTag += ">";
+
+ textContent = "";
+
+ if (voidElements.indexOf(element.localName) >= 0) endTag = "";
+ else endTag = "</" + element.localName + ">";
+
+ /* External images referenced in any element's style attribute */
+
+ if (element.hasAttribute("style"))
+ {
+ csstext = element.getAttribute("style");
+
+ baseuri = element.ownerDocument.baseURI;
+
+ csstext = replaceCSSURLs(csstext,baseuri);
+
+ startTag = startTag.split("style=\"" + element.getAttribute("style").replace(/"/g,""") + "\"").join("style=\"" + csstext.replace(/"/g,""") + "\"");
+ }
+
+ /* Internal script in <script> element */
+ /* External script in <script> element */
+
+ if (element.localName == "script")
+ {
+ if (element.src == "") /* internal script */
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveScripts)) textContent = element.textContent;
+ }
+ else /* element.src != "" */ /* external script */
+ {
+ if (menuAction == 2 || (menuAction == 1 && saveScripts))
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+ }
+ }
+ else
+ {
+ origurl = element.getAttribute("src");
+
+ origstr = " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"\"");
+ }
+ }
+ }
+
+ /* External images or fonts referenced in <style> element */
+
+ else if (element.localName == "style")
+ {
+ if (element.id == "zoompage-pageload-style" || element.id == "zoompage-zoomlevel-style" || element.id == "zoompage-fontsize-style")
+ {
+ startTag = "";
+ endTag = "";
+ textContent = "";
+ }
+ else
+ {
+ csstext = element.textContent;
+
+ baseuri = element.ownerDocument.baseURI;
+
+ textContent = replaceCSSURLsInStyleSheet(csstext,baseuri);
+ }
+ }
+
+ /* External images or fonts referenced in <link> element */
+ /* External icon referenced in <link> element */
+
+ else if (element.localName == "link")
+ {
+ if (element.rel.toLowerCase() == "stylesheet" && element.getAttribute("href") != "" && element.href != "") /* href attribute and property may be different */
+ {
+ if (element.href.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ if (baseuri != null)
+ {
+ location = resolveURL(element.href,baseuri);
+
+ if (location != null)
+ {
+ for (i = 0; i < resourceLocation.length; i++)
+ if (resourceLocation[i] == location && resourceStatus[i] == "success") break;
+
+ if (i < resourceLocation.length) /* style sheet found */
+ {
+ csstext = resourceContent[i];
+
+ baseuri = element.href;
+
+ csstext = replaceCSSURLsInStyleSheet(csstext,baseuri);
+
+ startTag = "<style data-savepage-href=\"" + element.getAttribute("href") + "\"";
+ if (element.type != "") startTag += " type=\"" + element.type + "\"";
+ if (element.media != "") startTag += " media=\"" + element.media + "\"";
+ startTag += ">" + csstext + "</style>";
+ endTag = "";
+
+ resourceReplaced[i]++;
+ }
+ }
+ }
+ }
+ }
+ else if ((element.rel.toLowerCase() == "icon" || element.rel.toLowerCase() == "shortcut icon") && element.href != "")
+ {
+ if (element.href.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("href");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-href=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ href="[^"]*"/,origstr + " href=\"" + datauri + "\"");
+ }
+ }
+ }
+
+ /* Remove existing base element */
+
+ else if (element.localName == "base")
+ {
+ startTag = "";
+ endTag = "";
+ }
+
+ /* Remove previous saved page information */
+
+ else if (element.localName == "meta")
+ {
+ if (element.name.substr(0,8) == "savepage")
+ {
+ startTag = "";
+ endTag = "";
+ }
+ }
+
+ /* External image referenced in <background> element */
+
+ else if (element.localName == "body")
+ {
+ if (element.background != "")
+ {
+ if (element.background.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("background");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-background=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ background="[^"]*"/,origstr + " background=\"" + datauri + "\"");
+ }
+ }
+ }
+
+ /* External image referenced in <img> element */
+
+ else if (element.localName == "img")
+ {
+ if (element.src != "")
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+ }
+ }
+
+ if (element.srcset != "")
+ {
+ origurl = element.getAttribute("srcset");
+
+ origstr = " data-savepage-srcset=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ srcset="[^"]*"/,origstr + " srcset=\"\"");
+ }
+ }
+
+ /* External image referenced in <input> element */
+ /* Reinstate checked state or text value of <input> element */
+
+ else if (element.localName == "input")
+ {
+ if (element.type.toLowerCase() == "image" && element.src != "")
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+ }
+ }
+
+ if (element.type.toLowerCase() == "file" || element.type.toLowerCase() == "password")
+ {
+ /* maintain security */
+ }
+ else if (element.type.toLowerCase() == "checkbox" || element.type.toLowerCase() == "radio")
+ {
+ startTag = startTag.replace(/ checked="[^"]*"/,"");
+
+ if (element.checked) startTag = startTag.replace(/>/," checked=\"\">");
+ }
+ else
+ {
+ startTag = startTag.replace(/ value="[^"]*"/,"");
+
+ startTag = startTag.replace(/>/," value=\"" + element.value + "\">");
+ }
+ }
+
+ /* Reinstate text value of <textarea> element */
+
+ else if (element.localName == "textarea")
+ {
+ textContent = element.value;
+ }
+
+ /* Reinstate selected state of <option> element */
+
+ else if (element.localName == "option")
+ {
+ startTag = startTag.replace(/ selected="[^"]*"/,"");
+
+ if (element.selected) startTag = startTag.replace(/>/," selected=\"\">");
+ }
+
+ /* External audio referenced in <audio> element */
+
+ else if (element.localName == "audio")
+ {
+ if (element.src != "" && element.src == element.currentSrc)
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+ }
+ }
+ }
+
+ /* External video referenced in <video> element */
+
+ else if (element.localName == "video")
+ {
+ if (element.src != "" && element.src == element.currentSrc)
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+ }
+ }
+
+ if (element.poster != "")
+ {
+ if (element.poster.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("poster");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-poster=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ poster="[^"]*"/,origstr + " poster=\"" + datauri + "\"");
+ }
+ }
+ }
+
+ /* External audio/video referenced in <source> element */
+
+ else if (element.localName == "source")
+ {
+ if (element.src != "" && element.src == element.parentElement.currentSrc)
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+ }
+ }
+ }
+
+ /* External subtitles referenced in <track> element */
+
+ else if (element.localName == "track")
+ {
+ if (element.src != "")
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+ }
+ }
+ }
+
+ /* External data referenced in <object> element */
+
+ else if (element.localName == "object")
+ {
+ if (element.data != "")
+ {
+ if (element.data.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("data");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-data=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ data="[^"]*"/,origstr + " data=\"" + datauri + "\"");
+ }
+ }
+ }
+
+ /* External data referenced in <embed> element */
+
+ else if (element.localName == "embed")
+ {
+ if (element.src != "")
+ {
+ if (element.src.substr(0,5).toLowerCase() != "data:") /* exclude existing data uri */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = replaceURL(origurl,baseuri);
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+ }
+ }
+ }
+
+ /* Handle nested frames and child elements & text nodes & comment nodes */
+ /* Generate HTML into array of strings */
+
+ if (element.localName == "iframe" || element.localName == "frame") /* frame elements */
+ {
+ if (element.src != "")
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ origurl = element.getAttribute("src");
+
+ datauri = origurl;
+
+ if (baseuri != null)
+ {
+ location = resolveURL(element.src,baseuri);
+
+ if (location != null)
+ {
+ try
+ {
+ if (element.contentDocument.documentElement != null) /* in case web page not fully loaded before extracting */
+ {
+ if (depth < maxFrameDepth)
+ {
+ startindex = htmlStrings.length;
+
+ extractHTML(depth+1,element.contentWindow,element.contentDocument.documentElement);
+
+ endindex = htmlStrings.length;
+
+ htmlFrameStrings = htmlStrings.splice(startindex,endindex-startindex);
+
+ htmltext = htmlFrameStrings.join("");
+
+ datauri = "data:text/html;charset=utf-8," + encodeURIComponent(htmltext);
+ }
+ }
+ }
+ catch (e) {} /* attempting cross-domain web page access */
+ }
+ }
+
+ origstr = (datauri == origurl) ? "" : " data-savepage-src=\"" + origurl + "\"";
+
+ startTag = startTag.replace(/ src="[^"]*"/,origstr + " src=\"" + datauri + "\"");
+
+ if (element.localName == "iframe") htmlStrings[htmlStrings.length] = startTag + endTag;
+ else htmlStrings[htmlStrings.length] = startTag;
+ }
+ }
+ else
+ {
+ if (element.localName == "html")
+ {
+ /* Add !DOCTYPE declaration */
+
+ doctype = element.ownerDocument.doctype;
+
+ if (doctype != null)
+ {
+ htmltext = '<!DOCTYPE ' + doctype.name + (doctype.publicId ? ' PUBLIC "' + doctype.publicId + '"' : '') +
+ ((doctype.systemId && !doctype.publicId) ? ' SYSTEM' : '') + (doctype.systemId ? ' "' + doctype.systemId + '"' : '') + '>';
+
+ htmlStrings[htmlStrings.length] = htmltext;
+ }
+ }
+
+ htmlStrings[htmlStrings.length] = startTag;
+
+ if (element.localName == "head")
+ {
+ /* Add <base> element to make relative URL's work in saved file */
+
+ if (element.ownerDocument.head.querySelector("base") != null) target = element.ownerDocument.head.querySelector("base").target;
+ else target = "";
+
+ htmltext = "\n";
+ htmltext += "<base href=\"" + element.ownerDocument.baseURI + "\"";
+ if (target != "") htmltext += " target=\"" + target + "\"";
+ htmltext += ">\n";
+
+ htmlStrings[htmlStrings.length] = htmltext;
+ }
+
+ if (voidElements.indexOf(element.localName) >= 0) ; /* void element */
+ else if (element.localName == "style" || (element.localName == "script" && element.src == "")) /* <style> or <script> element */
+ {
+ htmlStrings[htmlStrings.length] = textContent;
+ }
+ else if (element.localName == "textarea") /* <textarea> element */
+ {
+ textContent = textContent.replace(/&/g,"&");
+ textContent = textContent.replace(/</g,"<");
+ textContent = textContent.replace(/>/g,">");
+
+ htmlStrings[htmlStrings.length] = textContent;
+ }
+ else
+ {
+ for (i = 0; i < element.childNodes.length; i++)
+ {
+ if (element.childNodes[i] != null) /* in case web page not fully loaded before extracting */
+ {
+ if (element.childNodes[i].nodeType == 1) /* element node */
+ {
+ extractHTML(depth,frame,element.childNodes[i]);
+ }
+ else if (element.childNodes[i].nodeType == 3) /* text node */
+ {
+ text = element.childNodes[i].textContent;
+
+ text = text.replace(/&/g,"&");
+ text = text.replace(/</g,"<");
+ text = text.replace(/>/g,">");
+
+ htmlStrings[htmlStrings.length] = text;
+ }
+ else if (element.childNodes[i].nodeType == 8) /* comment node */
+ {
+ htmlStrings[htmlStrings.length] = "<!--" + element.childNodes[i].textContent + "-->";
+ }
+ }
+ }
+ }
+
+ if (element.localName == "head" && depth == 0)
+ {
+ /* Add favicon if missing */
+
+ if (!iconFound)
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ datauri = replaceURL("/favicon.ico",baseuri);
+
+ htmltext = "\n";
+ htmltext += "<link rel=\"icon\" href=\"" + datauri + "\">\n";
+
+ htmlStrings[htmlStrings.length] = htmltext;
+ }
+
+ /* Add page loader script */
+
+ if (usePageLoader && !savedPage)
+ {
+ htmlStrings[htmlStrings.length] = "\n<script id=\"savepage-pageloader\" type=\"application/javascript\">\n";
+ htmlStrings[htmlStrings.length] = "savepage_PageLoader(" + maxFrameDepth + ");\n";
+ htmlStrings[htmlStrings.length] = pageLoader.substr(0,pageLoader.length-1); /* remove final '}' */
+ htmlStrings[htmlStrings.length] = "\n";
+ for (i = 0; i < resourceLocation.length; i++)
+ {
+ if (resourceStatus[i] == "success" && resourceCharSet[i] == "") /* charset not defined - therefore binary data */
+ {
+ htmlStrings[htmlStrings.length] = "resourceMimeType[" + i + "] = \"" + resourceMimeType[i] + "\"; ";
+ htmlStrings[htmlStrings.length] = "resourceBase64Data[" + i + "] = \"" + btoa(resourceContent[i]) + "\";\n";
+ }
+ }
+ htmlStrings[htmlStrings.length] = "}\n</script>";
+ }
+
+ /* Add saved page information */
+
+ date = new Date();
+
+ if (menuAction == 0)
+ {
+ state = "Current State;";
+ if (usePageLoader && !savedPage) state += " Used page loader;";
+ if (removeUnsavedURLs) state += " Removed unsaved URLs;";
+ state += " Max frame depth = " + maxFrameDepth + ";";
+ state += " Max resource size = " + maxResourceSize + "MB;";
+ }
+ else if (menuAction == 1)
+ {
+ state = "Chosen Items;";
+ if (saveHTMLAudioVideo) state += " HTML audio & video;";
+ if (saveHTMLObjectEmbed) state += " HTML object & embed;";
+ if (saveHTMLImagesAll) state += " HTML images all;";
+ if (saveCSSImagesAll) state += " CSS images all;";
+ if (saveCSSFontsWoff) state += " CSS fonts woff;";
+ if (saveScripts) state += " Scripts;";
+ if (usePageLoader && !savedPage) state += " Used page loader;";
+ if (removeUnsavedURLs) state += " Removed unsaved URLs;";
+ state += " Max frame depth = " + maxFrameDepth + ";";
+ state += " Max resource size = " + maxResourceSize + "MB;";
+ }
+ else if (menuAction == 2)
+ {
+ state = "Complete Page - excludes CSS images (all);";
+ if (usePageLoader && !savedPage) state += " Used page loader;";
+ if (removeUnsavedURLs) state += " Removed unsaved URLs;";
+ state += " Max frame depth = " + maxFrameDepth + ";";
+ state += " Max resource size = " + maxResourceSize + "MB;";
+ }
+
+ htmltext = "\n";
+ htmltext += "<meta name=\"savepage-url\" content=\"" + decodeURIComponent(document.URL) + "\">\n";
+ htmltext += "<meta name=\"savepage-title\" content=\"" + document.title + "\">\n";
+ htmltext += "<meta name=\"savepage-date\" content=\"" + date.toString() + "\">\n";
+ htmltext += "<meta name=\"savepage-state\" content=\"" + state + "\">\n";
+ htmltext += "<meta name=\"savepage-version\" content=\"" + chrome.runtime.getManifest().version + "\">\n";
+
+ htmlStrings[htmlStrings.length] = htmltext;
+ }
+
+ if (element.localName == "body" && depth == 0)
+ {
+ if (includeInfoBar)
+ {
+ /* Add page info bar */
+
+ date = new Date();
+
+ htmltext = "\n";
+ htmltext += "<div id=\"savepage-pageinfo-bar\" style=\"display: flex !important; position: fixed !important; left:0px !important; top: 0px !important;";
+ htmltext += " width: 100% !important; height: 25px !important; border-bottom: 1px solid #E0E0E0 !important; font-family: 'Segoe UI','Helvetica Neue',Ubuntu,Arial !important; font-size: 12px !important;";
+ htmltext += " color: black !important; background-color: #FFFFE0 !important; overflow: hidden !important; z-index: 2147483645 !important; cursor: default !important;\">";
+ htmltext += "<img src onerror=\"document.body.style.setProperty('position','relative','important'); document.body.style.setProperty('margin-top','25px','important');\"";
+ htmltext += " style=\"display: none !important;\">";
+ htmltext += "<div style=\"flex: 0 1 auto !important; padding: 4px 0px !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important;\">";
+ htmltext += " Saved from:  <a href=\"" + document.URL + "\" target=\"_blank\" style=\"padding: 4px 0px !important; color: #6060E0 !important;\">" + decodeURIComponent(document.URL) + "</a>";
+ htmltext += "</div>";
+ htmltext += "<div style=\"flex: 1 1 auto !important;\"> </div>";
+ htmltext += "<div style=\"flex: 0 100000000 auto !important; min-width: 0px !important; padding: 4px 0px !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important;\">";
+ htmltext += "Saved on: " + date.toDateString().substr(8,2) + " " + date.toDateString().substr(4,3) + " " + date.toDateString().substr(11,4) + " at " + date.toTimeString().substr(0,8) + " ";
+ htmltext += "</div>";
+ htmltext += "<div style=\"flex: 0 0 auto !important; padding: 3px; border-left: 1px solid #E0E0E0 !important; border-right: 1px solid #E0E0E0 !important;\">";
+ htmltext += "<div onmouseover=\"this.style.backgroundColor = '#E0E0E0';\" onmouseout=\"this.style.backgroundColor = '#F0F0F0';\"";
+ htmltext += " onclick=\"document.body.style.removeProperty('position'); document.body.style.removeProperty('margin-top'); document.body.removeChild(this.parentElement.parentElement);\"";
+ htmltext += " style=\"padding: 0px 5px !important; border: 1px solid #E0E0E0; background-color: #F0F0F0 !important; font-size: 12px !important;\">";
+ htmltext += "X";
+ htmltext += "</div>";
+ htmltext += "</div>";
+ htmltext += "\n";
+
+ htmlStrings[htmlStrings.length] = htmltext;
+ }
+ }
+
+ htmlStrings[htmlStrings.length] = endTag;
+ }
+}
+
+function replaceCSSURLsInStyleSheet(csstext,baseuri)
+{
+ var regex;
+ var matches = new Array();
+
+ /* @import url() excluding existing data uri or */
+ /* font or image url() excluding existing data uri or */
+ /* avoid matches inside double-quote strings */
+ /* avoid matches inside single-quote strings */
+ /* avoid matches inside comments */
+
+ regex = new RegExp(/(?:( ?)@import\s*(?:url\(\s*)?(?:'|")?(?!data:)([^\s'";)]+)(?:'|")?(?:\s*\))?\s*;)|/.source + /* p1 & p2 */
+ /(?:( ?)url\(\s*(?:'|")?(?!data:)([^\s'")]+)(?:'|")?\s*\))|/.source + /* p3 & p4 */
+ /(?:"(?:\\"|[^"])*")|/.source +
+ /(?:'(?:\\'|[^'])*')|/.source +
+ /(?:\/\*(?:\*[^\\]|[^\*])*?\*\/)/.source,
+ "gi");
+
+ csstext = csstext.replace(regex,_replaceCSSURLOrImportStyleSheet);
+
+ return csstext;
+
+ function _replaceCSSURLOrImportStyleSheet(match,p1,p2,p3,p4,offset,string)
+ {
+ var i,location,csstext,datauri,origstr;
+
+ if (match.trim().substr(0,7).toLowerCase() == "@import") /* @import url() */
+ {
+ if (baseuri != null)
+ {
+ location = resolveURL(p2,baseuri);
+
+ if (location != null)
+ {
+ for (i = 0; i < resourceLocation.length; i++)
+ if (resourceLocation[i] == location && resourceStatus[i] == "success") break;
+
+ if (i < resourceLocation.length) /* style sheet found */
+ {
+ csstext = replaceCSSURLsInStyleSheet(resourceContent[i],resourceLocation[i]);
+
+ return p1 + "/*savepage-import-url=" + p2 + "*/" + p1 + csstext;
+ }
+ }
+ }
+
+ if (removeUnsavedURLs) return p1 + "/*savepage-import-url=" + p2 + "*/" + p1;
+ else return match; /* original @import rule */
+ }
+ else if (match.trim().substr(0,4).toLowerCase() == "url(") /* font or image url() */
+ {
+ datauri = replaceURL(p4,baseuri);
+
+ origstr = (datauri == p4) ? p3 : p3 + "/*savepage-url=" + p4 + "*/" + p3;
+
+ return origstr + "url(" + datauri + ")";
+ }
+ else if (match.substr(0,1) == "\"") return match; /* double-quote string */
+ else if (match.substr(0,1) == "'") return match; /* single-quote string */
+ else if (match.substr(0,2) == "/*") return match; /* comment */
+ }
+}
+
+function replaceCSSURLs(csstext,baseuri)
+{
+ var regex;
+
+ regex = /( ?)url\(\s*(?:'|")?(?!data:)([^\s'")]+)(?:'|")?\s*\)/gi; /* image url() excluding existing data uri */
+
+ csstext = csstext.replace(regex,_replaceCSSURL);
+
+ return csstext;
+
+ function _replaceCSSURL(match,p1,p2,offset,string)
+ {
+ var datauri,origstr;
+
+ datauri = replaceURL(p2,baseuri);
+
+ origstr = (datauri == p2) ? p1 : p1 + "/*savepage-url=" + p2 + "*/" + p1;
+
+ return origstr + "url(" + datauri + ")";
+ }
+}
+
+function replaceURL(url,baseuri)
+{
+ var i,location,count;
+
+ if (savedPage) return url; /* ignore new resources when re-saving */
+
+ if (baseuri != null)
+ {
+ location = resolveURL(url,baseuri);
+
+ if (location != null)
+ {
+ for (i = 0; i < resourceLocation.length; i++)
+ if (resourceLocation[i] == location && resourceStatus[i] == "success") break;
+
+ if (i < resourceLocation.length)
+ {
+ if (resourceCharSet[i] == "") /* charset not defined - therefore binary data */
+ {
+ count = usePageLoader ? 1 : resourceRemembered[i];
+
+ if (resourceContent[i].length*count*(4/3) > maxResourceSize*1024*1024) /* skip large and/or repeated resource */
+ {
+ if (removeUnsavedURLs) return ""; /* null string */
+ else return url; /* original url */
+ }
+
+ resourceReplaced[i]++;
+
+ if (usePageLoader) return "data:" + resourceMimeType[i] + ";resource=" + i + ";base64,"; /* binary data encoded as Base64 ASCII string */
+
+ return "data:" + resourceMimeType[i] + ";base64," + btoa(resourceContent[i]); /* binary data encoded as Base64 ASCII string */
+ }
+ else /* charset defined - therefore character data */
+ {
+ resourceReplaced[i]++;
+
+ return "data:" + resourceMimeType[i] + ";charset=utf-8," + encodeURIComponent(resourceContent[i]); /* characters encoded as UTF-8 %escaped string */
+ }
+ }
+ }
+ }
+
+ if (removeUnsavedURLs) return ""; /* null string */
+ else return url; /* original url */
+}
+
+function showUnsavedResources(title,urllist)
+{
+ var i,j,k,before,after,urlstring;
+
+ before = isFirefox ? 30 : 25;
+ after = isFirefox ? 50 : 35;
+
+ j = k = 0;
+ urlstring = "";
+
+ for (i = 0; i < urllist.length; i++)
+ {
+ if (urllist[i].length > before+after) urlstring += urllist[i].substr(0,before) + "..." + urllist[i].substr(-after) + "\n";
+ else urlstring += urllist[i] + "\n";
+
+ k++;
+
+ if (k-j >= 20 || (k-j > 0 && i == urllist.length-1))
+ {
+ if (!confirm(title + ":\n\n" + urlstring + "\n")) return false;
+
+ j = k;
+ urlstring = "";
+ }
+ }
+
+ return true;
+}
+
+/************************************************************************/
+
+/* Save utility functions */
+
+function resolveURL(url,baseuri)
+{
+ var resolvedURL;
+
+ try
+ {
+ resolvedURL = new URL(url,baseuri);
+ }
+ catch (e)
+ {
+ return null; /* baseuri invalid or null */
+ }
+
+ return resolvedURL.href;
+}
+
+function convertUTF8ToUTF16(utf8str)
+{
+ var i,byte1,byte2,byte3,byte4,codepoint,utf16str;
+
+ /* Convert UTF-8 string to Javascript UTF-16 string */
+ /* Each codepoint in UTF-8 string comprises one to four 8-bit values */
+ /* Each codepoint in UTF-16 string comprises one or two 16-bit values */
+
+ i = 0;
+ utf16str = "";
+
+ while (i < utf8str.length)
+ {
+ byte1 = utf8str.charCodeAt(i++);
+
+ if ((byte1 & 0x80) == 0x00)
+ {
+ utf16str += String.fromCharCode(byte1); /* one 16-bit value */
+ }
+ else if ((byte1 & 0xE0) == 0xC0)
+ {
+ byte2 = utf8str.charCodeAt(i++);
+
+ codepoint = ((byte1 & 0x1F) << 6) + (byte2 & 0x3F);
+
+ utf16str += String.fromCodePoint(codepoint); /* one 16-bit value */
+ }
+ else if ((byte1 & 0xF0) == 0xE0)
+ {
+ byte2 = utf8str.charCodeAt(i++);
+ byte3 = utf8str.charCodeAt(i++);
+
+ codepoint = ((byte1 & 0x0F) << 12) + ((byte2 & 0x3F) << 6) + (byte3 & 0x3F);
+
+ utf16str += String.fromCodePoint(codepoint); /* one 16-bit value */
+ }
+ else if ((byte1 & 0xF8) == 0xF0)
+ {
+ byte2 = utf8str.charCodeAt(i++);
+ byte3 = utf8str.charCodeAt(i++);
+ byte4 = utf8str.charCodeAt(i++);
+
+ codepoint = ((byte1 & 0x07) << 18) + ((byte2 & 0x3F) << 12) + ((byte3 & 0x3F) << 6) + (byte4 & 0x3F);
+
+ utf16str += String.fromCodePoint(codepoint); /* two 16-bit values */
+ }
+ }
+
+ return utf16str;
+}
+
+/************************************************************************/
+
+/* View saved page information function */
+
+function viewSavedPageInfo()
+{
+ var i,xhr,parser,pageinfodoc,container,metaurl,metatitle,metadate,metastate,metaversion;
+
+ /* Load page info panel */
+
+ xhr = new XMLHttpRequest();
+ xhr.open("GET",chrome.extension.getURL("pageinfo.html"),true);
+ xhr.onload = complete;
+ xhr.send();
+
+ function complete()
+ {
+ if (xhr.status == 200)
+ {
+ /* Parse page info document */
+
+ parser = new DOMParser();
+ pageinfodoc = parser.parseFromString(xhr.responseText,"text/html");
+
+ /* Create container element */
+
+ container = document.createElement("div");
+ container.setAttribute("id","savepage-pageinfo-container");
+ document.body.appendChild(container);
+
+ /* Append page info elements */
+
+ container.appendChild(pageinfodoc.getElementById("savepage-pageinfo-style"));
+ container.appendChild(pageinfodoc.getElementById("savepage-pageinfo-overlay"));
+
+ metaurl = document.head.querySelector("meta[name='savepage-url']").content;
+ metatitle = document.head.querySelector("meta[name='savepage-title']").content;
+ metadate = document.head.querySelector("meta[name='savepage-date']").content;
+ metastate = document.head.querySelector("meta[name='savepage-state']").content;
+ metaversion = document.head.querySelector("meta[name='savepage-version']").content;
+
+ if (metaversion < "6.0") metastate = metastate.replace(/(.*) (Max frame depth = \d+; Max resource size = \d+MB;) (.*)/,"$1 $3 $2");
+ if (metaversion < "7.0") metastate = metastate.replace(/CSS fonts used;/,"\n - " + "$&");
+
+ metastate = metastate.replace(/HTML audio & video;/,"\n - " + "$&");
+ metastate = metastate.replace(/HTML object & embed;/,"\n - " + "$&");
+ metastate = metastate.replace(/HTML images all;/,"\n - " + "$&");
+ metastate = metastate.replace(/CSS images all;/,"\n - " + "$&");
+ metastate = metastate.replace(/CSS fonts woff;/,"\n - " + "$&");
+ metastate = metastate.replace(/Scripts;/,"\n - " + "$&");
+ metastate = metastate.replace(/Used page loader;/,"\n" + "$&");
+ metastate = metastate.replace(/Removed unsaved URLs;/,"\n" + "$&");
+ metastate = metastate.replace(/Max frame depth = \d+;/,"\n" + "$&");
+ metastate = metastate.replace(/Max resource size = \d+MB;/,"\n" + "$&");
+
+ metaversion = "Save Page WE " + metaversion;
+
+ document.getElementById("savepage-pageinfo-url").textContent = metaurl;
+ document.getElementById("savepage-pageinfo-title").textContent = metatitle;
+ document.getElementById("savepage-pageinfo-date").textContent = metadate;
+ document.getElementById("savepage-pageinfo-state").textContent = metastate;
+ document.getElementById("savepage-pageinfo-version").textContent = metaversion;
+
+ document.getElementById("savepage-pageinfo-open").addEventListener("click",openURL,false);
+ document.getElementById("savepage-pageinfo-okay").addEventListener("click",closePanel,false);
+ }
+ }
+
+ function openURL()
+ {
+ window.open(metaurl);
+ }
+
+ function closePanel()
+ {
+ document.body.removeChild(document.getElementById("savepage-pageinfo-container"));
+ }
+}
+
+/************************************************************************/
+
+/* Remove Page Loader function */
+
+function removePageLoader()
+{
+ var resourceBlobURL = new Array();
+ var resourceMimeType = new Array();
+ var resourceBase64Data = new Array();
+ var resourceStatus = new Array();
+ var resourceRemembered = new Array();
+
+ var resourceCount;
+
+ gatherBlobResources();
+
+ /* First Pass - to gather blob resources */
+
+ function gatherBlobResources()
+ {
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "REM", color: "#FF8000" });
+
+ findBlobResources(0,window,document.documentElement);
+
+ loadBlobResources();
+ }
+
+ function findBlobResources(depth,frame,element)
+ {
+ var i,csstext,regex;
+ var matches = new Array();
+
+ if (element.hasAttribute("style"))
+ {
+ csstext = element.style.cssText;
+ regex = /url\(\s*(?:'|")?(blob:[^\s'")]+)(?:'|")?\s*\)/gi;
+ while ((matches = regex.exec(csstext)) != null) rememberBlobURL(matches[1],"image/png");
+ }
+
+ if (element.localName == "style")
+ {
+ csstext = element.textContent;
+ regex = /url\(\s*(?:'|")?(blob:[^\s'")]+)(?:'|")?\s*\)/gi;
+ while ((matches = regex.exec(csstext)) != null) rememberBlobURL(matches[1],"image/png");
+ }
+ else if (element.localName == "link" && (element.rel.toLowerCase() == "icon" || element.rel.toLowerCase() == "shortcut icon"))
+ {
+ if (element.href.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.href,"image/vnd.microsoft.icon");
+ }
+ else if (element.localName == "body")
+ {
+ if (element.background.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.background,"image/png");
+ }
+ else if (element.localName == "img")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.src,"image/png");
+ }
+ else if (element.localName == "input" && element.type.toLowerCase() == "image")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.src,"image/png");
+ }
+ else if (element.localName == "audio")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.src,"audio/mpeg");
+ }
+ else if (element.localName == "video")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.src,"video/mp4");
+ if (element.poster.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.poster,"image/png");
+ }
+ else if (element.localName == "source")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:")
+ {
+ if (element.parentElement.localName == "audio") rememberBlobURL(element.src,"audio/mpeg","");
+ else if (element.parentElement.localName == "video") rememberBlobURL(element.src,"video/mp4","");
+ }
+ }
+ else if (element.localName == "track")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.src,"text/vtt");
+ }
+ else if (element.localName == "object")
+ {
+ if (element.data.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.data,"application/octet-stream");
+ }
+ else if (element.localName == "embed")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") rememberBlobURL(element.src,"application/octet-stream");
+ }
+
+ /* Handle nested frames and child elements */
+
+ if (element.localName == "iframe" || element.localName == "frame") /* frame elements */
+ {
+ try
+ {
+ if (element.contentDocument.documentElement != null) /* in case web page not fully loaded before finding */
+ {
+ if (depth < maxFrameDepth)
+ {
+ findBlobResources(depth+1,element.contentWindow,element.contentDocument.documentElement);
+ }
+ }
+ }
+ catch (e) {} /* attempting cross-domain web page access */
+ }
+ else
+ {
+ for (i = 0; i < element.children.length; i++)
+ if (element.children[i] != null) /* in case web page not fully loaded before finding */
+ findBlobResources(depth,frame,element.children[i]);
+ }
+ }
+
+ function rememberBlobURL(bloburl,mimetype)
+ {
+ var i;
+
+ for (i = 0; i < resourceBlobURL.length; i++)
+ if (resourceBlobURL[i] == bloburl) break;
+
+ if (i == resourceBlobURL.length) /* new blob */
+ {
+ resourceBlobURL[i] = bloburl;
+ resourceMimeType[i] = mimetype; /* default if load fails */
+ resourceBase64Data[i] = ""; /* default if load fails */
+ resourceStatus[i] = "pending";
+ resourceRemembered[i] = 1;
+ }
+ else resourceRemembered[i]++; /* repeated blob */
+ }
+
+ /* Load blob resources - after first pass */
+
+ function loadBlobResources()
+ {
+ var i,xhr;
+
+ resourceCount = 0;
+
+ for (i = 0; i < resourceBlobURL.length; i++)
+ {
+ if (resourceStatus[i] == "pending")
+ {
+ resourceCount++;
+
+ try
+ {
+ xhr = new XMLHttpRequest();
+
+ xhr.open("GET",resourceBlobURL[i],true);
+ xhr.setRequestHeader("Cache-Control","no-store");
+ xhr.responseType = "arraybuffer";
+ xhr.timeout = 1000;
+ xhr._resourceIndex = i;
+ xhr.onload = loadSuccess;
+ xhr.onerror = loadFailure;
+ xhr.ontimeout = loadFailure;
+
+ xhr.send(); /* throws exception if url is invalid */
+ }
+ catch(e)
+ {
+ resourceStatus[i] = "failure";
+
+ --resourceCount;
+ }
+ }
+ }
+
+ if (resourceCount <= 0) substituteBlobResources();
+ }
+
+ function loadSuccess()
+ {
+ var i,binaryString,contentType,mimetype;
+ var byteArray = new Uint8Array(this.response);
+ var matches = new Array();
+
+ if (this.status == 200)
+ {
+ binaryString = "";
+ for (i = 0; i < byteArray.byteLength; i++) binaryString += String.fromCharCode(byteArray[i]);
+
+ contentType = this.getResponseHeader("Content-Type");
+ if (contentType == null) contentType = "";
+
+ matches = contentType.match(/([^;]+)/i);
+ if (matches != null) mimetype = matches[1].toLowerCase();
+ else mimetype = "";
+
+ if (mimetype != "") resourceMimeType[this._resourceIndex] = mimetype;
+
+ resourceBase64Data[this._resourceIndex] = btoa(binaryString);
+
+ resourceStatus[this._resourceIndex] = "success";
+ }
+ else resourceStatus[this._resourceIndex] = "failure";
+
+ if (--resourceCount <= 0) substituteBlobResources();
+ }
+
+ function loadFailure()
+ {
+ resourceStatus[this._resourceIndex] = "failure";
+
+ if (--resourceCount <= 0) substituteBlobResources();
+ }
+
+ /* Second Pass - to substitute blob URL's with data URI's */
+
+ function substituteBlobResources()
+ {
+ var i,dataurisize,skipcount,failcount,count,script;
+
+ /* Check for large resource sizes */
+
+ dataurisize = 0;
+ skipcount = 0;
+
+ for (i = 0; i < resourceBlobURL.length; i++)
+ {
+ count = resourceRemembered[i];
+
+ if (resourceBase64Data[i].length*count > maxResourceSize*1024*1024) skipcount++; /* skip large and/or repeated resource */
+ else dataurisize += resourceBase64Data[i].length*count;
+ }
+
+ if (dataurisize > 200*1024*1024) /* 200MB */
+ {
+ alert("Cannot remove page loader because the total size of resources exceeds 200MB.\n\n" +
+ "Try this suggestion:\n\n" +
+ " ��� Reduce the 'Maximum size allowed for a resource' option value.\n\n");
+
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "", color: "#000000" });
+
+ return;
+ }
+
+ if (skipcount > 0)
+ {
+ if (!confirm(skipcount + " resources exceed maximum size and will be discarded.\n\n" +
+ "Try this suggestion:\n\n" +
+ " ��� Reduce the 'Maximum size allowed for a resource' option value.\n\n"))
+ {
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "", color: "#000000" });
+
+ return;
+ }
+ }
+
+ /* Remove page loader script */
+
+ script = document.getElementById("savepage-pageloader");
+ script.parentElement.removeChild(script);
+
+ /* Release blob memory allocation */
+
+ for (i = 0; i < resourceBlobURL.length; i++)
+ window.URL.revokeObjectURL(resourceBlobURL[i]);
+
+ /* Replace blob URL's with data URI's */
+
+ replaceBlobResources(0,window,document.documentElement); /* replace blob url's with data uri's */
+
+ savedPageLoader = false;
+
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "", color: "#000000" });
+ }
+
+ function replaceBlobResources(depth,frame,element)
+ {
+ var i,csstext,regex;
+
+ if (element.hasAttribute("style"))
+ {
+ csstext = element.style.cssText;
+ regex = /url\(\s*(?:'|")?(blob:[^\s'")]+)(?:'|")?\s*\)/gi;
+ element.style.cssText = csstext.replace(regex,replaceCSSBlobURL);
+ }
+
+ if (element.localName == "style")
+ {
+ csstext = element.textContent;
+ regex = /url\(\s*(?:'|")?(blob:[^\s'")]+)(?:'|")?\s*\)/gi;
+ element.textContent = csstext.replace(regex,replaceCSSBlobURL);
+ }
+ else if (element.localName == "link" && (element.rel.toLowerCase() == "icon" || element.rel.toLowerCase() == "shortcut icon"))
+ {
+ if (element.href.substr(0,5).toLowerCase() == "blob:") element.href = replaceBlobURL(element.href);
+ }
+ else if (element.localName == "body")
+ {
+ if (element.background.substr(0,5).toLowerCase() == "blob:") element.background = replaceBlobURL(element.background);
+ }
+ else if (element.localName == "img")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") element.src = replaceBlobURL(element.src);
+ }
+ else if (element.localName == "input" && element.type.toLowerCase() == "image")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") element.src = replaceBlobURL(element.src);
+ }
+ else if (element.localName == "audio")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:")
+ {
+ element.src = replaceBlobURL(element.src);
+ element.load();
+ }
+ }
+ else if (element.localName == "video")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:")
+ {
+ element.src = replaceBlobURL(element.src);
+ element.load();
+ }
+ if (element.poster.substr(0,5).toLowerCase() == "blob:") element.poster = replaceBlobURL(element.poster);
+ }
+ else if (element.localName == "source")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:")
+ {
+ element.src = replaceBlobURL(element.src);
+ element.parentElement.load();
+ }
+ }
+ else if (element.localName == "track")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") element.src = replaceBlobURL(element.src);
+ }
+ else if (element.localName == "object")
+ {
+ if (element.data.substr(0,5).toLowerCase() == "blob:") element.data = replaceBlobURL(element.data);
+ }
+ else if (element.localName == "embed")
+ {
+ if (element.src.substr(0,5).toLowerCase() == "blob:") element.src = replaceBlobURL(element.src);
+ }
+
+ /* Handle nested frames and child elements */
+
+ if (element.localName == "iframe" || element.localName == "frame") /* frame elements */
+ {
+ try
+ {
+ if (element.contentDocument.documentElement != null) /* in case web page not fully loaded before replacing */
+ {
+ if (depth < maxFrameDepth)
+ {
+ replaceBlobResources(depth+1,element.contentWindow,element.contentDocument.documentElement);
+ }
+ }
+ }
+ catch (e) {} /* attempting cross-domain web page access */
+ }
+ else
+ {
+ for (i = 0; i < element.children.length; i++)
+ if (element.children[i] != null) /* in case web page not fully loaded before replacing */
+ replaceBlobResources(depth,frame,element.children[i]);
+ }
+ }
+
+ function replaceCSSBlobURL(match,p1,offset,string)
+ {
+ return "url(" + replaceBlobURL(p1) + ")";
+ }
+
+ function replaceBlobURL(bloburl)
+ {
+ var i,count;
+
+ for (i = 0; i < resourceBlobURL.length; i++)
+ if (resourceBlobURL[i] == bloburl && resourceStatus[i] == "success") break;
+
+ if (i < resourceBlobURL.length)
+ {
+ count = resourceRemembered[i];
+
+ if (resourceBase64Data[i].length*count > maxResourceSize*1024*1024) return bloburl; /* skip large and/or repeated resource */
+
+ return "data:" + resourceMimeType[i] + ";base64," + resourceBase64Data[i]; /* binary data encoded as Base64 ASCII string */
+ }
+
+ return bloburl;
+ }
+}
+
+/************************************************************************/
+
+/* Extract saved page media (image/audio/video) function */
+
+function extractSavedPageMedia(srcurl)
+{
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "EXT", color: "#00A000" });
+
+ if (!extract(0,window,document.documentElement)) alert("Image/Audio/Video element not found.");
+
+ chrome.runtime.sendMessage({ type: "setSaveBadge", text: "", color: "#000000" });
+
+ function extract(depth,frame,element)
+ {
+ var i,baseuri,location,mediaURL,filename,datestr,link;
+ var pathsegments = new Array();
+ var date = new Date();
+
+ if (element.localName == "img" || element.localName == "audio" || element.localName == "video" || element.localName == "source")
+ {
+ if (element.src == srcurl) /* image/audio/video found */
+ {
+ baseuri = element.ownerDocument.baseURI;
+
+ if (baseuri != null)
+ {
+ location = resolveURL(element.getAttribute("data-savepage-src"),baseuri);
+
+ mediaURL = new URL(location,"about:blank");
+
+ if (location != null)
+ {
+ pathsegments = mediaURL.pathname.split("/");
+ filename = pathsegments.pop();
+ if (filename == "") filename = pathsegments.pop();
+
+ filename = decodeURIComponent(filename);
+
+ if (prefixFileName) filename = "{" + mediaURL.hostname + "} " + filename;
+
+ if (suffixFileName)
+ {
+ datestr = new Date(date.getTime()-(date.getTimezoneOffset()*60000)).toISOString();
+ datestr = datestr.substr(2,17);
+ datestr = datestr.replace(/T/," ");
+ datestr = datestr.replace(/:/g,"-");
+
+ i = filename.lastIndexOf(".");
+ if (i < 0) filename = filename + " {" + datestr + "}";
+ else filename = filename.substring(0,i) + " {" + datestr + "}" + filename.substring(i);
+ }
+
+ link = document.createElement("a");
+ link.download = filename;
+ link.href = srcurl;
+
+ document.body.appendChild(link);
+
+ link.click(); /* save image/audio/video as file */
+
+ return true;
+ }
+ }
+ }
+ }
+
+ /* Handle nested frames and child elements */
+
+ if (element.localName == "iframe" || element.localName == "frame") /* frame elements */
+ {
+ try
+ {
+ if (element.contentDocument.documentElement != null) /* in case web page not fully loaded before extracting */
+ {
+ if (depth < maxFrameDepth)
+ {
+ if (extract(depth+1,element.contentWindow,element.contentDocument.documentElement)) return true;
+ }
+ }
+ }
+ catch (e) {} /* attempting cross-domain web page access */
+ }
+ else
+ {
+ for (i = 0; i < element.children.length; i++)
+ if (element.children[i] != null) /* in case web page not fully loaded before extracting */
+ if (extract(depth,frame,element.children[i])) return true;
+ }
+
+ return false;
+ }
+}
+
+/************************************************************************/