Microsoft has acquired GitHub. Decentraleyes has left GitHub. Welcome to its new home!

To participate, please register, or sign in with an existing GitLab.com, Bitbucket, or GitHub account.

Past contributions on GitHub? Be sure to reclaim your Comments, Issues, and Pull Requests.

Verified Commit 2b67d921 authored by Thomas Rientjes's avatar Thomas Rientjes
Browse files

Refactor existing codebase

parent 009902d6
...@@ -6,25 +6,18 @@ ...@@ -6,25 +6,18 @@
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"globals": { "globals": {
"Address": true,
"files": true, "files": true,
"helpers": true, "helpers": true,
"HOST_PREFIX": true,
"HOST_SUFFIX": true,
"HTTP_EXPRESSION": true,
"interceptor": true, "interceptor": true,
"MAPPING_FILE_EXPRESSION": true,
"mappings": true, "mappings": true,
"REQUEST_BLOCKING": true,
"REQUEST_HEADERS": true,
"requestAnalyzer": true, "requestAnalyzer": true,
"Resource": true,
"resources": true, "resources": true,
"Setting": true,
"stateManager": true, "stateManager": true,
"VALUE_SEPARATOR": true, "WebRequest": true,
"VERSION_EXPRESSION": true, "Whitelist": true
"VERSION_PLACEHOLDER": true,
"WEB_DOMAIN_EXPRESSION": true,
"WEB_PREFIX_LENGTH": true,
"WEB_PREFIX_VALUE": true
}, },
"overrides": { "overrides": {
"files": [ "files": [
......
...@@ -17,6 +17,13 @@ ...@@ -17,6 +17,13 @@
"unix" "unix"
], ],
"no-console": "off", "no-console": "off",
"no-multiple-empty-lines": [
"error", {
"max": 1,
"maxEOF": 1,
"maxBOF": 0
}
],
"no-use-before-define": "error", "no-use-before-define": "error",
"operator-assignment": "error", "operator-assignment": "error",
"prefer-template": "error", "prefer-template": "error",
......
...@@ -17,15 +17,42 @@ ...@@ -17,15 +17,42 @@
* Constants * Constants
*/ */
const HOST_PREFIX = '*://'; const Address = {
const HOST_SUFFIX = '/*'; 'ANY': '*://*/*',
const HTTP_EXPRESSION = /^http?:\/\//; 'ANY_PATH': '/*',
const MAPPING_FILE_EXPRESSION = /\.map$/i; 'ANY_PROTOCOL': '*://',
const REQUEST_BLOCKING = 'blocking'; 'DOMAIN_EXPRESSION': /:\/\/(.[^/]+)(.*)/,
const REQUEST_HEADERS = 'requestHeaders'; 'EXAMPLE': 'example.org',
const VALUE_SEPARATOR = ';'; 'HTTP_EXPRESSION': /^http?:\/\//,
const VERSION_EXPRESSION = /(?:\d{1,2}\.){1,3}\d{1,2}/; 'HTTPS': 'https://',
const VERSION_PLACEHOLDER = '{version}'; 'WWW_PREFIX': 'www.',
const WEB_DOMAIN_EXPRESSION = /:\/\/(.[^/]+)(.*)/; 'WWW_PREFIX_LENGTH': 4
const WEB_PREFIX_LENGTH = 4; };
const WEB_PREFIX_VALUE = 'www.';
const Resource = {
'MAPPING_EXPRESSION': /\.map$/i,
'VERSION_EXPRESSION': /(?:\d{1,2}\.){1,3}\d{1,2}/,
'VERSION_PLACEHOLDER': '{version}'
};
const Setting = {
'AMOUNT_INJECTED': 'amountInjected',
'BLOCK_MISSING': 'blockMissing',
'DISABLE_PREFETCH': 'disablePrefetch',
'SHOW_ICON_BADGE': 'showIconBadge',
'STRIP_METADATA': 'stripMetadata',
'WHITELISTED_DOMAINS': 'whitelistedDomains'
};
const WebRequest = {
'GET': 'GET',
'BLOCKING': 'blocking',
'HEADERS': 'requestHeaders',
'ORIGIN_HEADER': 'Origin',
'REFERER_HEADER': 'Referer'
};
const Whitelist = {
'TRIM_EXPRESSION': /^;+|;+$/g,
'VALUE_SEPARATOR': ';'
};
...@@ -37,10 +37,10 @@ interceptor.handleRequest = function (requestDetails, tabIdentifier, tab) { ...@@ -37,10 +37,10 @@ interceptor.handleRequest = function (requestDetails, tabIdentifier, tab) {
} }
try { try {
tabDomain = tab.url.match(WEB_DOMAIN_EXPRESSION)[1]; tabDomain = tab.url.match(Address.DOMAIN_EXPRESSION)[1];
tabDomain = requestAnalyzer._normalizeDomain(tabDomain); tabDomain = requestAnalyzer._normalizeDomain(tabDomain);
} catch (exception) { } catch (exception) {
tabDomain = 'example.org'; tabDomain = Address.EXAMPLE;
} }
// Temporary list of undetectable tainted domains. // Temporary list of undetectable tainted domains.
...@@ -98,9 +98,9 @@ interceptor._handleMissingCandidate = function (requestUrl) { ...@@ -98,9 +98,9 @@ interceptor._handleMissingCandidate = function (requestUrl) {
}; };
} }
if (requestUrl.match(HTTP_EXPRESSION)) { if (requestUrl.match(Address.HTTP_EXPRESSION)) {
let secureRequestUrl = requestUrl.replace(HTTP_EXPRESSION, 'https://'); let secureRequestUrl = requestUrl.replace(Address.HTTP_EXPRESSION, Address.HTTPS);
return { return {
'redirectUrl': secureRequestUrl 'redirectUrl': secureRequestUrl
...@@ -116,7 +116,7 @@ interceptor._handleMissingCandidate = function (requestUrl) { ...@@ -116,7 +116,7 @@ interceptor._handleMissingCandidate = function (requestUrl) {
interceptor._handleStorageChanged = function (changes) { interceptor._handleStorageChanged = function (changes) {
if ('blockMissing' in changes) { if (Setting.BLOCK_MISSING in changes) {
interceptor.blockMissing = changes.blockMissing.newValue; interceptor.blockMissing = changes.blockMissing.newValue;
} }
}; };
...@@ -128,7 +128,7 @@ interceptor._handleStorageChanged = function (changes) { ...@@ -128,7 +128,7 @@ interceptor._handleStorageChanged = function (changes) {
interceptor.amountInjected = 0; interceptor.amountInjected = 0;
interceptor.blockMissing = false; interceptor.blockMissing = false;
chrome.storage.local.get(['amountInjected', 'blockMissing'], function (items) { chrome.storage.local.get([Setting.AMOUNT_INJECTED, Setting.BLOCK_MISSING], function (items) {
interceptor.amountInjected = items.amountInjected || 0; interceptor.amountInjected = items.amountInjected || 0;
interceptor.blockMissing = items.blockMissing || false; interceptor.blockMissing = items.blockMissing || false;
......
...@@ -54,7 +54,8 @@ main._showReleaseNotes = function (details) { ...@@ -54,7 +54,8 @@ main._showReleaseNotes = function (details) {
let location = chrome.extension.getURL('pages/welcome/welcome.html'); let location = chrome.extension.getURL('pages/welcome/welcome.html');
if (details.reason === 'install' || details.reason === 'update') { if (details.reason === chrome.runtime.OnInstalledReason.INSTALL ||
details.reason === chrome.runtime.OnInstalledReason.UPDATE) {
if (details.temporary !== true) { if (details.temporary !== true) {
......
...@@ -28,9 +28,9 @@ requestAnalyzer.isValidCandidate = function (requestDetails, tabDetails) { ...@@ -28,9 +28,9 @@ requestAnalyzer.isValidCandidate = function (requestDetails, tabDetails) {
let initiatorHost; let initiatorHost;
try { try {
initiatorHost = tabDetails.url.match(WEB_DOMAIN_EXPRESSION)[1]; initiatorHost = tabDetails.url.match(Address.DOMAIN_EXPRESSION)[1];
} catch (exception) { } catch (exception) {
initiatorHost = 'example.org'; initiatorHost = Address.EXAMPLE;
} }
if (initiatorHost && requestAnalyzer.whitelistedDomains[requestAnalyzer._normalizeDomain(initiatorHost)]) { if (initiatorHost && requestAnalyzer.whitelistedDomains[requestAnalyzer._normalizeDomain(initiatorHost)]) {
...@@ -38,21 +38,21 @@ requestAnalyzer.isValidCandidate = function (requestDetails, tabDetails) { ...@@ -38,21 +38,21 @@ requestAnalyzer.isValidCandidate = function (requestDetails, tabDetails) {
} }
// Only requests of type GET can be valid candidates. // Only requests of type GET can be valid candidates.
return requestDetails.method === 'GET'; return requestDetails.method === WebRequest.GET;
}; };
requestAnalyzer.getLocalTarget = function (requestDetails) { requestAnalyzer.getLocalTarget = function (requestDetails) {
let destinationHost, destinationPath, hostMappings, basePath, resourceMappings; let destinationHost, destinationPath, hostMappings, basePath, resourceMappings;
destinationHost = requestDetails.url.match(WEB_DOMAIN_EXPRESSION)[1]; destinationHost = requestDetails.url.match(Address.DOMAIN_EXPRESSION)[1];
destinationPath = requestDetails.url.match(WEB_DOMAIN_EXPRESSION)[2]; destinationPath = requestDetails.url.match(Address.DOMAIN_EXPRESSION)[2];
// Use the proper mappings for the targeted host. // Use the proper mappings for the targeted host.
hostMappings = mappings[destinationHost]; hostMappings = mappings[destinationHost];
// Resource mapping files are never locally available. // Resource mapping files are never locally available.
if (MAPPING_FILE_EXPRESSION.test(destinationPath)) { if (Resource.MAPPING_EXPRESSION.test(destinationPath)) {
return false; return false;
} }
...@@ -89,8 +89,8 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel ...@@ -89,8 +89,8 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel
resourcePath = channelPath.replace(basePath, ''); resourcePath = channelPath.replace(basePath, '');
versionNumber = resourcePath.match(VERSION_EXPRESSION); versionNumber = resourcePath.match(Resource.VERSION_EXPRESSION);
resourcePattern = resourcePath.replace(versionNumber, VERSION_PLACEHOLDER); resourcePattern = resourcePath.replace(versionNumber, Resource.VERSION_PLACEHOLDER);
for (let resourceMold of Object.keys(resourceMappings)) { for (let resourceMold of Object.keys(resourceMappings)) {
...@@ -99,9 +99,9 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel ...@@ -99,9 +99,9 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel
let targetPath, version; let targetPath, version;
targetPath = resourceMappings[resourceMold].path; targetPath = resourceMappings[resourceMold].path;
targetPath = targetPath.replace(VERSION_PLACEHOLDER, versionNumber); targetPath = targetPath.replace(Resource.VERSION_PLACEHOLDER, versionNumber);
version = versionNumber && versionNumber[0] || targetPath.match(VERSION_EXPRESSION); version = versionNumber && versionNumber[0] || targetPath.match(Resource.VERSION_EXPRESSION);
// Prepare and return a local target. // Prepare and return a local target.
return { return {
...@@ -117,7 +117,7 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel ...@@ -117,7 +117,7 @@ requestAnalyzer._findLocalTarget = function (resourceMappings, basePath, channel
requestAnalyzer._applyWhitelistedDomains = function () { requestAnalyzer._applyWhitelistedDomains = function () {
chrome.storage.local.get('whitelistedDomains', function (items) { chrome.storage.local.get(Setting.WHITELISTED_DOMAINS, function (items) {
requestAnalyzer.whitelistedDomains = items.whitelistedDomains || {}; requestAnalyzer.whitelistedDomains = items.whitelistedDomains || {};
}); });
}; };
...@@ -126,8 +126,8 @@ requestAnalyzer._normalizeDomain = function (domain) { ...@@ -126,8 +126,8 @@ requestAnalyzer._normalizeDomain = function (domain) {
domain = domain.toLowerCase().trim(); domain = domain.toLowerCase().trim();
if (domain.startsWith(WEB_PREFIX_VALUE)) { if (domain.startsWith(Address.WWW_PREFIX)) {
domain = domain.slice(WEB_PREFIX_LENGTH); domain = domain.slice(Address.WWW_PREFIX_LENGTH);
} }
return domain; return domain;
......
...@@ -53,7 +53,7 @@ stateManager.registerInjection = function (tabIdentifier, injection) { ...@@ -53,7 +53,7 @@ stateManager.registerInjection = function (tabIdentifier, injection) {
if (isNaN(interceptor.amountInjected)) { if (isNaN(interceptor.amountInjected)) {
chrome.storage.local.get('amountInjected', function (items) { chrome.storage.local.get(Setting.AMOUNT_INJECTED, function (items) {
interceptor.amountInjected = items.amountInjected; interceptor.amountInjected = items.amountInjected;
...@@ -121,7 +121,7 @@ stateManager._createTab = function (tab) { ...@@ -121,7 +121,7 @@ stateManager._createTab = function (tab) {
}); });
}); });
}, requestFilters, [REQUEST_BLOCKING]); }, requestFilters, [WebRequest.BLOCKING]);
}; };
stateManager._removeTab = function (tabIdentifier) { stateManager._removeTab = function (tabIdentifier) {
...@@ -156,9 +156,9 @@ stateManager._stripMetadata = function (requestDetails) { ...@@ -156,9 +156,9 @@ stateManager._stripMetadata = function (requestDetails) {
for (let i = 0; i < requestDetails.requestHeaders.length; ++i) { for (let i = 0; i < requestDetails.requestHeaders.length; ++i) {
if (requestDetails.requestHeaders[i].name === 'Origin') { if (requestDetails.requestHeaders[i].name === WebRequest.ORIGIN_HEADER) {
requestDetails.requestHeaders.splice(i--, 1); requestDetails.requestHeaders.splice(i--, 1);
} else if (requestDetails.requestHeaders[i].name === 'Referer') { } else if (requestDetails.requestHeaders[i].name === WebRequest.REFERER_HEADER) {
requestDetails.requestHeaders.splice(i--, 1); requestDetails.requestHeaders.splice(i--, 1);
} }
} }
...@@ -190,13 +190,13 @@ stateManager._handleStorageChanged = function (changes) { ...@@ -190,13 +190,13 @@ stateManager._handleStorageChanged = function (changes) {
onBeforeSendHeaders.removeListener(stateManager._stripMetadata, { onBeforeSendHeaders.removeListener(stateManager._stripMetadata, {
'urls': stateManager.validHosts 'urls': stateManager.validHosts
}, [REQUEST_BLOCKING, REQUEST_HEADERS]); }, [WebRequest.BLOCKING, WebRequest.HEADERS]);
if (changes.stripMetadata.newValue !== false) { if (changes.stripMetadata.newValue !== false) {
onBeforeSendHeaders.addListener(stateManager._stripMetadata, { onBeforeSendHeaders.addListener(stateManager._stripMetadata, {
'urls': stateManager.validHosts 'urls': stateManager.validHosts
}, [REQUEST_BLOCKING, REQUEST_HEADERS]); }, [WebRequest.BLOCKING, WebRequest.HEADERS]);
} }
} }
}; };
...@@ -219,7 +219,7 @@ stateManager.validHosts = []; ...@@ -219,7 +219,7 @@ stateManager.validHosts = [];
for (let mapping in mappings) { for (let mapping in mappings) {
let supportedHost = HOST_PREFIX + mapping + HOST_SUFFIX; let supportedHost = Address.ANY_PROTOCOL + mapping + Address.ANY_PATH;
stateManager.validHosts.push(supportedHost); stateManager.validHosts.push(supportedHost);
} }
...@@ -248,7 +248,7 @@ chrome.webRequest.onErrorOccurred.addListener(function (requestDetails) { ...@@ -248,7 +248,7 @@ chrome.webRequest.onErrorOccurred.addListener(function (requestDetails) {
delete stateManager.requests[requestDetails.requestId]; delete stateManager.requests[requestDetails.requestId];
} }
}, {'urls': ['*://*/*']}); }, {'urls': [Address.ANY]});
chrome.webRequest.onBeforeRedirect.addListener(function (requestDetails) { chrome.webRequest.onBeforeRedirect.addListener(function (requestDetails) {
...@@ -260,10 +260,10 @@ chrome.webRequest.onBeforeRedirect.addListener(function (requestDetails) { ...@@ -260,10 +260,10 @@ chrome.webRequest.onBeforeRedirect.addListener(function (requestDetails) {
delete stateManager.requests[requestDetails.requestId]; delete stateManager.requests[requestDetails.requestId];
} }
}, {'urls': ['*://*/*']}); }, {'urls': [Address.ANY]});
chrome.webRequest.onBeforeSendHeaders.addListener(stateManager._stripMetadata, { chrome.webRequest.onBeforeSendHeaders.addListener(stateManager._stripMetadata, {
'urls': stateManager.validHosts 'urls': stateManager.validHosts
}, [REQUEST_BLOCKING, REQUEST_HEADERS]); }, [WebRequest.BLOCKING, WebRequest.HEADERS]);
chrome.storage.onChanged.addListener(stateManager._handleStorageChanged); chrome.storage.onChanged.addListener(stateManager._handleStorageChanged);
...@@ -31,8 +31,8 @@ function _normalizeDomain (domain) { ...@@ -31,8 +31,8 @@ function _normalizeDomain (domain) {
domain = domain.toLowerCase().trim(); domain = domain.toLowerCase().trim();
if (domain.startsWith(WEB_PREFIX_VALUE)) { if (domain.startsWith(Address.WWW_PREFIX)) {
domain = domain.slice(WEB_PREFIX_LENGTH); domain = domain.slice(Address.WWW_PREFIX_LENGTH);
} }
return domain; return domain;
...@@ -60,11 +60,11 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -60,11 +60,11 @@ document.addEventListener('DOMContentLoaded', function () {
helpers.insertI18nContentIntoDocument(document); helpers.insertI18nContentIntoDocument(document);
optionElements = { optionElements = {
'showIconBadge': options._getOptionElement('showIconBadge'), 'showIconBadge': options._getOptionElement(Setting.SHOW_ICON_BADGE),
'blockMissing': options._getOptionElement('blockMissing'), 'blockMissing': options._getOptionElement(Setting.BLOCK_MISSING),
'disablePrefetch': options._getOptionElement('disablePrefetch'), 'disablePrefetch': options._getOptionElement(Setting.DISABLE_PREFETCH),
'stripMetadata': options._getOptionElement('stripMetadata'), 'stripMetadata': options._getOptionElement(Setting.STRIP_METADATA),
'whitelistedDomains': options._getOptionElement('whitelistedDomains') 'whitelistedDomains': options._getOptionElement(Setting.WHITELISTED_DOMAINS)
}; };
chrome.storage.local.get(Object.keys(optionElements), function (items) { chrome.storage.local.get(Object.keys(optionElements), function (items) {
...@@ -79,7 +79,7 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -79,7 +79,7 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
domainWhitelist = domainWhitelist.slice(0, -1); domainWhitelist = domainWhitelist.slice(0, -1);
domainWhitelist = domainWhitelist.replace(/^;+|;+$/g, ''); domainWhitelist = domainWhitelist.replace(Whitelist.TRIM_EXPRESSION, '');
optionElements.showIconBadge.checked = items.showIconBadge; optionElements.showIconBadge.checked = items.showIconBadge;
optionElements.blockMissing.checked = items.blockMissing; optionElements.blockMissing.checked = items.blockMissing;
...@@ -103,7 +103,7 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -103,7 +103,7 @@ document.addEventListener('DOMContentLoaded', function () {
optionValue = target.value; optionValue = target.value;
} }
if (optionKey === 'disablePrefetch') { if (optionKey === Setting.DISABLE_PREFETCH) {
if (optionValue === false) { if (optionValue === false) {
...@@ -118,13 +118,13 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -118,13 +118,13 @@ document.addEventListener('DOMContentLoaded', function () {
} }
} }
if (optionKey === 'whitelistedDomains') { if (optionKey === Setting.WHITELISTED_DOMAINS) {
let domainWhitelist = optionValue; let domainWhitelist = optionValue;
optionValue = {}; optionValue = {};
domainWhitelist.split(VALUE_SEPARATOR).forEach(function (domain) { domainWhitelist.split(Whitelist.VALUE_SEPARATOR).forEach(function (domain) {
optionValue[_normalizeDomain(domain)] = true; optionValue[_normalizeDomain(domain)] = true;
}); });
} }
......
...@@ -51,7 +51,7 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -51,7 +51,7 @@ document.addEventListener('DOMContentLoaded', function () {
popup.backgroundPage = backgroundPage; popup.backgroundPage = backgroundPage;
if (backgroundPage.main.operatingSystem === 'android') { if (backgroundPage.main.operatingSystem === chrome.runtime.PlatformOs.ANDROID) {
browser.tabs.getCurrent().then(function (tab) { browser.tabs.getCurrent().then(function (tab) {
...@@ -67,7 +67,7 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -67,7 +67,7 @@ document.addEventListener('DOMContentLoaded', function () {
injectionOverview = {}; injectionOverview = {};
try { try {
domain = tabs[0].url.match(WEB_DOMAIN_EXPRESSION)[1]; domain = tabs[0].url.match(Address.DOMAIN_EXPRESSION)[1];
} catch (exception) { } catch (exception) {
domain = null; domain = null;
} }
...@@ -80,8 +80,8 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -80,8 +80,8 @@ document.addEventListener('DOMContentLoaded', function () {
protectionToggleElement = document.getElementById('protection-toggle-button'); protectionToggleElement = document.getElementById('protection-toggle-button');
domainIndicatorElement = document.getElementById('domain-indicator'); domainIndicatorElement = document.getElementById('domain-indicator');
if (domain.startsWith(WEB_PREFIX_VALUE)) { if (domain.startsWith(Address.WWW_PREFIX)) {
domain = domain.slice(WEB_PREFIX_LENGTH); domain = domain.slice(Address.WWW_PREFIX_LENGTH);
} }
domainIndicatorElement.innerText = domain; domainIndicatorElement.innerText = domain;
...@@ -99,7 +99,7 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -99,7 +99,7 @@ document.addEventListener('DOMContentLoaded', function () {
chrome.tabs.reload(tabs[0].id); chrome.tabs.reload(tabs[0].id);
if (backgroundPage.main.operatingSystem === 'android') { if (backgroundPage.main.operatingSystem === chrome.runtime.PlatformOs.ANDROID) {
return browser.tabs.getCurrent().then(function (tab) { return browser.tabs.getCurrent().then(function (tab) {
browser.tabs.remove(tab.id); browser.tabs.remove(tab.id);
...@@ -125,7 +125,7 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -125,7 +125,7 @@ document.addEventListener('DOMContentLoaded', function () {
chrome.tabs.reload(tabs[0].id); chrome.tabs.reload(tabs[0].id);
if (backgroundPage.main.operatingSystem === 'android') { if (backgroundPage.main.operatingSystem === chrome.runtime.PlatformOs.ANDROID) {
return browser.tabs.getCurrent().then(function (tab) { return browser.tabs.getCurrent().then(function (tab) {
browser.tabs.remove(tab.id); browser.tabs.remove(tab.id);
...@@ -225,7 +225,7 @@ document.addEventListener('DOMContentLoaded', function () { ...@@ -225,7 +225,7 @@ document.addEventListener('DOMContentLoaded', function () {
optionsButtonElement.addEventListener('mouseup', function () { optionsButtonElement.addEventListener('mouseup', function () {
if (popup.backgroundPage.main.operatingSystem === 'android') { if (popup.backgroundPage.main.operatingSystem === chrome.runtime.PlatformOs.ANDROID) {
return chrome.tabs.create({ return chrome.tabs.create({
'url': chrome.extension.getURL('pages/options/options.html') 'url': chrome.extension.getURL('pages/options/options.html')
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment