--- a
+++ b/background.js
@@ -0,0 +1,761 @@
+/************************************************************************/
+/*                                                                      */
+/*      Save Page WE - Generic WebExtension - Background Page           */
+/*                                                                      */
+/*      Javascript for Background Page                                  */
+/*                                                                      */
+/*      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/overview                    */
+/*  https://developer.chrome.com/extensions/content_scripts             */
+/*  https://developer.chrome.com/extensions/messaging                   */
+/*  https://developer.chrome.com/extensions/xhr                         */
+/*  https://developer.chrome.com/extensions/contentSecurityPolicy       */
+/*                                                                      */
+/*  https://developer.chrome.com/extensions/manifest                    */
+/*  https://developer.chrome.com/extensions/declare_permissions         */
+/*  https://developer.chrome.com/extensions/match_patterns              */
+/*                                                                      */
+/*  https://developer.chrome.com/extensions/browserAction               */
+/*  https://developer.chrome.com/extensions/contextMenus                */
+/*  https://developer.chrome.com/extensions/downloads                   */
+/*  https://developer.chrome.com/extensions/notifications               */
+/*  https://developer.chrome.com/extensions/permissions                 */
+/*  https://developer.chrome.com/extensions/runtime                     */
+/*  https://developer.chrome.com/extensions/storage                     */
+/*  https://developer.chrome.com/extensions/tabs                        */
+/*                                                                      */
+/*  Refer to IETF data uri and mime type documentation:                 */
+/*                                                                      */
+/*  RFC 2397 - https://tools.ietf.org/html/rfc2397                      */
+/*  RFC 2045 - https://tools.ietf.org/html/rfc2045                      */
+/*                                                                      */
+/************************************************************************/
+
+/************************************************************************/
+/*                                                                      */
+/*  Notes on Save Page WE Operation                                     */
+/*                                                                      */
+/*  1. The basic approach is to traverse the DOM tree three times.      */
+/*                                                                      */
+/*  2. The current states of the HTML elements are extracted from       */
+/*     the DOM tree. External resources are downloaded and scanned.     */
+/*                                                                      */
+/*  3. The first pass gathers external style sheet resources:           */
+/*                                                                      */
+/*     - <style> element: find style sheet url()'s in @import rules,    */
+/*       then remember locations.                                       */
+/*                                                                      */
+/*     - <link rel="stylesheet" href="..."> element: find style sheet   */
+/*       url()'s in @import rules, then remember locations.             */
+/*                                                                      */
+/*  4. After the first pass, the referenced external style sheets are   */
+/*     downloaded from the remembered locations.                        */
+/*                                                                      */
+/*  5. The second pass gathers external script/font/image resources:    */
+/*                                                                      */
+/*     - <script> element: remember location from src attribute.        */
+/*                                                                      */
+/*     - <link rel="icon" href="..."> element: remember location        */
+/*       from href attribute.                                           */
+/*                                                                      */
+/*     - <img> element: remember location from src attribute.           */
+/*                                                                      */
+/*     if just saving currently displayed CSS images:                   */
+/*                                                                      */
+/*     - all elements: find url()'s in CSS computed style for element   */
+/*       and for before/after pseudo-elements and remember locations.   */
+/*                                                                      */
+/*     otherwise, if saving all CSS images:                             */
+/*                                                                      */
+/*     - style attribute on any element: find image url()'s in CSS      */
+/*       rules and remember locations.                                  */
+/*                                                                      */
+/*     - <style> element: handle @import rules, then find font and      */
+/*       image url()'s in CSS rules and remember locations.             */
+/*                                                                      */
+/*     - <link rel="stylesheet" href="..."> element: handle @import     */
+/*       rules, then find font and image url()'s in CSS rules and       */
+/*       remember locations.                                            */
+/*                                                                      */
+/*  6. After the second pass, the referenced external resources are     */
+/*     downloaded from the remembered locations.                        */
+/*                                                                      */
+/*  7. The third pass generates HTML and data uri's:                    */
+/*                                                                      */
+/*     - style attribute on any element: replace image url()'s in       */
+/*       CSS rules with data uri's.                                     */
+/*                                                                      */
+/*     - <script> element: Javascript is not changed.                   */
+/*                                                                      */
+/*     - <script src="..."> element: convert Javascript to data uri     */
+/*       and use this to replace url in src attribute.                  */
+/*                                                                      */
+/*     - <style> element: handle @import rules, then replace font and   */
+/*       image url()'s in CSS rules with data uri's.                    */
+/*                                                                      */
+/*     - <link rel="stylesheet" href="..."> element: handle @import     */
+/*       rules, then replace font and image url()'s in CSS rules        */
+/*       with data uri's, then enclose in new <style> element and       */
+/*       replace original <link> element.                               */
+/*                                                                      */
+/*     - <link rel="icon" href="..."> element: convert icon to data     */
+/*       uri and use this to replace url in href attribute.             */
+/*                                                                      */
+/*     - <base href="..." target="..."> element: remove existing        */
+/*       base element (if any) and insert new base element with href    */
+/*       attribute set to document.baseURI and target attribute set     */
+/*       to the same value as for removed base element (if any).        */
+/*                                                                      */
+/*     - <body background="..."> element: convert image to data uri     */
+/*       and use this to replace url in background attribute.           */
+/*                                                                      */
+/*     - <img src="..."> element: convert image to data uri and use     */
+/*       this to replace url in src attribute.                          */
+/*                                                                      */
+/*     - <img srcset="..."> element: replace list of images by "".      */
+/*                                                                      */
+/*     - <input type="image" src="..."> element: convert image to       */
+/*       data uri and use this to replace url in src attribute.         */
+/*                                                                      */
+/*     - <input type="file"> or <input type="password"> element:        */
+/*       no changes made to maintain security.                          */
+/*                                                                      */
+/*     - <input type="checkbox"> or <input type="radio"> element:       */
+/*       add or remove checked attribute depending on the value of      */
+/*       element.checked reflecting any user changes.                   */
+/*                                                                      */
+/*     - <input type="-other-"> element: add value attribute set to     */
+/*       element.value reflecting any user changes.                     */
+/*                                                                      */
+/*     - <audio src="..."> element: if current source, convert audio    */
+/*       to data uri and use this to replace url in src attribute.      */
+/*                                                                      */
+/*     - <video src="..."> element: if current source, convert video    */
+/*       to data uri and use this to replace url in src attribute.      */
+/*                                                                      */
+/*     - <video poster="..."> element: convert image to data uri and    */
+/*       use this to replace url in poster attribute.                   */
+/*                                                                      */
+/*     - <source src="..."> element: if current source, convert audio   */
+/*       or video to data uri and use this to replace url in src        */
+/*       attribute.                                                     */
+/*                                                                      */
+/*     - <track src="..."> element: convert subtitles to data uri and   */
+/*       use this to replace url in src attribute.                      */
+/*                                                                      */
+/*     - <object data="..."> element: convert binary data to data uri   */
+/*       and use these to replace url in data attribute.                */
+/*                                                                      */
+/*     - <embed src="..."> element: convert binary data to data uri     */
+/*       and use this to replace url in src attribute.                  */
+/*                                                                      */
+/*     - <frame src="..."> or <iframe src="..."> element: process       */
+/*       sub-tree to extract HTML, then convert HTML to data uri and    */
+/*       use this to replace url in src attribute.                      */
+/*                                                                      */
+/*     - other elements: process child nodes to extract HTML.           */
+/*                                                                      */
+/*     - text nodes: escape '<' and '>' characters.                     */
+/*                                                                      */
+/*     - comment nodes: enclose within <!-- and  -->                    */
+/*                                                                      */
+/*  8. Data URI syntax and defaults:                                    */
+/*                                                                      */
+/*     - data:[<media type>][;base64],<encoded data>                    */
+/*                                                                      */
+/*     - where <media type> is: <mime type>[;charset=<charset>]         */
+/*                                                                      */
+/*     - default for text content: text/plain;charset=US-ASCII          */
+/*                                                                      */
+/*     - default for binary content: application/octet-stream;base64    */
+/*                                                                      */
+/************************************************************************/
+
+/************************************************************************/
+/*                                                                      */
+/*  Potential Improvements                                              */
+/*                                                                      */
+/*  1. The main document and <frame> and <iframe> documents could be    */
+/*     downloaded and scanned to extract the original states of the     */
+/*     HTML elements, as an alternative to the current states.          */
+/*                                                                      */
+/*  2. <script src="..."> element could be converted to <script>        */
+/*     element to avoid data uri in href attribute, which would also    */
+/*     avoid using encodeURIComponent(), but any 'async' or 'defer'     */
+/*     attributes would be lost and the order of execution of scripts   */
+/*     could change.                                                    */
+/*                                                                      */
+/************************************************************************/
+
+/************************************************************************/
+/*                                                                      */
+/*  Handling of URL's in HTML Attributes                                */
+/*                                                                      */
+/*  Element         Attribute     HTML   Content        Handling        */
+/*                                                                      */
+/*  <a>             href          4 5    -              -               */
+/*  <applet>        codebase      4      java           -               */
+/*  <area>          href          4 5    -              -               */
+/*  <audio>         src             5    audio          data uri   (1)  */
+/*  <base>          href          4 5    -              -               */
+/*  <blockquote>    cite          4 5    info           -               */
+/*  <body>          background    4      image          data uri        */
+/*  <button>        formaction      5    -              -               */
+/*  <del>           cite          4 5    info           -               */
+/*  <embed>         src             5    data           data uri        */
+/*  <form>          action        4 5    -              -               */
+/*  <frame>         longdesc      4      info           -               */
+/*  <frame>         src           4      html           data uri        */
+/*  <head>          profile       4      metadata       -               */
+/*  <html>          manifest        5    -              -               */
+/*  <iframe>        longdesc      4      info           -               */
+/*  <iframe>        src           4 5    html           data uri        */
+/*  <img>           longdesc      4      info           -               */
+/*  <img>           src           4 5    image          data uri   (2)  */
+/*  <img>           srcset          5    images         -          (2)  */
+/*  <input>         formaction      5    -              -               */
+/*  <input>         src           4 5    image          data uri        */
+/*  <ins>           cite          4 5    info           -               */
+/*  <link>          href          4 5    css            style      (3)  */
+/*  <link>          href          4 5    icon           data uri        */
+/*  <object>        archive       4      -              -               */
+/*  <object>        classid       4      -              -               */
+/*  <object>        codebase      4      -              -               */
+/*  <object>        data          4 5    data           data uri        */
+/*  <q>             cite          4 5    info           -               */
+/*  <script>        src           4 5    javscript      data uri        */
+/*  <source>        src             5    audio/video    data uri   (1)  */
+/*  <track>         src             5    audio/video    data uri        */
+/*  <video>         poster          5    image          data uri        */
+/*  <video>         src             5    video          data uri   (1)  */
+/*                                                                      */
+/*  Notes:                                                              */
+/*                                                                      */
+/*  (1) data uri is created only if the URL in the 'src' attribute      */
+/*      is the same as the URL in element.currentSrc of the related     */
+/*      <audio> or <video> element.                                     */
+/*                                                                      */
+/*  (2) data uri is created for the URL in the 'src' attribute, but     */
+/*      not for URL's in the 'srcset' attribute.                        */
+/*                                                                      */
+/*  (3) replace <link> element with <style> element containing the      */
+/*      style sheet referred to by the URL in the 'href' attribute.     */
+/*                                                                      */
+/************************************************************************/
+
+/************************************************************************/
+/*                                                                      */
+/*  Handling of Binary Data and Characters                              */
+/*                                                                      */
+/*  1. Files downloaded by XMLHttpRequest GET request are received      */
+/*     as a Uint8Array (8-bit unsigned integers) representing:          */
+/*     - either binary data (image, font, audio or video)               */
+/*     - or encoded characters (style sheets or scripts)                */
+/*                                                                      */
+/*  2. The Uint8Array is then converted to a Javascript string          */
+/*     (16-bit unsigned integers) containing 8-bit unsigned values      */
+/*     (a binary string) which is sent to the content script.           */
+/*                                                                      */
+/*  3. A binary string containing binary data is copied directly        */
+/*     into the resourceContent store.                                  */
+/*                                                                      */
+/*  4. A binary string containing UTF-8 characters is converted to      */
+/*     a normal Javascript string (containing UTF-16 characters)        */
+/*     before being copied into the resourceContent store.              */
+/*                                                                      */
+/*  5. A binary string containing non-UTF-8 (ASCII, ANSI, ISO-8859-1)   */
+/*     characters is copied directly into the resourceContent store.    */
+/*                                                                      */
+/*  6. When creating a Base64 data uri, the binary string from the      */
+/*     resourceContent store is converted to a Base64 ASCII string      */
+/*     using btoa().                                                    */
+/*                                                                      */
+/*  7. When creating a UTF-8 data uri, the UTF-16 string from the       */
+/*     resourceContent store is converted to a UTF-8 %-escaped          */
+/*     string using encodeURIComponent(). The following characters      */
+/*     are not %-escaped: alphabetic, digits, - _ . ! ~ * ' ( )         */
+/*                                                                      */
+/*  8. Character encodings are determined as follows:                   */
+/*     - UTF-8 Byte Order Mark (BOM) at the start of a text file        */
+/*     - charset parameter in the HTTP Content-Type header field        */
+/*     - @charset rule at the start of a style sheet                    */
+/*     - charset attribute on an element referencing a text file        */
+/*     - charset encoding of the parent document or style sheet         */
+/*                                                                      */
+/************************************************************************/
+
+"use strict";
+
+/************************************************************************/
+
+/* Global variables */
+
+var isFirefox;
+var ffVersion;
+
+var buttonAction;
+var showSubmenu;
+
+var badgeTabId;
+
+/************************************************************************/
+
+/* Initialize on browser startup */
+
+isFirefox = (navigator.userAgent.indexOf("Firefox") >= 0);
+
+chrome.storage.local.set({ "environment-isfirefox": isFirefox });
+
+if (isFirefox)
+{
+    chrome.runtime.getBrowserInfo(
+    function(info)
+    {
+        ffVersion = info.version.substr(0,info.version.indexOf("."));
+        
+        initialize();
+    });
+}
+else initialize();
+
+function initialize()
+{
+    chrome.storage.local.get(null,
+    function(object)
+    {
+        var context;
+        
+        /* Initialize or migrate options */
+        
+        if (!("options-buttonaction" in object)) object["options-buttonaction"] =
+            ("options-savebuttonaction" in object) ? object["options-savebuttonaction"] : 0;  /* Version 2.0-2.1 */
+        
+        if (!("options-showsubmenu" in object)) object["options-showsubmenu"] =
+            ("options-showmenuitem" in object) ? object["options-showmenuitem"] : true;  /* Version 3.0-5.0 */
+        
+        if (!("options-showwarning" in object)) object["options-showwarning"] = true;
+        
+        if (!("options-showurllist" in object)) object["options-showurllist"] = false;
+        
+        if (!("options-prefixfilename" in object)) object["options-prefixfilename"] = false;
+        
+        if (!("options-suffixfilename" in object)) object["options-suffixfilename"] = false;
+        
+        if (!("options-usepageloader" in object)) object["options-usepageloader"] = true;
+        
+        if (!("options-removeunsavedurls" in object)) object["options-removeunsavedurls"] = true;
+        
+        if (!("options-includeinfobar" in object)) object["options-includeinfobar"] =
+            ("options-includenotification" in object) ? object["options-includenotification"] : false;  /* Version 7.4 */
+        
+        
+        if (!("options-includesummary" in object)) object["options-includesummary"] = false;
+        
+        if (!("options-savehtmlaudiovideo" in object)) object["options-savehtmlaudiovideo"] = false;
+        
+        if (!("options-savehtmlobjectembed" in object)) object["options-savehtmlobjectembed"] = false;
+        
+        if (!("options-savehtmlimagesall" in object)) object["options-savehtmlimagesall"] =
+            ("options-saveallhtmlimages" in object) ? object["options-saveallhtmlimages"] : false;  /* Version 2.0-3.0 */
+        
+        if (!("options-savecssimagesall" in object)) object["options-savecssimagesall"] =
+            ("options-saveallcssimages" in object) ? object["options-saveallcssimages"] : false;  /* Version 2.0-3.0 */
+        
+        if (!("options-savecssfontswoff" in object)) object["options-savecssfontswoff"] =
+            ("options-saveallcustomfonts" in object) ? object["options-saveallcustomfonts"] : false;  /* Version 2.0-3.0 */
+        
+        if (!("options-savescripts" in object)) object["options-savescripts"] =
+            ("options-saveallscripts" in object) ? object["options-saveallscripts"] : false;  /* Version 2.0-3.0 */
+        
+        if (!("options-maxframedepth" in object)) object["options-maxframedepth"] =
+            ("options-saveframedepth" in object) ? object["options-saveframedepth"] : 2;  /* Version 2.0-2.1 */
+        
+        if (!("options-maxresourcesize" in object)) object["options-maxresourcesize"] = 50;
+        
+        /* Update stored options */
+        
+        chrome.storage.local.set(object);
+        
+        /* Initialize local options */
+        
+        buttonAction = object["options-buttonaction"];
+        
+        showSubmenu = object["options-showsubmenu"];
+        
+        /* Add context menu items */
+        
+        context = showSubmenu ? "all" : "browser_action";
+        
+        chrome.contextMenus.create({ id: "currentstate", title: "Save Current State", contexts: [ context ], enabled: true });
+        chrome.contextMenus.create({ id: "chosenitems", title: "Save Chosen Items", contexts: [ context ], enabled: true });
+        chrome.contextMenus.create({ id: "completepage", title: "Save Complete Page", contexts: [ context ], enabled: true });
+        chrome.contextMenus.create({ id: "separator", type: "separator", contexts: [ context ], enabled: true });
+        chrome.contextMenus.create({ id: "viewpageinfo", title: "View Saved Page Info", contexts: [ context ], enabled: true });
+        chrome.contextMenus.create({ id: "removepageloader", title: "Remove Page Loader", contexts: [ context ], enabled: true });
+        chrome.contextMenus.create({ id: "extractmedia", title: "Extract Image/Audio/Video", contexts: [ "image", "audio", "video" ], enabled: true });
+        
+        /* Set button and menu states */
+        
+        chrome.tabs.query({ lastFocusedWindow: true, active: true },
+        function(tabs)
+        {
+            setButtonAndMenuStates(tabs[0].id,tabs[0].url);
+        });
+        
+        /* Add listeners */
+        
+        addListeners();
+    });
+}
+
+/************************************************************************/
+
+/* Add listeners */
+
+function addListeners()
+{
+    /* Storage changed listener */
+    
+    chrome.storage.onChanged.addListener(
+    function(changes,areaName)
+    {
+        chrome.storage.local.get(null,
+        function(object)
+        {
+            var context;
+            
+            buttonAction = object["options-buttonaction"];
+            
+            showSubmenu = object["options-showsubmenu"];
+            
+            if ("options-showsubmenu" in changes)
+            {
+                context = showSubmenu ? "all" : "browser_action";
+                
+                chrome.contextMenus.update("currentstate",{ contexts: [ context ] });
+                chrome.contextMenus.update("chosenitems",{ contexts: [ context ] });
+                chrome.contextMenus.update("completepage",{ contexts: [ context ] });
+                chrome.contextMenus.update("separator", { contexts: [ context ] });
+                chrome.contextMenus.update("viewpageinfo", { contexts: [ context ] });
+                chrome.contextMenus.update("removepageloader", { contexts: [ context ] });
+                chrome.contextMenus.update("extractmedia", { contexts: [ "image", "audio", "video" ] });
+                
+                chrome.tabs.query({ lastFocusedWindow: true, active: true },
+                function(tabs)
+                {
+                    setButtonAndMenuStates(tabs[0].id,tabs[0].url);
+                });
+            }
+        });
+    });
+    
+    /* Browser action listener */
+    
+    chrome.browserAction.onClicked.addListener(
+    function(tab)
+    {
+        initiateAction(tab,buttonAction);
+    });
+    
+    /* Context menu listener */
+    
+    chrome.contextMenus.onClicked.addListener(
+    function(info,tab)
+    {
+        if (info.menuItemId == "currentstate") initiateAction(tab,0,null);
+        else if (info.menuItemId == "chosenitems") initiateAction(tab,1,null);
+        else if (info.menuItemId == "completepage") initiateAction(tab,2,null);
+        else if (info.menuItemId == "viewpageinfo") initiateAction(tab,3,null);
+        else if (info.menuItemId == "removepageloader") initiateAction(tab,4,null);
+        else if (info.menuItemId == "extractmedia") initiateAction(tab,5,info.srcUrl);
+    });
+    
+    /* Tab event listeners */
+    
+    chrome.tabs.onActivated.addListener(  /* tab selected */
+    function(activeInfo)
+    {
+        chrome.tabs.get(activeInfo.tabId,
+        function(tab)
+        {
+            setButtonAndMenuStates(activeInfo.tabId,tab.url);
+        });
+    });
+    
+    chrome.tabs.onUpdated.addListener(  /* URL updated */
+    function(tabId,changeInfo,tab)
+    {
+        setButtonAndMenuStates(tabId,tab.url);
+    });
+    
+    /* Web navigation listeners */
+    
+    chrome.webNavigation.onCompleted.addListener(  /* page loaded or (Firefox) extracted resource downloaded */
+    function(details)
+    {
+        if (details.frameId == 0)
+        {
+            /* If triggered by Extract Image/Audio/Video, details.url */
+            /* will contain resource URL, so need to get page URL */ 
+            
+            chrome.tabs.get(details.tabId,
+            function(tab)
+            {
+                setButtonAndMenuStates(details.tabId,tab.url);
+                
+                setSaveBadge("","#000000");
+            });
+        }
+    });
+    
+    /* Message received listener */
+    
+    chrome.runtime.onMessage.addListener(
+    function(message,sender,sendResponse)
+    {
+        var xhr = new Object();
+        
+        /* Messages from content script */
+        
+        switch (message.type)
+        {
+            case "downloadFile":
+            {
+                console.log("SAVEPAGE: Downloading " + message.location + " to " + message.filename);
+                var downloading =  browser.downloads.download({filename: message.filename, url: message.location, saveAs: false });
+                downloading.then(function() {console.log("Download started");}, function(reason) {console.log(reason);});
+            }
+            break;
+            
+            case "loadResource":
+                
+                if (message.location.substr(0,6) == "https:" ||
+                    (message.location.substr(0,5) == "http:" && message.pagescheme == "http:"))  /* block https: resource within http: page */
+                { 
+                    try
+                    {
+                        xhr = new XMLHttpRequest();
+                        
+                        xhr.open("GET",message.location,true);
+                        xhr.setRequestHeader("Cache-Control","no-store");
+                        xhr.responseType = "arraybuffer";
+                        xhr.timeout = 10000;
+                        xhr._tabId = sender.tab.id;
+                        xhr._resourceIndex = message.index;
+                        xhr.onload = onloadResource;
+                        xhr.onerror = onerrorResource;
+                        xhr.ontimeout = ontimeoutResource;
+                        
+                        xhr.send();  /* throws exception if url is invalid */
+                    }
+                    catch(e)
+                    {
+                        chrome.tabs.sendMessage(sender.tab.id,{ type: "loadFailure", index: message.index });
+                    }
+                }
+                else chrome.tabs.sendMessage(sender.tab.id,{ type: "loadFailure", index: message.index });
+                
+                function onloadResource()
+                {
+                    var i,binaryString,contentType,allowOrigin;
+                    var byteArray = new Uint8Array(this.response);
+                    
+                    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 = "";
+                        
+                        allowOrigin = this.getResponseHeader("Access-Control-Allow-Origin");
+                        if (allowOrigin == null) allowOrigin = "";
+                        
+                        chrome.tabs.sendMessage(this._tabId,{ type: "loadSuccess", index: this._resourceIndex, content: binaryString, contenttype: contentType, alloworigin: allowOrigin });
+                    }
+                    else chrome.tabs.sendMessage(this._tabId,{ type: "loadFailure", index: this._resourceIndex });
+                }
+                
+                function onerrorResource()
+                {
+                    chrome.tabs.sendMessage(this._tabId,{ type: "loadFailure", index: this._resourceIndex });
+                }
+                
+                function ontimeoutResource()
+                {
+                    chrome.tabs.sendMessage(this._tabId,{ type: "loadFailure", index: this._resourceIndex });
+                }
+                
+                break;
+                
+            case "setSaveBadge":
+                
+                setSaveBadge(message.text,message.color);
+                
+                break;
+        }
+    });
+}
+
+/************************************************************************/
+
+/* Initiate action function */
+
+function initiateAction(tab,menuaction,srcurl)
+{
+    if (specialPage(tab.url))  /* special page - no operations allowed */
+    {
+        alertNotify("Cannot be used with these special pages:\n" +
+                    "about:, moz-extension:,\n" +
+                    "https://addons.mozilla.org,\n" +
+                    "chrome:, chrome-extension:,\n" +
+                    "https://chrome.google.com/webstore.");
+    }
+    else if (tab.url.substr(0,5) != "file:" && menuaction >= 3)  /* probably not saved page - view saved page info and extract media operations not allowed */
+    {
+        alertNotify("Cannot view saved page information or extract media files for unsaved pages.");
+    }
+    else  /* normal page - save operations allowed, saved page - all operations allowed */
+    {
+        badgeTabId = tab.id;
+        
+        chrome.tabs.sendMessage(tab.id,{ type: "performAction", menuaction: menuaction, srcurl: srcurl },
+        function(response)
+        {
+            if (chrome.runtime.lastError != null || typeof response == "undefined")  /* no response received - content script not loaded in active tab */
+            {
+                chrome.tabs.executeScript(tab.id,{ file: "content.js" },
+                function()
+                {
+                    chrome.tabs.sendMessage(tab.id,{ type: "performAction", menuaction: menuaction, srcurl: srcurl },
+                    function(response)
+                    {
+                        if (chrome.runtime.lastError != null || typeof response == "undefined")  /* no response received - content script cannot be loaded in active tab*/
+                        {
+                            alertNotify("Cannot be used with this page.");
+                        }
+                    });
+                });
+            }
+        });
+    }
+}
+
+/************************************************************************/
+
+/* Special page function */
+
+function specialPage(url)
+{
+    return (url.substr(0,6) == "about:" || url.substr(0,14) == "moz-extension:" || url.substr(0,26) == "https://addons.mozilla.org" ||
+            url.substr(0,7) == "chrome:" || url.substr(0,17) == "chrome-extension:" || url.substr(0,34) == "https://chrome.google.com/webstore");
+}
+
+/************************************************************************/
+
+/* Set button and menu states function */
+
+function setButtonAndMenuStates(tabId,url)
+{
+    if (specialPage(url))  /* special page - disable all operations */
+    {
+        chrome.browserAction.disable(tabId);
+        
+        if (isFirefox && ffVersion <= 54) chrome.browserAction.setIcon({ tabId: tabId, path: "icon16-disabled.png"});  /* Firefox 54- - icon not changed */
+        
+        chrome.browserAction.setTitle({ tabId: tabId, title: "Save Page WE - cannot be used with this page" });
+        
+        chrome.contextMenus.update("currentstate",{ enabled: false });
+        chrome.contextMenus.update("chosenitems",{ enabled: false });
+        chrome.contextMenus.update("completepage",{ enabled: false });
+        chrome.contextMenus.update("separator", { enabled: true });
+        chrome.contextMenus.update("viewpageinfo", { enabled: false });
+        chrome.contextMenus.update("removepageloader", { enabled: false });
+        if (showSubmenu) chrome.contextMenus.update("extractmedia", { contexts: [ "image", "audio", "video" ], enabled: false });
+        else chrome.contextMenus.update("extractmedia", { contexts: [ "page_action" ], enabled: false });  /* never shown because there is no page action */
+    }
+    else if (url.substr(0,5) == "file:")  /* probably saved page - enable all operations */
+    {
+        chrome.browserAction.enable(tabId);
+        
+        if (isFirefox && ffVersion <= 54) chrome.browserAction.setIcon({ tabId: tabId, path: "icon16.png"});  /* Firefox 54- - icon not changed */
+        
+        chrome.browserAction.setTitle({ tabId: tabId, title: "Save Page WE" });
+        
+        chrome.contextMenus.update("currentstate",{ enabled: true });
+        chrome.contextMenus.update("chosenitems",{ enabled: true });
+        chrome.contextMenus.update("completepage",{ enabled: true });
+        chrome.contextMenus.update("separator", { enabled: true });
+        chrome.contextMenus.update("viewpageinfo", { enabled: true });
+        chrome.contextMenus.update("removepageloader", { enabled: true });
+        chrome.contextMenus.update("extractmedia", { contexts: [ "image", "audio", "video" ], enabled: true });
+    }
+    else  /* normal page - enable save operations */
+    {
+        chrome.browserAction.enable(tabId);
+        
+        if (isFirefox && ffVersion <= 54) chrome.browserAction.setIcon({ tabId: tabId, path: "icon16.png"});  /* Firefox 54- - icon not changed */
+        
+        chrome.browserAction.setTitle({ tabId: tabId, title: "Save Page WE" });
+        
+        chrome.contextMenus.update("currentstate",{ enabled: true });
+        chrome.contextMenus.update("chosenitems",{ enabled: true });
+        chrome.contextMenus.update("completepage",{ enabled: true });
+        chrome.contextMenus.update("separator", { enabled: true });
+        chrome.contextMenus.update("viewpageinfo", { enabled: false });
+        chrome.contextMenus.update("removepageloader", { enabled: false });
+        if (showSubmenu) chrome.contextMenus.update("extractmedia", { contexts: [ "image", "audio", "video" ], enabled: false });
+        else chrome.contextMenus.update("extractmedia", { contexts: [ "page_action" ], enabled: false });  /* never shown because there is no page action */
+    }
+}
+
+/************************************************************************/
+
+/* Set save badge function */
+
+function setSaveBadge(text,color)
+{
+    chrome.browserAction.setBadgeText({ tabId: badgeTabId, text: text });
+    chrome.browserAction.setBadgeBackgroundColor({ tabId: badgeTabId, color: color });
+}
+
+/************************************************************************/
+
+/* Display alert notification */
+
+function alertNotify(message)
+{
+    chrome.notifications.create("alert",{ type: "basic", iconUrl: "icon32.png", title: "SAVE PAGE WE", message: "" + message });
+}
+
+/************************************************************************/
+
+/* Display debug notification */
+
+function debugNotify(message)
+{
+    chrome.notifications.create("debug",{ type: "basic", iconUrl: "icon32.png", title: "SAVE PAGE WE - DEBUG", message: "" + message });
+}
+
+function debugModal(message)
+{
+    chrome.tabs.query({ lastFocusedWindow: true, active: true },
+    function(tabs)
+    {
+        chrome.tabs.executeScript(tabs[0].id,{ code: "alert('" + message + "');" });
+    });
+}
+
+/************************************************************************/