diff --git a/bomViewer/bomViewer.js b/bomViewer/bomViewer.js index 9ba44e1..b1e366a 100644 --- a/bomViewer/bomViewer.js +++ b/bomViewer/bomViewer.js @@ -1,28 +1,54 @@ $(function() { $("#bomTree").fancytree({ source: [ - {title: "window", key: "window", folder: true, lazy: true, data: { obj: window }} + {title: "window", key: "window", folder: true, lazy: true, data: { obj: window }, icon: "images/ClassIcon.png"} ], checkbox: false, lazyLoad: function(event, data) { var node = data.node; var object = node.data.obj; var result = []; - for(property in object) { - var propertyType = typeof object[property]; - switch(propertyType) { - case "object": - result.push({title: property, key: node.key + "." + property, folder: true, lazy: true, data: { obj : object[property]}}); - break; - case "function": - var func = object[property].toString(); - if(func.indexOf("function (") == 0) - func = func.substring(0, 9) + property + func.substring(9); - result.push({title: func, key: node.key + "." + property, folder: false, lazy: false}); - break; - default: - result.push({title: property + " = '" + object[property] + "'", key: node.key + "." + property, folder: false, lazy: false}); - break; + if(object != null) { + + if(typeof object.constructor != 'undefined' && object.constructor !== object) { + var func = "constructor: " + object.constructor.toString(); + result.push({title: func, key: node.key + ".constructor", folder: true, lazy: true, data: {obj: object.constructor}, icon: "images/Function_8941.png"}); + } + + var propertyNames = Object.getOwnPropertyNames(object); + for(var propertyIndex = 0; propertyIndex < propertyNames.length; propertyIndex++) { + var propertyName = propertyNames[propertyIndex]; + if(propertyName == "constructor") continue; + var propertyType = typeof object[propertyName]; + var propertyDescriptor = Object.getOwnPropertyDescriptor(object, propertyName); + var enumerable = (typeof propertyDescriptor.enumerable != 'undefined' && propertyDescriptor.enumerable == true); + var child = { + title: enumerable ? "" + propertyName + "" : "" + propertyName + "", + key: node.key + "." + propertyName, + data: {obj: object[propertyName]}, + } + switch(propertyType) { + case "object": + child.title += ": " + object[propertyName]; + child.icon = "images/ClassIcon.png"; + child.folder = object[propertyName] != null; + child.lazy = object[propertyName] != null; + break; + case "function": + child.title += ": " + object[propertyName].toString(); + child.folder = true; + child.lazy = true; + child.icon = "images/Function_8941.png"; + break; + case "string": + child.title += ": '" + object[propertyName] + "'"; + break; + default: + child.title += ": " + object[propertyName]; + child.icon = "images/PropertyIcon.png"; + break; + } + result.push(child); } } console.log("Adding " + result.length + " children to " + node.key); diff --git a/bomViewer/images/ClassIcon.png b/bomViewer/images/ClassIcon.png new file mode 100644 index 0000000..5202f2d Binary files /dev/null and b/bomViewer/images/ClassIcon.png differ diff --git a/bomViewer/images/Function_8941.png b/bomViewer/images/Function_8941.png new file mode 100644 index 0000000..0c25e25 Binary files /dev/null and b/bomViewer/images/Function_8941.png differ diff --git a/bomViewer/images/PropertyIcon.png b/bomViewer/images/PropertyIcon.png new file mode 100644 index 0000000..e105be8 Binary files /dev/null and b/bomViewer/images/PropertyIcon.png differ diff --git a/js/background.js b/js/background.js index e9b641a..93842f1 100644 --- a/js/background.js +++ b/js/background.js @@ -1,3 +1,7 @@ +console.log("RubberGlove: Persisting settings"); +persistConfig("persistedSettings.js", {local:{enabled:true, verbose:true}}); +console.log("RubberGlove: Settings stored at " + getWebStorageUri("persistedSettings.js")); + function resetBadgeCounter(tabId) { localStorage['RubberGlove_BlockCount_' + tabId] = 0; chrome.browserAction.setBadgeBackgroundColor({ color: [255, 0, 0, 255] }); diff --git a/js/bomOverload.js b/js/bomOverload.js index c79a3d6..ac56e81 100644 --- a/js/bomOverload.js +++ b/js/bomOverload.js @@ -1,165 +1,258 @@ -var bomOverloadFunction = function() { +function bomOverload() { + if(config.local.verbose) console.log("RubberGlove: Creating PluginArray"); + function PluginArray() { // native(PluginArray) + if(window.navigator.plugins.constructor === PluginArray) + throw new TypeError("Illegal constructor"); + + if(config.local.verbose) console.log("RubberGlove: Creating PluginArray instance"); + + Object.defineProperty(this, 'length', { + enumerable: true, + get: (function(eventNode) { + return function() { + // native() + console.error('RubberGlove: Iteration of window.navigator.plugins blocked for ' + window.location.href + ' (Informational, not an error.)'); + window.postMessage({ + type: 'RubberGlove', + text: 'window.navigator.plugins', + url: window.location.href + }, '*'); + return 0; + }; + })(document.currentScript.parentNode) + }); + + // Add hidden named plugins + var plugins = window.navigator.plugins; + for(var i = 0; i < plugins.length; i++) { + var plugin = plugins[i]; + if(typeof plugin != 'undefined' && typeof plugin.name != 'undefined' && plugin.name != null) { + Object.defineProperty(this, plugin.name, { + configurable: true, + value: plugin + }); + } + } + } + // Don't ask me why the real function has this... It's not even a prototype. + PluginArray.toString = function toString() { // native(toString) + return Function.prototype.toString.apply(this, Array.prototype.slice.apply(arguments)); + }; + if(config.local.verbose) console.log("RubberGlove: Creating PluginArray.prototype.item()"); + PluginArray.prototype.item = function item() { // native(item) + return this[arguments[0]]; + }; + if(config.local.verbose) console.log("RubberGlove: Creating PluginArray.prototype.namedItem()"); + PluginArray.prototype.namedItem = function namedItem() { // native(namedItem) + return this[arguments[0]]; + }; + if(config.local.verbose) console.log("RubberGlove: Creating PluginArray.prototype.refresh()"); + PluginArray.prototype.refresh = (function(plugins) { + if(config.local.verbose) console.log("RubberGlove: Returning our custom PluginArray.refresh()"); + return function refresh() { // native(refresh) + // Refresh the real plugins list + plugins.refresh.apply(plugins, Array.prototype.slice.apply(arguments)); + + // Delete our existing set of plugins + var propertyNames = Object.getOwnPropertyNames(this); + for(var i = 0; i < propertyNames.length; i++) { + var property = propertyNames[i]; + if(property != 'length') delete this[property]; + } + + // Add hidden named plugins + for(var i = 0; i < plugins.length; i++) { + var plugin = plugins[i]; + if(typeof plugin.name != 'undefined' && plugin.name != null) { + Object.defineProperty(this, plugin.name, { + configurable: true, + value: plugin + }); + } + } + } + })(window.navigator.plugins); + if(config.local.verbose) console.log("RubberGlove: Replacing window.PluginArray"); + Object.defineProperty(window, 'PluginArray', { + enumerable: false, + configurable: false, + writable: true, + value: PluginArray + }); + + // TODO: This should refresh as well when PluginArray.refresh() is called. + if(config.local.verbose) console.log("RubberGlove: Creating MimeTypeArray"); + function MimeTypeArray() { // native(MimeTypeArray) + if(window.navigator.mimeTypes.constructor === MimeTypeArray) + throw new TypeError("Illegal constructor"); + + if(config.local.verbose) console.log("RubberGlove: Creating MimeTypeArray instance"); + + Object.defineProperty(this, 'length', { + enumerable: true, + get: (function(eventNode) { + return function() { + // native() + console.error('RubberGlove: Iteration of window.navigator.mimeTypes blocked for ' + window.location.href + ' (Informational, not an error.)'); + window.postMessage({ + type: 'RubberGlove', + text: 'window.navigator.mimeTypes', + url: window.location.href + }, '*'); + return 0; + }; + })(document.currentScript.parentNode) + }); + + // Add hidden named mimeTypes + var mimeTypes = window.navigator.mimeTypes; + for(var i = 0; i < mimeTypes.length; i++) { + var mimeType = mimeTypes[i]; + if(typeof mimeType != 'undefined' && typeof mimeType.type != 'undefined' && mimeType.type != null) { + Object.defineProperty(this, mimeType.type, { + configurable: true, + value: mimeType + }); + } + } + } + // Don't ask me why the real function has this... It's not even a prototype. + MimeTypeArray.toString = function toString() { // native(toString) + return Function.prototype.toString.apply(this, Array.prototype.slice.apply(arguments)); + }; + // Yes, these duplicate the ones for PluginArray. No, they should + // not use the same functions as they shouldn't test as equal. + if(config.local.verbose) console.log("RubberGlove: Creating MimeTypeArray.prototype.item()"); + MimeTypeArray.prototype.item = function item(index) { // native(item) + return this[arguments[0]]; + }; + if(config.local.verbose) console.log("RubberGlove: Creating MimeTypeArray.prototype.namedItem()"); + MimeTypeArray.prototype.namedItem = function namedItem(name) { // native(namedItem) + return this[arguments[0]]; + }; + if(config.local.verbose) console.log("RubberGlove: Replacing window.MimeTypeArray"); + Object.defineProperty(window, 'MimeTypeArray', { + enumerable: false, + configurable: false, + writable: true, + value: MimeTypeArray + }); + + if(config.local.verbose) console.log("RubberGlove: Creating Navigator"); + function Navigator() { // native(Navigator) + if(window.navigator.constructor === Navigator) + throw new TypeError("Illegal constructor"); + + if(config.local.verbose) console.log("RubberGlove: Creating Navigator instance"); + + var propertyNames = Object.getOwnPropertyNames(window.navigator); + for(var propertyIndex = 0; propertyIndex < propertyNames.length; propertyIndex++) { + var propertyName = propertyNames[propertyIndex]; + var descriptor = Object.getOwnPropertyDescriptor(window.navigator, propertyName); + var writable = descriptor.writable == true || typeof descriptor.set == 'function'; + + delete descriptor.value; + delete descriptor.get; + delete descriptor.set; + delete descriptor.writable; + + switch(propertyName) { + case 'plugins': + console.log('RubberGlove: Cloaking plugins for ' + window.location.href); + descriptor.value = new PluginArray(); + break; + case 'mimeTypes': + console.log('RubberGlove: Cloaking mimeTypes for ' + window.location.href); + descriptor.value = new MimeTypeArray(); + break; + default: + //console.log("RubberGlove: wrapping " + propertyName); + descriptor.get = (function(propertyName, navigator) { + return function() { /* native() */ return navigator[propertyName] }; + })(propertyName, window.navigator); + if(writable) { + descriptor.set = (function(propertyName, navigator) { + return function(value) { /* native(item) */ navigator[propertyName] = value; }; + })(propertyName, window.navigator); + } + break; + } + Object.defineProperty(this, propertyName, descriptor); + } + } + // Don't ask me why the real function has this... It's not even a prototype. + Navigator.toString = function toString() { // native(toString) + return Function.prototype.toString.apply(this, Array.prototype.slice.apply(arguments)); + }; + if(config.local.verbose) console.log("RubberGlove: Replacing Navigator.prototype"); + for(var property in window.Navigator.prototype) { + Navigator.prototype[property] = window.Navigator.prototype[property]; + } + if(config.local.verbose) console.log("RubberGlove: Replacing window.Navigator"); + Object.defineProperty(window, 'Navigator', { + enumerable: false, + configurable: false, + writable: true, + value: Navigator + }); + + if(config.local.verbose) console.log("RubberGlove: Constructing Navigator"); + var navigatorProxy = new Navigator(); + if(config.local.verbose) console.log("RubberGlove: Replacing window.navigator"); + Object.defineProperty(window, 'navigator', { + enumerable: true, + configurable: false, + writable: true, + value: navigatorProxy + }); + if(config.local.verbose) console.log("RubberGlove: Replacing window.clientInformation"); + Object.defineProperty(window, 'clientInformation', { + enumerable: true, + configurable: false, + writable: true, + value: navigatorProxy + }); + // Hides source code when it contains "// native(functionName)" or // "/* native(functionName)" at the beginning of the function body. - Function.prototype.toString = (function() { - var toString = Function.prototype.toString; - return function(thisArg, argsArray) { - // native(toString) <-- yes, it handles itself - var result = toString.apply(this, Array.prototype.slice.apply(arguments)); + if(config.local.verbose) console.log("RubberGlove: Replacing Function.prototype.toString()"); + Function.prototype.toString = (function(oldToString) { + return function toString() { // native(toString) <-- yes, it handles itself + var result = oldToString.apply(this, Array.prototype.slice.apply(arguments)); var match = result.match(/^\s*?function.*?\(.*?\)\s*?{\s*?\/[\*\/]\s*?native\((.*?)\)/); if(match != null && match.length > 1) return 'function ' + match[1] + '() { [native code] }'; return result; }; - })(); + })(Function.prototype.toString); - var navWrapper = (function() { - var oldNavigator = navigator; - var altNav = {}; - var propertyNames = Object.getOwnPropertyNames(oldNavigator); - for(var propertyIndex = 0; propertyIndex < propertyNames.length; propertyIndex++) { - propertyName = propertyNames[propertyIndex]; - - // Get the Property Descriptor - var descriptor = Object.getOwnPropertyDescriptor(navigator, propertyName); - - // Delete any values we'll be replacing - if(typeof descriptor.value != 'undefined') delete descriptor['value']; - if(typeof descriptor.get != 'undefined') delete descriptor['get']; - var writable = false; - if(typeof descriptor.set != 'undefined') { - delete descriptor['set']; - writable = true; - } - if(typeof descriptor.writable != 'undefined') { - if(descriptor.writable == 'true') writable = true; - delete descriptor['writable']; - } - - switch(propertyName) { - - // Wrap the navigator.plugins object - case 'plugins': - console.log('RubberGlove: Cloaking plugins for ' + window.location.href); - var plugins = { }; - Object.defineProperty(plugins, 'length', { 'get': (function() { - var eventNode = document.currentScript.parentNode; - return function() { - // native() - console.error('RubberGlove: Iteration of navigator.plugins blocked for ' + window.location.href + ' (Informational, not an error.)'); - window.postMessage({ type: 'RubberGlove', text: 'navigator.plugins', url: window.location.href }, '*'); - return 0; - }; - })(), enumerable: true}); - plugins.item = (function() { - var fakePlugins = plugins; - return function(index) { /* native(item) */ return fakePlugins[index]; }; - })(); - plugins.namedItem = (function() { - var fakePlugins = plugins; - return function(name) { /* native(namedItem) */ return fakePlugins[name]; }; - })(); - plugins.refresh = (function() { - var fakePlugins = plugins; - var realPlugins = oldNavigator.plugins; - return function() { - // native(refresh) - // Refresh the real plugins list - // TODO: We probably shouldn't call this the first time if possible - realPlugins.refresh(); - // Remove any plugins we already have - var propNames = Object.getOwnPropertyNames(oldNavigator); - for(var i = 0; i < propNames.length; i++) { - var property = propNames[propertyIndex]; - if(property != 'length') delete fakePlugins[property]; - } - // Add plugins so they are accessible by key but not index - for(var n = 0; n < realPlugins.length; n++) { - var plugin = realPlugins[n]; - //console.log('RubberGlove: Cloaking \'' + plugin.name + '\''); - if(typeof plugin.name != 'undefined' && plugin.name != null && plugin.name != '') - Object.defineProperty(fakePlugins, plugin.name, { 'value': plugin, configurable: true }); - } - }; - })(); - // Refresh to initially populate the plugins - plugins.refresh(); - descriptor.value = plugins; - break; - - // Wrap the navigator.mimeTypes object - case 'mimeTypes': - console.log('RubberGlove: Cloaking mimeTypes for ' + window.location.href); - var mimeTypes = { }; - Object.defineProperty(mimeTypes, 'length', { 'get': (function() { - var eventNode = document.currentScript.parentNode; - return function() { - // native() - console.error('RubberGlove: Iteration of navigator.mimeTypes blocked for ' + window.location.href + ' (Informational, not an error.)'); - window.postMessage({ type: 'RubberGlove', text: 'navigator.mimeTypes', url: window.location.href }, '*'); - return 0; - }; - })(), enumerable: true}); - mimeTypes.item = (function() { - var fakeMimeTypes = mimeTypes; - return function(index) { /* native(item) */ return fakeMimeTypes[index]; }; - })(); - // Add mimeTypes so they are accessible by key but not index - for(var n = 0; n < oldNavigator.mimeTypes.length; n++) { - var mimeType = oldNavigator.mimeTypes[n]; - //console.log('RubberGlove: Cloaking \'' + mimeType.type + '\''); - if(typeof mimeType.type != 'undefined' && mimeType.type != null && mimeType.type != '') - Object.defineProperty(mimeTypes, mimeType.type, { 'value': mimeType, configurable: true }); + // Hides named plugins and mimeTypes + if(config.local.verbose) console.log("RubberGlove: Replacing Object.getOwnPropertyNames()"); + Object.getOwnPropertyNames = (function(oldGetOwnPropertyNames) { + return function getOwnPropertyNames() { // native(getOwnPropertyNames) + var propertyNames = oldGetOwnPropertyNames.apply(this, Array.prototype.slice.apply(arguments)); + if(arguments[0] === window.navigator.plugins || arguments[0] === window.navigator.mimeTypes) { + var filteredNames = []; + for(var i=0; i < propertyNames.length; i++) { + var propertyName = propertyNames[i]; + if(propertyName == 'item' || propertyName == 'namedItem' || propertyName == 'length') { + filteredNames.push(propertyName); } - descriptor.value = mimeTypes; - break; - - // wrap any other properties of navigator - default: - //console.log("RubberGlove: wrapping " + propertyName); - descriptor.get = (function() { - var prop = propertyName; - var nav = oldNavigator; - return function() { /* native() */ return nav[prop] }; - })(); - if(writable) { - descriptor.set = (function(value) { - var prop = propertyName; - var nav = oldNavigator; - return function() { /* native(item) */ nav[prop] = value; }; - })(); - } - break; - } - Object.defineProperty(altNav, propertyName, descriptor); - } - - // Add or wrap any functions - for(propertyName in oldNavigator) { - if(typeof(oldNavigator[propertyName]) == "function") { - switch(propertyName) { - default: - //console.log("RubberGlove: Adding function " + propertyName + "()"); - altNav[propertyName] = oldNavigator[propertyName]; - break; } + return filteredNames; } + return propertyNames; } + })(Object.getOwnPropertyNames); - return altNav; - })(); - - Object.defineProperty(window, 'navigator', { - enumerable: true, - configurable: false, - writable: true, - value: navWrapper - }); - - Object.defineProperty(window, 'clientInformation', { - enumerable: true, - configurable: false, - writable: true, - value: navWrapper - }); + // Makes our objects look like first class objects + if(config.local.verbose) console.log("RubberGlove: Replacing Object.prototype.toString()"); + Object.prototype.toString = (function(oldToString) { + return function toString() { // native(toString) + if(this === window.navigator) return "[object Navigator]"; + if(this === window.navigator.plugins) return "[object PluginArray]"; + if(this === window.navigator.mimeTypes) return "[object MimeTypeArray]"; + return oldToString.apply(this, Array.prototype.slice.apply(arguments)); + }; + })(Object.prototype.toString); } diff --git a/js/injectScripts.js b/js/injectScripts.js index a2d989c..c21d5ef 100644 --- a/js/injectScripts.js +++ b/js/injectScripts.js @@ -1,38 +1,54 @@ //console.log("RubberGlove: Content Script for " + window.location.href); - -// Create the script to be injected -var pageScript = document.createElement('script'); -pageScript.type = 'text/javascript'; -pageScript.async = false; -pageScript.text = "(" + bomOverloadFunction.toString() + ")();" - + "(" + scriptCleanupFunction.toString() + ")();"; - -// Locate the or create a new one if there isn't one or it isn't at -// the top of the page. -var html = document.documentElement -var headTags = document.getElementsByTagName("head"); -var head = headTags.length > 0 ? head = headTags[0] : null; -if(!head || head !== html.firstChild) { - head = document.createElement('head'); - html.insertBefore(head, html.firstChild); - pageScript.id = "_RubberGlove_removeHead"; +function getConfig(filename) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'filesystem:' + chrome.extension.getURL('temporary/' + filename), false); + xhr.send(null); + if(xhr.status == 200) return xhr.responseText; + return; } -// Listen for callbacks from the page script -window.addEventListener("message", function(event) { - if(event.source != window) return; - if(event.data.type && event.data.type == "RubberGlove") { - // Tell background.js to increment the badge counter - chrome.runtime.sendMessage({ - type: "increment-counter", - url: window.location.href, - message: event.data.text - }); - } -}); +var configText = getConfig("persistedSettings.js"); +console.log("RubberGlove: config = " + configText); +var config = JSON.parse(configText); -// Insert our script into the page. Note: This absolutely cannot under -// any circumstances happen in an async callback otherwise scripts on the -// page may run first and have access to the unwrapped objects. -// Synchronous XHR requests before this seem to work fine, however. -head.insertBefore(pageScript, head.firstChild); +if(config.local.enabled) { + // Create the script to be injected + var pageScript = document.createElement('script'); + pageScript.type = 'text/javascript'; + pageScript.async = false; + pageScript.text = "(function() {\n" + + "var config = " + configText + ";\n" + + "(" + bomOverload.toString() + ")();\n" + + "(" + scriptCleanupFunction.toString() + ")();\n" + + "})();"; + + // Locate the or create a new one if there isn't one or it isn't at + // the top of the page. + var html = document.documentElement + var headTags = document.getElementsByTagName("head"); + var head = headTags.length > 0 ? head = headTags[0] : null; + if(!head || head !== html.firstChild) { + head = document.createElement('head'); + html.insertBefore(head, html.firstChild); + pageScript.id = "_RubberGlove_removeHead"; + } + + // Listen for callbacks from the page script + window.addEventListener("message", function(event) { + if(event.source != window) return; + if(event.data.type && event.data.type == "RubberGlove") { + // Tell background.js to increment the badge counter + chrome.runtime.sendMessage({ + type: "increment-counter", + url: window.location.href, + message: event.data.text + }); + } + }); + + // Insert our script into the page. Note: This absolutely cannot under + // any circumstances happen in an async callback otherwise scripts on the + // page may run first and have access to the unwrapped objects. + // Synchronous XHR requests before this seem to work fine, however. + head.insertBefore(pageScript, head.firstChild); +} diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 0000000..5731de6 --- /dev/null +++ b/js/settings.js @@ -0,0 +1,36 @@ +function persistConfig(fileName, data, callback) { + console.log("RubberGlove: Requesting quota"); + navigator.webkitTemporaryStorage.requestQuota(1024*1024, function(grantedBytes) { + console.log("RubberGlove: Granted quota of " + grantedBytes); + window.webkitRequestFileSystem(TEMPORARY, grantedBytes, function(fs) { + console.log("RubberGlove: Got FileSystem"); + fs.root.getFile(fileName, {create: true}, function(fileEntry) { + console.log("RubberGlove: Got file " + fileEntry.name); + fileEntry.createWriter(function(fileWriter) { + console.log("RubberGlove: Got writer"); + fileWriter.onwriteend=function(e) { + console.log("RubberGlove: onWriteEnd"); + fileWriter.onwriteend=null; + var blob = new Blob([JSON.stringify(data)], {type: 'application/json'}); + fileWriter.write(blob); + } + fileWriter.truncate(0); + }); + }, fsErrorHandler); + }, fsErrorHandler); + }); +} + +function fsErrorHandler(e) { + console.error(e.name + ": " + e.message); +} + +function getConfigAsync(fileName, callback) { + window.webkitStorageInfo.requestQuota(PERSISTENT, 1024*1024, function(grantedBytes) { + window.webkitRequestFileSystem(PERSISTENT, grantedBytes, function(fs) { + fs.root.getFile(fileName, {create: false}, function(fileEntry) { + // TODO: Finish writing this! + }); + }); + }); +} \ No newline at end of file diff --git a/js/versionSanitizer.js b/js/versionSanitizer.js new file mode 100644 index 0000000..a3fe26c --- /dev/null +++ b/js/versionSanitizer.js @@ -0,0 +1,14 @@ +function sanitizeVersions(version) { + return version.replace(/(\b(?:[0-9]+\.?)+\b)/g, function(match) { + var version = match.split('.'); + var foundMajor = false; + for(var i=0; i < version.length; i++) { + var number = parseInt(version[i]); + if(number > 0) { + if(foundMajor) version[i] = 0; + else foundMajor = true; + } + } + return version.join("."); + }); +} \ No newline at end of file diff --git a/manifest.json b/manifest.json index b7af2db..cbffc3d 100644 --- a/manifest.json +++ b/manifest.json @@ -31,7 +31,10 @@ } ], "background": { - "scripts": ["js/background.js"], + "scripts": [ + "js/settings.js", + "js/background.js" + ], "persistent": true }, "browser_action": {