diff --git a/background.html b/background.html index 680190082..9e3123fdd 100644 --- a/background.html +++ b/background.html @@ -25,6 +25,7 @@ + diff --git a/js/background.js b/js/background.js index 3961ac656..da3bf4d78 100644 --- a/js/background.js +++ b/js/background.js @@ -54,6 +54,7 @@ return { userSettings: { autoUpdate: true, collapseBlocked: true, + contextMenuEnabled: false, externalLists: defaultExternalLists, logRequests: false, parseAllABPHideFilters: true, @@ -114,6 +115,7 @@ return { noopFunc: function(){}, apiErrorCount: 0, + elementPickerTarget: '', // so that I don't have to care for last comma dummy: 0 diff --git a/js/contextmenu.js b/js/contextmenu.js new file mode 100644 index 000000000..1a9022d41 --- /dev/null +++ b/js/contextmenu.js @@ -0,0 +1,104 @@ +/******************************************************************************* + + µBlock - a Chromium browser extension to block requests. + Copyright (C) 2014 Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +/* global */ + +/******************************************************************************/ + +// New namespace + +µBlock.contextMenu = (function() { + +/******************************************************************************/ + +var µb = µBlock; +var enabled = false; + +/******************************************************************************/ + +var onContextMenuClicked = function(details, tab) { + if ( details.menuItemId !== 'blockElement' ) { + return; + } + if ( tab === undefined ) { + return; + } + if ( /^https?:\/\//.test(tab.url) === false ) { + return; + } + var tagName = ''; + var src = ''; + if ( typeof details.frameUrl === 'string' ) { + tagName = 'iframe'; + src = details.frameUrl; + } else if ( typeof details.srcUrl === 'string' ) { + if ( details.mediaType === 'image' ) { + tagName = 'img'; + } else if ( details.mediaType === 'video' ) { + tagName = 'video'; + } else if ( details.mediaType === 'audio' ) { + tagName = 'audio'; + } + src = details.srcUrl; + } else if ( typeof details.linkUrl === 'string' ) { + tagName = 'a'; + src = details.linkUrl; + } + + if ( src === '' ) { + return; + } + + µb.elementPickerExec(tab.id, tagName + '\t' + src); +}; + +/******************************************************************************/ + +var toggleMenu = function(on) { + // This needs to be local scope: we can't reuse it for more than one + // menu creation call. + var menuCreateDetails = { + id: 'blockElement', + title: chrome.i18n.getMessage('pickerContextMenuEntry'), + contexts: ['frame', 'link', 'image', 'video'], + documentUrlPatterns: ['https://*/*', 'http://*/*'] + }; + + if ( on === true && enabled === false ) { + chrome.contextMenus.create(menuCreateDetails); + chrome.contextMenus.onClicked.addListener(onContextMenuClicked); + enabled = true; + } else if ( on !== true && enabled === true ) { + chrome.contextMenus.onClicked.removeListener(onContextMenuClicked); + chrome.contextMenus.remove(menuCreateDetails.id); + enabled = false; + } +}; + +/******************************************************************************/ + +return { + toggle: toggleMenu +}; + +/******************************************************************************/ + +})(); diff --git a/js/element-picker.js b/js/element-picker.js index 0f0b69823..5eaa1de66 100644 --- a/js/element-picker.js +++ b/js/element-picker.js @@ -229,6 +229,10 @@ var svgOcean = null; var svgIslands = null; var divDialog = null; var taCandidate = null; +var urlNormalizer = null; + +var netFilterCandidates = []; +var cosmeticFilterCandidates = []; var targetElements = []; var svgWidth = 0; @@ -329,7 +333,7 @@ var removeElements = function(elems) { // Extract the best possible net filter, i.e. as specific as possible. -var netFilterFromElement = function(elem) { +var netFilterFromElement = function(elem, out) { if ( elem === null ) { return; } @@ -344,14 +348,31 @@ var netFilterFromElement = function(elem) { if ( typeof src !== 'string' || src.length === 0 ) { return; } - return src.replace(/^https?:\/\//, '||').replace(/\?.*$/, ''); + // Remove fragment + var pos = src.indexOf('#'); + if ( pos !== -1 ) { + src = src.slice(0, pos); + } + // Feed the attribute to a link element, then retrieve back: this + // should normalize it. + urlNormalizer.href = src; + src = urlNormalizer.href; + // Anchor absolute filter to hostname + src = src.replace(/^https?:\/\//, '||'); + out.push(src); + // Suggest a less narrow filter if possible + pos = src.indexOf('?'); + if ( pos !== -1 ) { + src = src.slice(0, pos); + out.push(src); + } }; /******************************************************************************/ // Extract the best possible cosmetic filter, i.e. as specific as possible. -var cosmeticFilterFromElement = function(elem) { +var cosmeticFilterFromElement = function(elem, out) { if ( elem === null ) { return; } @@ -417,42 +438,73 @@ var cosmeticFilterFromElement = function(elem) { } } - return '##' + prefix + suffix.join(''); + out.push('##' + prefix + suffix.join('')); }; /******************************************************************************/ -var selectorFromCandidate = function() { - var selector = ''; - var v = taCandidate.value; - if ( v.slice(0, 2) === '##' ) { - selector = v.replace('##', ''); - } else { - if ( v.slice(0, 2) === '||' ) { - v = v.replace('||', ''); - } - selector = '[src*="' + v + '"]'; +var filtersFromElement = function(elem) { + netFilterCandidates.length = 0; + cosmeticFilterCandidates.length = 0; + while ( elem && elem !== document.body ) { + netFilterFromElement(elem, netFilterCandidates); + cosmeticFilterFromElement(elem, cosmeticFilterCandidates); + elem = elem.parentNode; } - try { - if ( document.querySelector(selector) === null ) { - return ''; - } - } - catch (e) { - return ''; - } - return selector; }; /******************************************************************************/ +var elementsFromFilter = function(filter) { + var out = []; + + // Cosmetic filters: these are straight CSS selectors + // TODO: This is still not working well for a[href], because there are + // many ways to compose a valid href to the same effective URL. + // One idea is to normalize all a[href] on the page, but for now I will + // wait and see, as I prefer to refrain from tampering with the page + // content if I can avoid it. + if ( filter.slice(0, 2) === '##' ) { + try { + out = document.querySelectorAll(filter.replace('##', '')); + } + catch (e) { + } + return out; + } + + // Net filters: we need to lookup manually -- translating into a + // foolproof CSS selector is just not possible + if ( filter.slice(0, 2) === '||' ) { + filter = filter.replace('||', ''); + } + var elems = document.querySelectorAll('[src]'); + var i = elems.length; + var elem; + while ( i-- ) { + elem = elems[i]; + if ( typeof elem.src !== 'string' ) { + continue; + } + if ( elem.src.indexOf(filter) !== -1 ) { + out.push(elem); + } + } + return out; +}; + +// https://www.youtube.com/watch?v=YI2XuIOW3gM + +/******************************************************************************/ + var userFilterFromCandidate = function() { - if ( selectorFromCandidate() === '' ) { + var v = taCandidate.value; + + var elems = elementsFromFilter(v); + if ( elems.length === 0 ) { return false; } - var v = taCandidate.value; - // Cosmetic filter? if ( v.slice(0, 2) === '##' ) { return window.location.hostname + v; @@ -468,52 +520,62 @@ var userFilterFromCandidate = function() { /******************************************************************************/ -var onCandidateChanged = function(ev) { - var selector = selectorFromCandidate(); - divDialog.querySelector('#create').disabled = selector === ''; - if ( selector === '' ) { - highlightElements([]); - return; - } - highlightElements(document.querySelectorAll(selector)); +var onCandidateChanged = function() { + var elems = elementsFromFilter(taCandidate.value); + divDialog.querySelector('#create').disabled = elems.length === 0; + highlightElements(elems); }; /******************************************************************************/ -var candidateFromClickEvent = function(ev) { - var target = ev.target; - if ( !target ) { +var candidateFromFilterChoice = function(filterChoice) { + var slot = filterChoice.slot; + var filters = filterChoice.filters; + var filter = filters[slot]; + + if ( filter === undefined ) { return ''; } - // Bare - if ( ev.ctrlKey || ev.metaKey ) { - return target.textContent; - } - // For net filters there no such thing as a path - if ( target.textContent.charAt(0) !== '#' ) { - return target.textContent; + if ( filterChoice.type === 'net' || filterChoice.modifier ) { + return filter; } // Return path: the target element, then all siblings prepended var selector = []; - while ( target ) { - if ( target.nodeType !== 1 || target.tagName.toLowerCase() !== 'li' ) { - continue; - } - selector.unshift(target.textContent.replace(/^##/, '')); + var filter; + for ( ; slot < filters.length; slot++ ) { + filter = filters[slot]; + selector.unshift(filter.replace(/^##/, '')); // Stop at any element with an id: these are unique in a web page - if ( target.textContent.slice(0, 3) === '###' ) { + if ( filter.slice(0, 3) === '###' ) { break; } - target = target.nextSibling; } return '##' + selector.join(' > '); }; /******************************************************************************/ +var filterChoiceFromEvent = function(ev) { + var li = ev.target; + var isNetFilter = li.textContent.slice(0, 2) !== '##'; + var r = { + type: isNetFilter ? 'net' : 'cosmetic', + filters: isNetFilter ? netFilterCandidates : cosmeticFilterCandidates, + slot: 0, + modifier: ev.ctrlKey || ev.metaKey + }; + while ( li.previousSibling !== null ) { + li = li.previousSibling; + r.slot += 1; + } + return r; +}; + +/******************************************************************************/ + var onDialogClicked = function(ev) { if ( ev.target === null ) { /* do nothing */ @@ -523,7 +585,7 @@ var onDialogClicked = function(ev) { var filter = userFilterFromCandidate(); if ( filter ) { messaging.tell({ what: 'createUserFilter', filters: filter }); - removeElements(document.querySelectorAll(selectorFromCandidate())); + removeElements(elementsFromFilter(taCandidate.value)); stopPicker(); } } @@ -537,7 +599,7 @@ var onDialogClicked = function(ev) { } else if ( ev.target.tagName.toLowerCase() === 'li' && pickerRootDistance(ev.target) === 5 ) { - taCandidate.value = candidateFromClickEvent(ev); + taCandidate.value = candidateFromFilterChoice(filterChoiceFromEvent(ev)); onCandidateChanged(); } @@ -558,28 +620,51 @@ var removeAllChildren = function(parent) { // TODO: for convenience I could provide a small set of net filters instead // of just a single one. Truncating the right-most part of the path etc. -var showDialog = function(filters) { - var divNet = divDialog.querySelector('ul > li:nth-of-type(1) > ul'); - var divCosmetic = divDialog.querySelector('ul > li:nth-of-type(2) > ul'); - removeAllChildren(divCosmetic); - removeAllChildren(divNet); - var filter, li; - for ( var i = 0; i < filters.length; i++ ) { - filter = filters[i]; - li = document.createElement('li'); - li.textContent = filter; - if ( filter.indexOf('##') === 0 ) { - divCosmetic.appendChild(li); - } else { - divNet.appendChild(li); - } - } - divDialog.querySelector('ul > li:nth-of-type(1)').style.display = divNet.firstChild ? '' : 'none'; - divDialog.querySelector('ul > li:nth-of-type(2)').style.display = divCosmetic.firstChild ? '' : 'none'; - divDialog.querySelector('ul').style.display = divNet.firstChild || divCosmetic.firstChild ? '' : 'none'; - taCandidate.value = ''; - divDialog.querySelector('#create').disabled = true; +var showDialog = function(options) { pausePicker(); + + options = options || {}; + + // Create lists of candidate filters + var populate = function(src, des) { + var root = divDialog.querySelector(des); + var ul = root.querySelector('ul'); + removeAllChildren(ul); + var li; + for ( var i = 0; i < src.length; i++ ) { + li = document.createElement('li'); + li.textContent = src[i]; + ul.appendChild(li); + } + root.style.display = src.length !== 0 ? '' : 'none'; + }; + + populate(netFilterCandidates, 'ul > li:nth-of-type(1)'); + populate(cosmeticFilterCandidates, 'ul > li:nth-of-type(2)'); + + divDialog.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none'; + divDialog.querySelector('#create').disabled = true; + + // Auto-select a candidate filter + var filterChoice = { + type: '', + filters: [], + slot: 0, + modifier: options.modifier || false + }; + if ( netFilterCandidates.length ) { + filterChoice.type = 'net'; + filterChoice.filters = netFilterCandidates; + } else if ( cosmeticFilterCandidates.length ) { + filterChoice.type = 'cosmetic'; + filterChoice.filters = cosmeticFilterCandidates; + } + + taCandidate.value = ''; + if ( filterChoice.type !== '' ) { + taCandidate.value = candidateFromFilterChoice(filterChoice); + onCandidateChanged(); + } }; /******************************************************************************/ @@ -604,18 +689,8 @@ var onSvgClicked = function() { if ( pickerPaused() ) { return; } - - var filter; - var filters = []; - for ( var elem = targetElements[0]; elem && elem !== document.body; elem = elem.parentNode ) { - if ( filter = cosmeticFilterFromElement(elem) ) { - filters.push(filter); - } - if ( filter = netFilterFromElement(elem) ) { - filters.push(filter); - } - } - showDialog(filters); + filtersFromElement(targetElements[0]); + showDialog(); }; /******************************************************************************/ @@ -646,6 +721,9 @@ var onScrolled = function(ev) { /******************************************************************************/ +// Let's have the element picker code flushed from memory when no longer +// in use: to ensure this, release all local references. + var stopPicker = function() { if ( pickerRoot !== null ) { document.removeEventListener('keydown', onKeyPressed); @@ -655,7 +733,11 @@ var stopPicker = function() { svgRoot.removeEventListener('mousemove', onSvgHovered); svgRoot.removeEventListener('click', onSvgClicked); document.body.removeChild(pickerRoot); - pickerRoot = divDialog = svgRoot = svgOcean = svgIslands = taCandidate = null; + pickerRoot = + divDialog = + svgRoot = svgOcean = svgIslands = + taCandidate = + urlNormalizer = null; messaging.stop(); } targetElements = []; @@ -685,6 +767,7 @@ var startPicker = function() { 'color: #000;', 'font: 12px sans-serif;', 'margin: 0;', + 'max-width: none;', 'outline: 0;', 'overflow: visible;', 'padding: 0;', @@ -710,6 +793,9 @@ var startPicker = function() { 'color: #999;', 'background-color: #ccc;', '}', + '.µBlock button#create:not(:disabled) {', + 'background-color: #ffdca8;', + '}', '.µBlock > svg {', 'position: absolute;', 'top: 0;', @@ -782,6 +868,17 @@ var startPicker = function() { 'text-align: left;', 'overflow: hidden;', '}', + '.µBlock > div > ul > li:not(:first-child) {', + 'margin-top: 0.5em;', + '}', + '.µBlock > div > ul > li > span:nth-of-type(1) {', + 'font-weight: bold;', + '}', + '.µBlock > div > ul > li > span:nth-of-type(2) {', + 'margin: 0 0 0 1em;', + 'font-size: smaller;', + 'color: gray;', + '}', '.µBlock > div > ul > li > ul {', 'margin: 0 0 0 1em;', 'list-style-type: none;', @@ -832,8 +929,8 @@ var startPicker = function() { '', '', '', '' ].join(''); @@ -845,6 +942,7 @@ var startPicker = function() { divDialog.addEventListener('click', onDialogClicked); taCandidate = divDialog.querySelector('textarea'); taCandidate.addEventListener('input', onCandidateChanged); + urlNormalizer = document.createElement('a'); window.addEventListener('scroll', onScrolled); document.addEventListener('keydown', onKeyPressed); }; @@ -855,12 +953,59 @@ startPicker(); /******************************************************************************/ -messaging.ask({ what: 'i18n' }, function(details) { - divDialog.querySelector('#create').firstChild.nodeValue = details.create; - divDialog.querySelector('#pick').firstChild.nodeValue = details.pick; - divDialog.querySelector('#quit').firstChild.nodeValue = details.quit; - divDialog.querySelector('ul > li:nth-of-type(1)').firstChild.nodeValue = details.netFilters; - divDialog.querySelector('ul > li:nth-of-type(2)').firstChild.nodeValue = details.cosmeticFilters; +messaging.ask({ what: 'elementPickerArguments' }, function(details) { + var i18nMap = { + '#create': 'create', + '#pick': 'pick', + '#quit': 'quit', + 'ul > li:nth-of-type(1) > span:nth-of-type(1)': 'netFilters', + 'ul > li:nth-of-type(2) > span:nth-of-type(1)': 'cosmeticFilters', + 'ul > li:nth-of-type(2) > span:nth-of-type(2)': 'cosmeticFiltersHint' + + }; + for ( var k in i18nMap ) { + if ( i18nMap.hasOwnProperty(k) === false ) { + continue; + } + divDialog.querySelector(k).firstChild.nodeValue = details.i18n[i18nMap[k]]; + } + + // Auto-select a specific target, if any, and if possible + var targetElement = details.targetElement || ''; + var pos = targetElement.indexOf('\t'); + if ( pos === -1 ) { + return; + } + var srcAttrMap = { + 'a': 'href', + 'img': 'src', + 'iframe': 'src', + 'video': 'src', + 'audio': 'src' + }; + var tagName = targetElement.slice(0, pos); + var url = targetElement.slice(pos + 1); + var attr = srcAttrMap[tagName]; + if ( attr === undefined ) { + return; + } + var elems = document.querySelectorAll(tagName + '[' + attr + ']'); + var i = elems.length; + var elem, src; + while ( i-- ) { + elem = elems[i]; + src = elem.getAttribute(attr); + if ( src === null || src === '' ) { + continue; + } + urlNormalizer.href = src; + src = urlNormalizer.href; + if ( src === url ) { + filtersFromElement(elem); + showDialog({ modifier: true }); + break; + } + } }); /******************************************************************************/ diff --git a/js/messaging-handlers.js b/js/messaging-handlers.js index d7c688625..e804b5d06 100644 --- a/js/messaging-handlers.js +++ b/js/messaging-handlers.js @@ -24,14 +24,17 @@ /******************************************************************************/ /******************************************************************************/ +// popup.js + (function() { -// popup.js +/******************************************************************************/ + +var µb = µBlock; /******************************************************************************/ var getStats = function(request) { - var µb = µBlock; var r = { globalBlockedRequestCount: µb.localSettings.blockedRequestCount, globalAllowedRequestCount: µb.localSettings.allowedRequestCount, @@ -72,26 +75,28 @@ var onMessage = function(request, sender, callback) { break; case 'toggleNetFiltering': - µBlock.toggleNetFilteringSwitch( + µb.toggleNetFilteringSwitch( request.url, request.scope, request.state ); - µBlock.updateBadgeAsync(request.tabId); + µb.updateBadgeAsync(request.tabId); break; case 'gotoPick': - chrome.tabs.executeScript(request.tabId, { file: 'js/element-picker.js' }); + µb.elementPickerExec(request.tabId); break; default: - return µBlock.messaging.defaultHandler(request, sender, callback); + return µb.messaging.defaultHandler(request, sender, callback); } callback(response); }; -µBlock.messaging.listen('popup.js', onMessage); +µb.messaging.listen('popup.js', onMessage); + +/******************************************************************************/ })(); @@ -102,8 +107,12 @@ var onMessage = function(request, sender, callback) { (function() { +/******************************************************************************/ + var µb = µBlock; +/******************************************************************************/ + var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { @@ -135,6 +144,8 @@ var onMessage = function(request, sender, callback) { µb.messaging.listen('contentscript-start.js', onMessage); +/******************************************************************************/ + })(); /******************************************************************************/ @@ -144,6 +155,8 @@ var onMessage = function(request, sender, callback) { (function() { +/******************************************************************************/ + var µb = µBlock; /******************************************************************************/ @@ -256,6 +269,8 @@ var onMessage = function(details, sender, callback) { µb.messaging.listen('contentscript-end.js', onMessage); +/******************************************************************************/ + })(); /******************************************************************************/ @@ -265,6 +280,12 @@ var onMessage = function(details, sender, callback) { (function() { +/******************************************************************************/ + +var µb = µBlock; + +/******************************************************************************/ + var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { @@ -276,28 +297,35 @@ var onMessage = function(request, sender, callback) { var response; switch ( request.what ) { - case 'i18n': + case 'elementPickerArguments': response = { - create: chrome.i18n.getMessage('pickerCreate'), - pick: chrome.i18n.getMessage('pickerPick'), - quit: chrome.i18n.getMessage('pickerQuit'), - netFilters: chrome.i18n.getMessage('pickerNetFilters'), - cosmeticFilters: chrome.i18n.getMessage('pickerCosmeticFilters') + i18n: { + create: chrome.i18n.getMessage('pickerCreate'), + pick: chrome.i18n.getMessage('pickerPick'), + quit: chrome.i18n.getMessage('pickerQuit'), + netFilters: chrome.i18n.getMessage('pickerNetFilters'), + cosmeticFilters: chrome.i18n.getMessage('pickerCosmeticFilters'), + cosmeticFiltersHint: chrome.i18n.getMessage('pickerCosmeticFiltersHint') + }, + targetElement: µb.elementPickerTarget }; + µb.elementPickerTarget = ''; break; case 'createUserFilter': - µBlock.appendUserFilters(request.filters); + µb.appendUserFilters(request.filters); break; default: - return µBlock.messaging.defaultHandler(request, sender, callback); + return µb.messaging.defaultHandler(request, sender, callback); } callback(response); }; -µBlock.messaging.listen('element-picker.js', onMessage); +µb.messaging.listen('element-picker.js', onMessage); + +/******************************************************************************/ })(); @@ -308,8 +336,13 @@ var onMessage = function(request, sender, callback) { (function() { +/******************************************************************************/ + +var µb = µBlock; + +/******************************************************************************/ + var getLists = function(callback) { - var µb = µBlock; var r = { available: null, current: µb.remoteBlacklists, @@ -334,8 +367,6 @@ var getLists = function(callback) { /******************************************************************************/ var onMessage = function(request, sender, callback) { - var µb = µBlock; - // Async switch ( request.what ) { case 'getLists': @@ -369,7 +400,9 @@ var onMessage = function(request, sender, callback) { callback(response); }; -µBlock.messaging.listen('3p-filters.js', onMessage); +µb.messaging.listen('3p-filters.js', onMessage); + +/******************************************************************************/ })(); @@ -380,9 +413,13 @@ var onMessage = function(request, sender, callback) { (function() { -var onMessage = function(request, sender, callback) { - var µb = µBlock; +/******************************************************************************/ +var µb = µBlock; + +/******************************************************************************/ + +var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'readUserFilters': @@ -406,7 +443,9 @@ var onMessage = function(request, sender, callback) { callback(response); }; -µBlock.messaging.listen('1p-filters.js', onMessage); +µb.messaging.listen('1p-filters.js', onMessage); + +/******************************************************************************/ })(); @@ -417,9 +456,13 @@ var onMessage = function(request, sender, callback) { (function() { -var onMessage = function(request, sender, callback) { - var µb = µBlock; +/******************************************************************************/ +var µb = µBlock; + +/******************************************************************************/ + +var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { default: @@ -446,7 +489,9 @@ var onMessage = function(request, sender, callback) { callback(response); }; -µBlock.messaging.listen('whitelist.js', onMessage); +µb.messaging.listen('whitelist.js', onMessage); + +/******************************************************************************/ })(); @@ -457,6 +502,12 @@ var onMessage = function(request, sender, callback) { (function() { +/******************************************************************************/ + +var µb = µBlock; + +/******************************************************************************/ + var getPageDetails = function(µb, tabId) { var r = { blockedRequests: [], @@ -510,8 +561,6 @@ var getPageDetails = function(µb, tabId) { /******************************************************************************/ var onMessage = function(request, sender, callback) { - var µb = µBlock; - // Async switch ( request.what ) { default: @@ -537,7 +586,9 @@ var onMessage = function(request, sender, callback) { callback(response); }; -µBlock.messaging.listen('stats.js', onMessage); +µb.messaging.listen('stats.js', onMessage); + +/******************************************************************************/ })(); @@ -548,9 +599,13 @@ var onMessage = function(request, sender, callback) { (function() { -var onMessage = function(request, sender, callback) { - var µb = µBlock; +/******************************************************************************/ +var µb = µBlock; + +/******************************************************************************/ + +var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { @@ -570,7 +625,9 @@ var onMessage = function(request, sender, callback) { callback(response); }; -µBlock.messaging.listen('about.js', onMessage); +µb.messaging.listen('about.js', onMessage); + +/******************************************************************************/ })(); diff --git a/js/settings.js b/js/settings.js index 55cdb7c71..a09e6674c 100644 --- a/js/settings.js +++ b/js/settings.js @@ -55,6 +55,12 @@ var onUserSettingsReceived = function(details) { .on('change', function(){ changeUserSettings('showIconBadge', this.checked); }); + + uDom('#context-menu-enabled') + .prop('checked', details.contextMenuEnabled === true) + .on('change', function(){ + changeUserSettings('contextMenuEnabled', this.checked); + }); }; messaging.ask({ what: 'userSettings' }, onUserSettingsReceived); diff --git a/js/storage.js b/js/storage.js index b5d6560be..202d755ad 100644 --- a/js/storage.js +++ b/js/storage.js @@ -74,7 +74,7 @@ var settingsLoaded = function(store) { µBlock.userSettings = store; if ( typeof callback === 'function' ) { - callback(); + callback(store); } }; @@ -666,7 +666,8 @@ }; // User settings are in memory - var onUserSettingsReady = function() { + var onUserSettingsReady = function(settings) { + µb.contextMenu.toggle(settings.contextMenuEnabled); µb.fromSelfie(onSelfieReady); }; diff --git a/js/ublock.js b/js/ublock.js index ddc000fad..ff12314c9 100644 --- a/js/ublock.js +++ b/js/ublock.js @@ -184,10 +184,8 @@ // Return all settings if none specified. µBlock.changeUserSettings = function(name, value) { - var µb = µBlock; - if ( name === undefined ) { - return µb.userSettings; + return this.userSettings; } if ( typeof name !== 'string' || name === '' ) { @@ -195,12 +193,12 @@ } // Do not allow an unknown user setting to be created - if ( µb.userSettings[name] === undefined ) { + if ( this.userSettings[name] === undefined ) { return; } if ( value === undefined ) { - return µb.userSettings[name]; + return this.userSettings[name]; } // Pre-change @@ -210,15 +208,18 @@ } // Change - µb.userSettings[name] = value; + this.userSettings[name] = value; // Post-change switch ( name ) { + case 'contextMenuEnabled': + this.contextMenu.toggle(value === true); + break; default: break; } - µb.saveUserSettings(); + this.saveUserSettings(); }; /******************************************************************************/ @@ -242,3 +243,10 @@ }; /******************************************************************************/ + +µBlock.elementPickerExec = function(tabId, targetElement) { + this.elementPickerTarget = targetElement || ''; + chrome.tabs.executeScript(tabId, { file: 'js/element-picker.js' }); +}; + +/******************************************************************************/ diff --git a/manifest.json b/manifest.json index 9b08b3a32..9c14eb6a5 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "µBlock", - "version": "0.6.4.0", + "version": "0.6.5.0", "description": "__MSG_extShortDesc__", "icons": { "16": "img/icon_16.png", @@ -38,6 +38,7 @@ "minimum_chrome_version": "22.0", "options_page": "dashboard.html", "permissions": [ + "contextMenus", "downloads", "storage", "tabs", diff --git a/settings.html b/settings.html index f478f3cb3..1b73bd59d 100644 --- a/settings.html +++ b/settings.html @@ -17,6 +17,7 @@ ul {