1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-07-05 11:37:01 +02:00

#1163: this implements "block elements by size"

This commit is contained in:
gorhill 2016-01-17 13:30:43 -05:00
parent 08d7ce96aa
commit 89148351e8
32 changed files with 1047 additions and 522 deletions

View File

@ -245,10 +245,6 @@ vAPI.tabs.registerListeners = function() {
}
};
var onUpdated = function(tabId, changeInfo, tab) {
onUpdatedClient(tabId, changeInfo, tab);
};
var onCommitted = function(details) {
if ( details.frameId !== 0 ) {
return;
@ -256,9 +252,18 @@ vAPI.tabs.registerListeners = function() {
onNavigationClient(details);
};
chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget);
var onActivated = function(details) {
vAPI.contextMenu.onMustUpdate(details.tabId);
};
var onUpdated = function(tabId, changeInfo, tab) {
onUpdatedClient(tabId, changeInfo, tab);
};
chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigate);
chrome.webNavigation.onCommitted.addListener(onCommitted);
chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget);
chrome.tabs.onActivated.addListener(onActivated);
chrome.tabs.onUpdated.addListener(onUpdated);
if ( typeof this.onClosed === 'function' ) {
@ -291,9 +296,7 @@ vAPI.tabs.get = function(tabId, callback) {
var onTabReceived = function(tabs) {
// https://code.google.com/p/chromium/issues/detail?id=410868#c8
if ( chrome.runtime.lastError ) {
/* noop */
}
void chrome.runtime.lastError;
callback(tabs[0]);
};
chrome.tabs.query({ active: true, currentWindow: true }, onTabReceived);
@ -553,6 +556,7 @@ vAPI.setIcon = function(tabId, iconStatus, badge) {
{ '19': 'img/browsericons/icon19-off.png', '38': 'img/browsericons/icon38-off.png' };
chrome.browserAction.setIcon({ tabId: tabId, path: iconPaths }, onIconReady);
vAPI.contextMenu.onMustUpdate(tabId);
};
/******************************************************************************/
@ -784,6 +788,51 @@ vAPI.net.registerListeners = function() {
var µb = µBlock;
var µburi = µb.URI;
// Chromium-based browsers understand only these network request types.
var validTypes = {
'main_frame': true,
'sub_frame': true,
'stylesheet': true,
'script': true,
'image': true,
'object': true,
'xmlhttprequest': true,
'other': true
};
var denormalizeTypes = function(aa) {
if ( aa.length === 0 ) {
return Object.keys(validTypes);
}
var out = [];
var i = aa.length,
type,
needOther = true;
while ( i-- ) {
type = aa[i];
if ( validTypes.hasOwnProperty(type) ) {
out.push(type);
}
if ( type === 'other' ) {
needOther = false;
}
}
if ( needOther ) {
out.push('other');
}
return out;
};
var headerValue = function(headers, name) {
var i = headers.length;
while ( i-- ) {
if ( headers[i].name.toLowerCase() === name ) {
return headers[i].value.trim();
}
}
return '';
};
var normalizeRequestDetails = function(details) {
µburi.set(details.url);
@ -801,34 +850,45 @@ vAPI.net.registerListeners = function() {
// https://github.com/chrisaljoudi/uBlock/issues/862
// If no transposition possible, transpose to `object` as per
// Chromium bug 410382 (see below)
if ( pos === -1 ) {
details.type = 'object';
return;
}
if ( pos !== -1 ) {
var ext = tail.slice(pos) + '.';
if ( '.eot.ttf.otf.svg.woff.woff2.'.indexOf(ext) !== -1 ) {
details.type = 'font';
return;
}
var ext = tail.slice(pos) + '.';
if ( '.eot.ttf.otf.svg.woff.woff2.'.indexOf(ext) !== -1 ) {
details.type = 'font';
return;
}
// Still need this because often behind-the-scene requests are wrongly
// categorized as 'other'
if ( '.ico.png.gif.jpg.jpeg.webp.'.indexOf(ext) !== -1 ) {
details.type = 'image';
return;
}
// https://code.google.com/p/chromium/issues/detail?id=410382
details.type = 'object';
};
if ( '.mp3.mp4.webm.'.indexOf(ext) !== -1 ) {
details.type = 'media';
return;
}
var headerValue = function(headers, name) {
var i = headers.length;
while ( i-- ) {
if ( headers[i].name.toLowerCase() === name ) {
return headers[i].value.trim();
// Still need this because often behind-the-scene requests are wrongly
// categorized as 'other'
if ( '.ico.png.gif.jpg.jpeg.webp.'.indexOf(ext) !== -1 ) {
details.type = 'image';
return;
}
}
return '';
// Try to extract type from response headers if present.
if ( details.responseHeaders ) {
var contentType = headerValue(details.responseHeaders, 'content-type');
if ( contentType.startsWith('font/') ) {
details.type = 'font';
return;
}
if ( contentType.startsWith('image/') ) {
details.type = 'image';
return;
}
if ( contentType.startsWith('audio/') || contentType.startsWith('video/') ) {
details.type = 'media';
return;
}
}
// https://code.google.com/p/chromium/issues/detail?id=410382
details.type = 'object';
};
var onBeforeRequestClient = this.onBeforeRequest.callback;
@ -839,13 +899,7 @@ vAPI.net.registerListeners = function() {
var onHeadersReceivedClient = this.onHeadersReceived.callback;
var onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0);
var onHeadersReceivedTypes = onHeadersReceivedClientTypes.slice(0);
if (
onHeadersReceivedTypes.length !== 0 &&
onHeadersReceivedTypes.indexOf('other') === -1
) {
onHeadersReceivedTypes.push('other');
}
var onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes);
var onHeadersReceived = function(details) {
normalizeRequestDetails(details);
// Hack to work around Chromium API limitations, where requests of
@ -857,21 +911,18 @@ vAPI.net.registerListeners = function() {
// `other` always becomes `object` when it can't be normalized into
// something else. Test case for "unfriendly" font URLs:
// https://www.google.com/fonts
if ( details.type === 'object' ) {
if ( headerValue(details.responseHeaders, 'content-type').startsWith('font/') ) {
details.type = 'font';
var r = onBeforeRequestClient(details);
if ( typeof r === 'object' && r.cancel === true ) {
return { cancel: true };
}
}
if (
onHeadersReceivedClientTypes.length !== 0 &&
onHeadersReceivedClientTypes.indexOf(details.type) === -1
) {
return;
if ( details.type === 'font' ) {
var r = onBeforeRequestClient(details);
if ( typeof r === 'object' && r.cancel === true ) {
return { cancel: true };
}
}
if (
onHeadersReceivedClientTypes.length !== 0 &&
onHeadersReceivedClientTypes.indexOf(details.type) === -1
) {
return;
}
return onHeadersReceivedClient(details);
};
@ -921,15 +972,46 @@ vAPI.net.registerListeners = function() {
/******************************************************************************/
vAPI.contextMenu = {
create: function(details, callback) {
this.menuId = details.id;
this.callback = callback;
chrome.contextMenus.create(details);
chrome.contextMenus.onClicked.addListener(this.callback);
_callback: null,
_entries: [],
_createEntry: function(entry) {
chrome.contextMenus.create(JSON.parse(JSON.stringify(entry)), function() {
void chrome.runtime.lastError;
});
},
remove: function() {
chrome.contextMenus.onClicked.removeListener(this.callback);
chrome.contextMenus.remove(this.menuId);
onMustUpdate: function() {},
setEntries: function(entries, callback) {
entries = entries || [];
var n = Math.max(this._entries.length, entries.length),
oldEntryId, newEntry;
for ( var i = 0; i < n; i++ ) {
oldEntryId = this._entries[i];
newEntry = entries[i];
if ( oldEntryId && newEntry ) {
if ( newEntry.id !== oldEntryId ) {
chrome.contextMenus.remove(oldEntryId);
this._createEntry(newEntry);
this._entries[i] = newEntry.id;
}
} else if ( oldEntryId && !newEntry ) {
chrome.contextMenus.remove(oldEntryId);
} else if ( !oldEntryId && newEntry ) {
this._createEntry(newEntry);
this._entries[i] = newEntry.id;
}
}
n = this._entries.length = entries.length;
callback = callback || null;
if ( callback === this._callback ) {
return;
}
if ( n !== 0 && callback !== null ) {
chrome.contextMenus.onClicked.addListener(callback);
this._callback = callback;
} else if ( n === 0 && this._callback !== null ) {
chrome.contextMenus.onClicked.removeListener(this._callback);
this._callback = null;
}
}
};

View File

@ -19,6 +19,8 @@
Home: https://github.com/gorhill/uBlock
*/
/* global HTMLDocument, XMLDocument */
// For non background pages
/******************************************************************************/
@ -29,6 +31,18 @@
/******************************************************************************/
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
// https://github.com/gorhill/uBlock/issues/1124
// Looks like `contentType` is on track to be standardized:
// https://dom.spec.whatwg.org/#concept-document-content-type
@ -43,14 +57,12 @@ var chrome = self.chrome;
// https://github.com/chrisaljoudi/uBlock/issues/456
// Already injected?
if ( vAPI.vapiClientInjected ) {
//console.debug('vapi-client.js already injected: skipping.');
if ( vAPI.sessionId ) {
return;
}
vAPI.vapiClientInjected = true;
vAPI.sessionId = String.fromCharCode(Date.now() % 26 + 97) +
Math.random().toString(36).slice(2);
Math.random().toString(36).slice(2);
vAPI.chrome = true;
/******************************************************************************/
@ -67,7 +79,6 @@ vAPI.shutdown = (function() {
};
var exec = function() {
//console.debug('Shutting down...');
var job;
while ( (job = jobs.pop()) ) {
job();
@ -89,28 +100,34 @@ vAPI.messaging = {
pendingCount: 0,
auxProcessId: 1,
onDisconnect: function() {
this.port = null;
this.close();
vAPI.shutdown.exec();
},
setup: function() {
try {
this.port = chrome.runtime.connect({name: vAPI.sessionId});
this.port = chrome.runtime.connect({name: vAPI.sessionId}) || null;
} catch (ex) {
}
if ( this.port === null ) {
//console.error("uBlock> Can't patch things up. It's over.");
vAPI.shutdown.exec();
return false;
}
this.port.onMessage.addListener(messagingConnector);
this.port.onDisconnect.addListener(this.onDisconnect.bind(this));
return true;
},
close: function() {
var port = this.port;
if ( port === null ) {
return;
if ( port !== null ) {
port.disconnect();
port.onMessage.removeListener(messagingConnector);
port.onDisconnect.removeListener(this.onDisconnect);
this.port = null;
}
this.port = null;
port.disconnect();
port.onMessage.removeListener(messagingConnector);
this.channels = {};
// service pending callbacks
var pending = this.pending, listener;

View File

@ -1440,6 +1440,7 @@ vAPI.setIcon = function(tabId, iconStatus, badge) {
if ( tabId === curTabId ) {
tb.updateState(win, tabId);
vAPI.contextMenu.onMustUpdate(tabId);
}
};
@ -2064,17 +2065,23 @@ var httpObserver = {
},
handleResponseHeaders: function(channel, URI, channelData) {
var type = this.typeMap[channelData[3]] || 'other';
if ( this.onHeadersReceivedTypes && this.onHeadersReceivedTypes.has(type) === false ) {
var requestType = this.typeMap[channelData[3]] || 'other';
if ( this.onHeadersReceivedTypes && this.onHeadersReceivedTypes.has(requestType) === false ) {
return;
}
// 'Content-Security-Policy' MUST come last in the array. Need to
// revised this eventually.
var responseHeaders = [];
var value = this.getResponseHeader(channel, 'Content-Security-Policy');
if ( value !== undefined ) {
responseHeaders.push({ name: 'Content-Security-Policy', value: value });
var value = channel.contentLength;
if ( value !== -1 ) {
responseHeaders.push({ name: 'Content-Length', value: value });
}
if ( requestType.endsWith('_frame') ) {
value = this.getResponseHeader(channel, 'Content-Security-Policy');
if ( value !== undefined ) {
responseHeaders.push({ name: 'Content-Security-Policy', value: value });
}
}
// https://github.com/gorhill/uBlock/issues/966
@ -2088,7 +2095,7 @@ var httpObserver = {
parentFrameId: channelData[1],
responseHeaders: responseHeaders,
tabId: channelData[2],
type: this.typeMap[channelData[3]] || 'other',
type: requestType,
url: URI.asciiSpec
});
@ -2179,7 +2186,7 @@ var httpObserver = {
// Carry data for behind-the-scene redirects
if ( channel instanceof Ci.nsIWritablePropertyBag ) {
channel.setProperty( this.REQDATAKEY, [0, -1, null, vAPI.noTabId, rawtype]);
channel.setProperty(this.REQDATAKEY, [0, -1, vAPI.noTabId, rawtype]);
}
return;
@ -3171,173 +3178,20 @@ if ( vAPI.toolbarButton.init !== null ) {
/******************************************************************************/
/******************************************************************************/
vAPI.contextMenu = {
contextMap: {
vAPI.contextMenu = (function() {
var clientCallback = null;
var clientEntries = [];
var contextMap = {
frame: 'inFrame',
link: 'onLink',
image: 'onImage',
audio: 'onAudio',
video: 'onVideo',
editable: 'onEditableArea'
}
};
/******************************************************************************/
vAPI.contextMenu.displayMenuItem = function({target}) {
var doc = target.ownerDocument;
var gContextMenu = doc.defaultView.gContextMenu;
if ( !gContextMenu.browser ) {
return;
}
var menuitem = doc.getElementById(vAPI.contextMenu.menuItemId);
var currentURI = gContextMenu.browser.currentURI;
// https://github.com/chrisaljoudi/uBlock/issues/105
// TODO: Should the element picker works on any kind of pages?
if ( !currentURI.schemeIs('http') && !currentURI.schemeIs('https') ) {
menuitem.setAttribute('hidden', true);
return;
}
var ctx = vAPI.contextMenu.contexts;
if ( !ctx ) {
menuitem.setAttribute('hidden', false);
return;
}
var ctxMap = vAPI.contextMenu.contextMap;
for ( var context of ctx ) {
if (
context === 'page' &&
!gContextMenu.onLink &&
!gContextMenu.onImage &&
!gContextMenu.onEditableArea &&
!gContextMenu.inFrame &&
!gContextMenu.onVideo &&
!gContextMenu.onAudio
) {
menuitem.setAttribute('hidden', false);
return;
}
if (
ctxMap.hasOwnProperty(context) &&
gContextMenu[ctxMap[context]]
) {
menuitem.setAttribute('hidden', false);
return;
}
}
menuitem.setAttribute('hidden', true);
};
/******************************************************************************/
vAPI.contextMenu.register = (function() {
var register = function(window) {
if ( canRegister(window) !== true ) {
return;
}
if ( !this.menuItemId ) {
return;
}
if ( vAPI.fennec ) {
// TODO https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/NativeWindow/contextmenus/add
/*var nativeWindow = doc.defaultView.NativeWindow;
contextId = nativeWindow.contextmenus.add(
this.menuLabel,
nativeWindow.contextmenus.linkOpenableContext,
this.onCommand
);*/
return;
}
// Already installed?
var doc = window.document;
if ( doc.getElementById(this.menuItemId) !== null ) {
return;
}
var contextMenu = doc.getElementById('contentAreaContextMenu');
// This can happen (Thunderbird).
if ( contextMenu === null ) {
return;
}
var menuitem = doc.createElement('menuitem');
menuitem.setAttribute('id', this.menuItemId);
menuitem.setAttribute('label', this.menuLabel);
menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon16.svg'));
menuitem.setAttribute('class', 'menuitem-iconic');
menuitem.addEventListener('command', this.onCommand);
contextMenu.addEventListener('popupshowing', this.displayMenuItem);
contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator'));
};
// https://github.com/gorhill/uBlock/issues/906
// Be sure document.readyState is 'complete': it could happen at launch
// time that we are called by vAPI.contextMenu.create() directly before
// the environment is properly initialized.
var canRegister = function(win) {
return win && win.document.readyState === 'complete';
};
return function(win) {
deferUntil(
canRegister.bind(null, win),
register.bind(this, win)
);
};
})();
/******************************************************************************/
vAPI.contextMenu.unregister = function(win) {
if ( !this.menuItemId ) {
return;
}
if ( vAPI.fennec ) {
// TODO
return;
}
var doc = win.document;
var menuitem = doc.getElementById(this.menuItemId);
// Not guarantee the menu item was actually registered.
if ( menuitem === null ) {
return;
}
var contextMenu = menuitem.parentNode;
menuitem.removeEventListener('command', this.onCommand);
contextMenu.removeEventListener('popupshowing', this.displayMenuItem);
contextMenu.removeChild(menuitem);
};
/******************************************************************************/
vAPI.contextMenu.create = function(details, callback) {
this.menuItemId = details.id;
this.menuLabel = details.title;
this.contexts = details.contexts;
if ( Array.isArray(this.contexts) && this.contexts.length ) {
this.contexts = this.contexts.indexOf('all') === -1 ? this.contexts : null;
} else {
// default in Chrome
this.contexts = ['page'];
}
this.onCommand = function() {
var onCommand = function() {
var gContextMenu = getOwnerWindow(this).gContextMenu;
var details = {
menuItemId: this.id
@ -3361,29 +3215,185 @@ vAPI.contextMenu.create = function(details, callback) {
details.linkUrl = gContextMenu.linkURL;
}
callback(details, {
clientCallback(details, {
id: tabWatcher.tabIdFromTarget(gContextMenu.browser),
url: gContextMenu.browser.currentURI.asciiSpec
});
};
for ( var win of winWatcher.getWindows() ) {
this.register(win);
}
};
var menuItemMatchesContext = function(contextMenu, clientEntry) {
if ( !clientEntry.contexts ) {
return false;
}
for ( var context of clientEntry.contexts ) {
if ( context === 'all' ) {
return true;
}
if (
contextMap.hasOwnProperty(context) &&
contextMenu[contextMap[context]]
) {
return true;
}
}
return false;
};
/******************************************************************************/
var onMenuShowing = function({target}) {
var doc = target.ownerDocument;
var gContextMenu = doc.defaultView.gContextMenu;
if ( !gContextMenu.browser ) {
return;
}
vAPI.contextMenu.remove = function() {
for ( var win of winWatcher.getWindows() ) {
this.unregister(win);
}
// https://github.com/chrisaljoudi/uBlock/issues/105
// TODO: Should the element picker works on any kind of pages?
var currentURI = gContextMenu.browser.currentURI,
isHTTP = currentURI.schemeIs('http') || currentURI.schemeIs('https'),
layoutChanged = false,
contextMenu = doc.getElementById('contentAreaContextMenu'),
newEntries = clientEntries,
oldMenuitems = contextMenu.querySelectorAll('[data-uBlock0="menuitem"]'),
newMenuitems = [],
n = Math.max(clientEntries.length, oldMenuitems.length),
menuitem, newEntry;
for ( var i = 0; i < n; i++ ) {
menuitem = oldMenuitems[i];
newEntry = newEntries[i];
if ( menuitem && !newEntry ) {
menuitem.parentNode.removeChild(menuitem);
menuitem.removeEventListener('command', onCommand);
menuitem = null;
layoutChanged = true;
} else if ( !menuitem && newEntry ) {
menuitem = doc.createElement('menuitem');
menuitem.setAttribute('data-uBlock0', 'menuitem');
menuitem.addEventListener('command', onCommand);
}
if ( !menuitem ) {
continue;
}
if ( menuitem.id !== newEntry.id ) {
menuitem.setAttribute('id', newEntry.id);
menuitem.setAttribute('label', newEntry.title);
layoutChanged = true;
}
menuitem.setAttribute('hidden', !isHTTP || !menuItemMatchesContext(gContextMenu, newEntry));
newMenuitems.push(menuitem);
}
// No changes?
if ( layoutChanged === false ) {
return;
}
// No entry: remove submenu if present.
var menu = contextMenu.querySelector('[data-uBlock0="menu"]');
if ( newMenuitems.length === 0 ) {
if ( menu !== null ) {
menu.parentNode.removeChild(menuitem);
}
return;
}
// Only one entry: no need for a submenu.
if ( newMenuitems.length === 1 ) {
if ( menu !== null ) {
menu.parentNode.removeChild(menu);
}
menuitem = newMenuitems[0];
menuitem.setAttribute('class', 'menuitem-iconic');
menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon16.svg'));
contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator'));
return;
}
// More than one entry: we need a submenu.
if ( menu === null ) {
menu = doc.createElement('menu');
menu.setAttribute('label', vAPI.app.name);
menu.setAttribute('data-uBlock0', 'menu');
menu.setAttribute('class', 'menu-iconic');
menu.setAttribute('image', vAPI.getURL('img/browsericons/icon16.svg'));
contextMenu.insertBefore(menu, doc.getElementById('inspect-separator'));
}
var menupopup = contextMenu.querySelector('[data-uBlock0="menupopup"]');
if ( menupopup === null ) {
menupopup = doc.createElement('menupopup');
menupopup.setAttribute('data-uBlock0', 'menupopup');
menu.appendChild(menupopup);
}
for ( i = 0; i < newMenuitems.length; i++ ) {
menuitem = newMenuitems[i];
menuitem.setAttribute('class', 'menuitem-non-iconic');
menuitem.removeAttribute('image');
menupopup.appendChild(menuitem);
}
};
this.menuItemId = null;
this.menuLabel = null;
this.contexts = null;
this.onCommand = null;
};
// https://github.com/gorhill/uBlock/issues/906
// Be sure document.readyState is 'complete': it could happen at launch
// time that we are called by vAPI.contextMenu.create() directly before
// the environment is properly initialized.
var canRegister = function(win) {
return win && win.document.readyState === 'complete';
};
var register = function(window) {
if ( canRegister(window) !== true ) {
return;
}
var contextMenu = window.document.getElementById('contentAreaContextMenu');
if ( contextMenu === null ) {
return;
}
contextMenu.addEventListener('popupshowing', onMenuShowing);
};
var registerAsync = function(win) {
// TODO https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/NativeWindow/contextmenus/add
// var nativeWindow = doc.defaultView.NativeWindow;
// contextId = nativeWindow.contextmenus.add(
// this.menuLabel,
// nativeWindow.contextmenus.linkOpenableContext,
// this.onCommand
// );
if ( vAPI.fennec ) {
return;
}
deferUntil(
canRegister.bind(null, win),
register.bind(null, win)
);
};
var unregister = function(win) {
// TODO
if ( vAPI.fennec ) {
return;
}
var contextMenu = win.document.getElementById('contentAreaContextMenu');
if ( contextMenu !== null ) {
contextMenu.removeEventListener('popupshowing', onMenuShowing);
}
var menuitems = win.document.querySelectorAll('[data-uBlock0]'),
menuitem;
for ( var i = 0; i < menuitems.length; i++ ) {
menuitem = menuitems[i];
menuitem.parentNode.removeChild(menuitem);
menuitem.removeEventListener('command', onCommand);
}
};
var setEntries = function(entries, callback) {
clientEntries = entries || [];
clientCallback = callback || null;
};
return {
onMustUpdate: function() {},
register: registerAsync,
unregister: unregister,
setEntries: setEntries
};
})();
/******************************************************************************/
/******************************************************************************/

View File

@ -19,7 +19,9 @@
Home: https://github.com/gorhill/uBlock
*/
/* global addMessageListener, removeMessageListener, sendAsyncMessage, outerShutdown */
/* global HTMLDocument, XMLDocument,
addMessageListener, removeMessageListener, sendAsyncMessage, outerShutdown
*/
// For non background pages
@ -29,6 +31,18 @@
'use strict';
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
/******************************************************************************/
// Not all sandboxes are given an rpc function, so assign a dummy one if it is

View File

@ -77,19 +77,19 @@
},
"popupTipNoPopups":{
"message":"Toggle the blocking of all popups for this site",
"description":"English: Toggle the blocking of all popups for this site"
"description":"Tooltip for the no-popups per-site switch"
},
"popupTipNoStrictBlocking":{
"message":"Toggle strict blocking for this site",
"description":"English: Toggle strict blocking for this site"
"popupTipNoLargeMedia":{
"message":"Toggle the blocking of large media elements for this site",
"description":"Tooltip for the no-large-media per-site switch"
},
"popupTipNoCosmeticFiltering":{
"message":"Toggle cosmetic filtering for this site",
"description":"English: Toggle cosmetic filtering for this site"
"description":"Tooltip for the no-cosmetic-filtering per-site switch"
},
"popupTipNoRemoteFonts":{
"message":"Toggle the blocking of remote fonts for this site",
"description":"English: Toggle the blocking of remote fonts for this site"
"description":"Tooltip for the no-remote-fonts per-site switch"
},
"popupTipGlobalRules":{
"message":"Global rules: this column is for rules which apply to all sites.",
@ -215,9 +215,25 @@
"message":"Prevent WebRTC from leaking local IP addresses",
"description":"English: "
},
"settingsExperimentalPrompt":{
"message":"Enable experimental features (<a href='https:\/\/github.com\/gorhill\/uBlock\/wiki\/Experimental-features'>About<\/a>)",
"description":"English: Enable experimental features"
"settingPerSiteSwitchGroup":{
"message":"Default behavior",
"description": ""
},
"settingPerSiteSwitchGroupSynopsis":{
"message":"These default behaviors can be overriden on a per-site basis",
"description": ""
},
"settingsNoCosmeticFilteringPrompt":{
"message":"Disable cosmetic filtering",
"description": ""
},
"settingsNoLargeMediaPrompt":{
"message":"Block media elements larger than {{input:number}} kB",
"description": ""
},
"settingsNoRemoteFontsPrompt":{
"message":"Block remote fonts",
"description": ""
},
"settingsStorageUsed":{
"message":"Storage used: {{value}} bytes",
@ -233,23 +249,23 @@
},
"3pListsOfBlockedHostsPrompt":{
"message":"{{netFilterCount}} network filters {{cosmeticFilterCount}} cosmetic filters from:",
"description":"English: {{netFilterCount}} network filters {{cosmeticFilterCount}} cosmetic filters from:"
"description":"Appears at the top of the _3rd-party filters_ pane"
},
"3pListsOfBlockedHostsPerListStats":{
"message":"{{used}} used out of {{total}}",
"description":"English: {{used}} used out of {{total}}"
"description":"Appears aside each filter list in the _3rd-party filters_ pane"
},
"3pAutoUpdatePrompt1":{
"message":"Auto-update filter lists.",
"description":"English: Auto-update filter lists."
"description":"A checkbox in the _3rd-party filters_ pane"
},
"3pUpdateNow":{
"message":"Update now",
"description":"English: Update now"
"description":"A button in the in the _3rd-party filters_ pane"
},
"3pPurgeAll":{
"message":"Purge all caches",
"description":"English: Purge all caches"
"description":"A button in the in the _3rd-party filters_ pane"
},
"3pParseAllABPHideFiltersPrompt1":{
"message":"Parse and enforce cosmetic filters.",
@ -651,6 +667,10 @@
"message": "bytes",
"description": ""
},
"contextMenuTemporarilyAllowLargeMediaElements": {
"message": "Temporarily allow large media elements",
"description": "A context menu entry, present when large media elements have been blocked on the current site"
},
"dummy":{
"message":"This entry must be the last one",
"description":"so we dont need to deal with comma for last entry"

View File

@ -34,10 +34,17 @@ div > p:first-child {
div > p:last-child {
margin-bottom: 0;
}
input[type="number"] {
width: 5em;
}
.para {
width: 40em;
}
.synopsis {
font-size: small;
opacity: 0.8;
}
.whatisthis {
margin: 0 0 0 8px;
border: 0;

View File

@ -176,7 +176,7 @@ body.off #switch .fa {
#extraTools > span {
cursor: pointer;
font-size: 18px;
margin: 0 0.4em;
margin: 0 0.5em;
position: relative;
}
#extraTools > span > span.badge {

View File

@ -61,6 +61,7 @@ return {
externalLists: defaultExternalLists,
firewallPaneMinimized: true,
hyperlinkAuditingDisabled: true,
largeMediaSize: 50,
parseAllABPHideFilters: true,
prefetchingDisabled: true,
requestLogMaxEntries: 1000,

View File

@ -19,8 +19,6 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, HTMLDocument, XMLDocument */
/******************************************************************************/
// Injected into content pages
@ -31,18 +29,6 @@
/******************************************************************************/
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
// I've seen this happens on Firefox
if ( window.location === null ) {
return;

View File

@ -20,7 +20,6 @@
*/
/* jshint multistr: true */
/* global vAPI, HTMLDocument, XMLDocument */
/******************************************************************************/
@ -34,18 +33,6 @@
/******************************************************************************/
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
// This can happen
if ( typeof vAPI !== 'object' ) {
//console.debug('contentscript-start.js > vAPI not found');

View File

@ -1,7 +1,7 @@
/*******************************************************************************
µBlock - a browser extension to block requests.
Copyright (C) 2014 Raymond Hill
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-2015 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
@ -19,26 +19,19 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, µBlock */
/******************************************************************************/
µBlock.contextMenu = (function() {
'use strict';
/******************************************************************************/
// New namespace
µBlock.contextMenu = (function() {
/******************************************************************************/
var µb = µBlock;
var enabled = false;
/******************************************************************************/
var onContextMenuClicked = function(details, tab) {
if ( details.menuItemId !== 'blockElement' ) {
return;
}
var onBlockElement = function(details, tab) {
if ( tab === undefined ) {
return;
}
@ -69,29 +62,90 @@ var onContextMenuClicked = function(details, tab) {
/******************************************************************************/
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: vAPI.i18n('pickerContextMenuEntry'),
contexts: ['page', 'editable', 'frame', 'link', 'image', 'video'],
documentUrlPatterns: ['https://*/*', 'http://*/*']
};
var onTemporarilyAllowLargeMediaElements = function(details, tab) {
if ( tab === undefined ) {
return;
}
var pageStore = µb.pageStoreFromTabId(tab.id);
if ( pageStore === null ) {
return;
}
pageStore.temporarilyAllowLargeMediaElements();
};
if ( on === true && enabled === false ) {
vAPI.contextMenu.create(menuCreateDetails, onContextMenuClicked);
enabled = true;
} else if ( on !== true && enabled === true ) {
vAPI.contextMenu.remove();
enabled = false;
/******************************************************************************/
var onEntryClicked = function(details, tab) {
if ( details.menuItemId === 'uBlock0-blockElement' ) {
return onBlockElement(details, tab);
}
if ( details.menuItemId === 'uBlock0-temporarilyAllowLargeMediaElements' ) {
return onTemporarilyAllowLargeMediaElements(details, tab);
}
};
/******************************************************************************/
var menuEntries = [
{
id: 'uBlock0-blockElement',
title: vAPI.i18n('pickerContextMenuEntry'),
contexts: ['all'],
documentUrlPatterns: ['https://*/*', 'http://*/*']
},
{
id: 'uBlock0-temporarilyAllowLargeMediaElements',
title: vAPI.i18n('contextMenuTemporarilyAllowLargeMediaElements'),
contexts: ['all'],
documentUrlPatterns: ['https://*/*', 'http://*/*']
}
];
/******************************************************************************/
var update = function(tabId) {
var newBits = 0;
if ( µb.userSettings.contextMenuEnabled && tabId !== null ) {
var pageStore = µb.pageStoreFromTabId(tabId);
if ( pageStore ) {
newBits |= 0x01;
if ( pageStore.largeMediaCount !== 0 ) {
newBits |= 0x02;
}
}
}
if ( newBits === currentBits ) {
return;
}
currentBits = newBits;
var usedEntries = [];
if ( newBits & 0x01 ) {
usedEntries.push(menuEntries[0]);
}
if ( newBits & 0x02 ) {
usedEntries.push(menuEntries[1]);
}
vAPI.contextMenu.setEntries(usedEntries, onEntryClicked);
};
var currentBits = 0;
vAPI.contextMenu.onMustUpdate = update;
/******************************************************************************/
return {
toggle: toggleMenu
update: function(tabId) {
if ( µb.userSettings.contextMenuEnabled && tabId === undefined ) {
vAPI.tabs.get(null, function(tab) {
if ( tab ) {
update(tab.id);
}
});
return;
}
update(tabId);
}
};
/******************************************************************************/

View File

@ -40,7 +40,8 @@ var switchBitOffsets = {
'no-strict-blocking': 0,
'no-popups': 2,
'no-cosmetic-filtering': 4,
'no-remote-fonts': 6
'no-remote-fonts': 6,
'no-large-media': 8
};
var fromLegacySwitchNames = {

View File

@ -128,10 +128,6 @@ var onMessage = function(request, sender, callback) {
response = getDomainNames(request.targets);
break;
case 'getUserSettings':
response = µb.userSettings;
break;
case 'launchElementPicker':
// Launched from some auxiliary pages, clear context menu coords.
µb.mouseX = µb.mouseY = -1;
@ -277,6 +273,7 @@ var getFirewallRules = function(srcHostname, desHostnames) {
var popupDataFromTabId = function(tabId, tabTitle) {
var tabContext = µb.tabContextManager.mustLookup(tabId);
var rootHostname = tabContext.rootHostname;
var r = {
advancedUserEnabled: µb.userSettings.advancedUserEnabled,
appName: vAPI.app.name,
@ -290,7 +287,7 @@ var popupDataFromTabId = function(tabId, tabTitle) {
netFilteringSwitch: false,
rawURL: tabContext.rawURL,
pageURL: tabContext.normalURL,
pageHostname: tabContext.rootHostname,
pageHostname: rootHostname,
pageDomain: tabContext.rootDomain,
pageAllowedRequestCount: 0,
pageBlockedRequestCount: 0,
@ -307,21 +304,22 @@ var popupDataFromTabId = function(tabId, tabTitle) {
r.netFilteringSwitch = pageStore.getNetFilteringSwitch();
r.hostnameDict = getHostnameDict(pageStore.hostnameToCountMap);
r.contentLastModified = pageStore.contentLastModified;
r.firewallRules = getFirewallRules(tabContext.rootHostname, r.hostnameDict);
r.canElementPicker = tabContext.rootHostname.indexOf('.') !== -1;
r.noPopups = µb.hnSwitches.evaluateZ('no-popups', tabContext.rootHostname);
r.noStrictBlocking = µb.hnSwitches.evaluateZ('no-strict-blocking', tabContext.rootHostname);
r.noCosmeticFiltering = µb.hnSwitches.evaluateZ('no-cosmetic-filtering', tabContext.rootHostname);
r.noRemoteFonts = µb.hnSwitches.evaluateZ('no-remote-fonts', tabContext.rootHostname);
r.remoteFontCount = pageStore.remoteFontCount;
r.firewallRules = getFirewallRules(rootHostname, r.hostnameDict);
r.canElementPicker = rootHostname.indexOf('.') !== -1;
r.noPopups = µb.hnSwitches.evaluateZ('no-popups', rootHostname);
r.popupBlockedCount = pageStore.popupBlockedCount;
r.noCosmeticFiltering = µb.hnSwitches.evaluateZ('no-cosmetic-filtering', rootHostname);
r.noLargeMedia = µb.hnSwitches.evaluateZ('no-large-media', rootHostname);
r.largeMediaCount = pageStore.largeMediaCount;
r.noRemoteFonts = µb.hnSwitches.evaluateZ('no-remote-fonts', rootHostname);
r.remoteFontCount = pageStore.remoteFontCount;
} else {
r.hostnameDict = {};
r.firewallRules = getFirewallRules();
}
r.matrixIsDirty = !µb.sessionFirewall.hasSameRules(
µb.permanentFirewall,
tabContext.rootHostname,
rootHostname,
r.hostnameDict
);
return r;
@ -1342,48 +1340,6 @@ vAPI.messaging.listen('logger-ui.js', onMessage);
/******************************************************************************/
/******************************************************************************/
// subscriber.js
(function() {
'use strict';
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'subscriberData':
response = {
confirmStr: vAPI.i18n('subscriberConfirm'),
externalLists: µBlock.userSettings.externalLists
};
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('subscriber.js', onMessage);
/******************************************************************************/
})();
/******************************************************************************/
/******************************************************************************/
// document-blocked.js
(function() {
@ -1461,6 +1417,7 @@ var logCosmeticFilters = function(tabId, details) {
var onMessage = function(request, sender, callback) {
var tabId = sender && sender.tab ? sender.tab.id : 0;
var pageStore = µb.pageStoreFromTabId(tabId);
// Async
switch ( request.what ) {
@ -1473,8 +1430,7 @@ var onMessage = function(request, sender, callback) {
switch ( request.what ) {
case 'liveCosmeticFilteringData':
var pageStore = µb.pageStoreFromTabId(tabId);
if ( pageStore ) {
if ( pageStore !== null ) {
pageStore.hiddenElementCount = request.filteredElementCount;
}
break;
@ -1483,6 +1439,19 @@ var onMessage = function(request, sender, callback) {
logCosmeticFilters(tabId, request);
break;
case 'temporarilyAllowLargeMediaElement':
if ( pageStore !== null ) {
pageStore.allowLargeMediaElementsUntil = Date.now() + 2000;
}
break;
case 'subscriberData':
response = {
confirmStr: vAPI.i18n('subscriberConfirm'),
externalLists: µBlock.userSettings.externalLists
};
break;
default:
return vAPI.messaging.UNHANDLED;
}

View File

@ -294,6 +294,16 @@ PageStore.factory = function(tabId) {
PageStore.prototype.init = function(tabId) {
var tabContext = µb.tabContextManager.mustLookup(tabId);
this.tabId = tabId;
// If we are navigating from-to same site, remember whether large
// media elements were temporarily allowed.
if (
typeof this.allowLargeMediaElementsUntil !== 'number' ||
tabContext.rootHostname !== this.tabHostname
) {
this.allowLargeMediaElementsUntil = 0;
}
this.tabHostname = tabContext.rootHostname;
this.title = tabContext.rawURL;
this.rawURL = tabContext.rawURL;
@ -305,6 +315,8 @@ PageStore.prototype.init = function(tabId) {
this.hiddenElementCount = ''; // Empty string means "unknown"
this.remoteFontCount = 0;
this.popupBlockedCount = 0;
this.largeMediaCount = 0;
this.largeMediaTimer = null;
this.netFilteringCache = NetFilteringResultCache.factory();
// Support `elemhide` filter option. Called at this point so the required
@ -354,6 +366,10 @@ PageStore.prototype.reuse = function(context) {
}
// A new page is completely reloaded from scratch, reset all.
if ( this.largeMediaTimer !== null ) {
clearTimeout(this.largeMediaTimer);
this.largeMediaTimer = null;
}
this.disposeFrameStores();
this.netFilteringCache = this.netFilteringCache.dispose();
this.init(this.tabId);
@ -373,6 +389,11 @@ PageStore.prototype.dispose = function() {
this.title = '';
this.rawURL = '';
this.hostnameToCountMap = null;
this.allowLargeMediaElementsUntil = 0;
if ( this.largeMediaTimer !== null ) {
clearTimeout(this.largeMediaTimer);
this.largeMediaTimer = null;
}
this.disposeFrameStores();
this.netFilteringCache = this.netFilteringCache.dispose();
if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
@ -469,6 +490,32 @@ PageStore.prototype.toggleNetFilteringSwitch = function(url, scope, state) {
/******************************************************************************/
PageStore.prototype.logLargeMedia = (function() {
var injectScript = function() {
this.largeMediaTimer = null;
µb.scriptlets.injectDeep(
this.tabId,
'load-large-media-interactive'
);
µb.contextMenu.update(this.tabId);
};
return function() {
this.largeMediaCount += 1;
if ( this.largeMediaTimer === null ) {
this.largeMediaTimer = vAPI.setTimeout(injectScript.bind(this), 500);
}
};
})();
PageStore.prototype.temporarilyAllowLargeMediaElements = function() {
this.largeMediaCount = 0;
µb.contextMenu.update(this.tabId);
this.allowLargeMediaElementsUntil = Date.now() + 86400000;
µb.scriptlets.injectDeep(this.tabId, 'load-large-media-all');
};
/******************************************************************************/
PageStore.prototype.filterRequest = function(context) {
var requestType = context.requestType;

View File

@ -160,6 +160,7 @@ var hashFromPopupData = function(reset) {
}
hasher.sort();
hasher.push(uDom('body').hasClass('off'));
hasher.push(uDom.nodeFromId('no-large-media').classList.contains('on'));
hasher.push(uDom.nodeFromId('no-cosmetic-filtering').classList.contains('on'));
hasher.push(uDom.nodeFromId('no-remote-fonts').classList.contains('on'));
@ -459,7 +460,7 @@ var renderPopup = function() {
// Extra tools
uDom.nodeFromId('no-popups').classList.toggle('on', popupData.noPopups === true);
uDom.nodeFromId('no-strict-blocking').classList.toggle('on', popupData.noStrictBlocking === true);
uDom.nodeFromId('no-large-media').classList.toggle('on', popupData.noLargeMedia === true);
uDom.nodeFromId('no-cosmetic-filtering').classList.toggle('on', popupData.noCosmeticFiltering === true);
uDom.nodeFromId('no-remote-fonts').classList.toggle('on', popupData.noRemoteFonts === true);
@ -468,6 +469,11 @@ var renderPopup = function() {
uDom.nodeFromSelector('#no-popups > span.badge')
.textContent = total ? total.toLocaleString() : '';
// Report large media count on badge
total = popupData.largeMediaCount;
uDom.nodeFromSelector('#no-large-media > span.badge')
.textContent = total ? total.toLocaleString() : '';
// Report remote font count on badge
total = popupData.remoteFontCount;
uDom.nodeFromSelector('#no-remote-fonts > span.badge')
@ -733,18 +739,18 @@ var revertFirewallRules = function() {
/******************************************************************************/
var toggleHostnameSwitch = function() {
var elem = uDom(this);
var switchName = elem.attr('id');
var toggleHostnameSwitch = function(ev) {
var target = ev.currentTarget;
var switchName = target.getAttribute('id');
if ( !switchName ) {
return;
}
elem.toggleClass('on');
target.classList.toggle('on');
messager.send({
what: 'toggleHostnameSwitch',
name: switchName,
hostname: popupData.pageHostname,
state: elem.hasClass('on'),
state: target.classList.contains('on'),
tabId: popupData.tabId
});
hashFromPopupData();

View File

@ -19,8 +19,6 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, HTMLDocument, XMLDocument */
/******************************************************************************/
(function() {
@ -29,19 +27,6 @@
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
// This can happen
if ( typeof vAPI !== 'object' ) {
return;
}

View File

@ -19,8 +19,6 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, HTMLDocument, XMLDocument */
/******************************************************************************/
(function() {
@ -29,21 +27,7 @@
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
// This can happen
if ( typeof vAPI !== 'object' ) {
//console.debug('cosmetic-off.js > no vAPI');
return;
}

View File

@ -19,8 +19,6 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, HTMLDocument, XMLDocument */
/******************************************************************************/
(function() {
@ -29,21 +27,7 @@
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
// This can happen
if ( typeof vAPI !== 'object' ) {
//console.debug('cosmetic-on.js > no vAPI');
return;
}

View File

@ -19,8 +19,6 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, HTMLDocument, XMLDocument */
/******************************************************************************/
(function() {
@ -29,21 +27,7 @@
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
// This can happen
if ( typeof vAPI !== 'object' ) {
//console.debug('cosmetic-survey.js > vAPI not found');
return;
}

View File

@ -19,8 +19,6 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, HTMLDocument, XMLDocument */
/******************************************************************************/
/******************************************************************************/
@ -30,19 +28,6 @@
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
}
// This can happen
if ( typeof vAPI !== 'object' ) {
return;
}

View File

@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
/* global self, vAPI, CSS, HTMLDocument, XMLDocument */
/* global CSS */
/******************************************************************************/
/******************************************************************************/
@ -66,16 +66,16 @@
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit === 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
index === 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
firstCodeUnit === 0x002D
)
) {
// http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point
@ -89,8 +89,8 @@
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit === 0x002D ||
codeUnit === 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
@ -120,20 +120,10 @@
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
// https://github.com/chrisaljoudi/uBlock/issues/1528
// A XMLDocument can be a valid HTML document.
if (
document instanceof XMLDocument === false ||
document.createElement('div') instanceof HTMLDivElement === false
) {
return;
}
if ( typeof vAPI !== 'object' ) {
return;
}
/******************************************************************************/
// don't run in frames
if ( window.top !== window ) {
return;

View File

@ -0,0 +1,69 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2015 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
*/
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
// For all media resources which have failed to load, trigger a reload.
var elems, i, elem, src;
// <audio> and <video> elements.
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
elems = document.querySelectorAll('audio,video');
i = elems.length;
while ( i-- ) {
elem = elems[i];
if ( elem.error !== null ) {
elem.load();
}
}
// <img> elements.
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
elems = document.querySelectorAll('img');
i = elems.length;
while ( i-- ) {
elem = elems[i];
if (
typeof elem.naturalWidth !== 'number' ||
elem.naturalWidth === 0 ||
typeof elem.naturalHeight !== 'number' ||
elem.naturalHeight === 0
) {
src = elem.getAttribute('src') || '';
elem.removeAttribute('src');
elem.setAttribute('src', src);
}
}
/******************************************************************************/
})();
/******************************************************************************/

View File

@ -0,0 +1,222 @@
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2015 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
*/
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
// This can happen
if ( typeof vAPI !== 'object' || vAPI.loadLargeMediaInteractive === true ) {
return;
}
/******************************************************************************/
var largeMediaElementAttribute = 'data-' + vAPI.sessionId;
var largeMediaElementSelector =
':root audio[' + largeMediaElementAttribute + '],\n' +
':root img[' + largeMediaElementAttribute + '],\n' +
':root video[' + largeMediaElementAttribute + ']';
/******************************************************************************/
var mediaNotLoaded = function(elem) {
var src = elem.getAttribute('src') || '';
if ( src === '' ) {
return false;
}
switch ( elem.localName ) {
case 'audio':
case 'video':
return elem.error !== null;
case 'img':
return elem.offsetWidth !== 0 && elem.offsetHeight !== 0 &&
(elem.naturalWidth === 0 || elem.naturalHeight === 0);
default:
break;
}
return false;
};
/******************************************************************************/
// For all media resources which have failed to load, trigger a reload.
// <audio> and <video> elements.
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
var surveyMissingMediaElements = function() {
var largeMediaElementCount = 0;
var elems = document.querySelectorAll('audio,img,video');
var i = elems.length, elem;
while ( i-- ) {
elem = elems[i];
if ( mediaNotLoaded(elem) ) {
elem.setAttribute(largeMediaElementAttribute, '');
largeMediaElementCount += 1;
}
}
return largeMediaElementCount;
};
if ( surveyMissingMediaElements() === 0 ) {
return;
}
vAPI.loadLargeMediaInteractive = true;
// Insert custom style tag.
var styleTag = document.createElement('style');
styleTag.setAttribute('type', 'text/css');
styleTag.textContent = [
largeMediaElementSelector + ' {',
'border: 1px dotted red !important;',
'box-sizing: border-box !important;',
'cursor: zoom-in !important;',
'display: inline-block;',
'font-size: 1em !important;',
'min-height: 1em !important;',
'min-width: 1em !important;',
'}'
].join('\n');
document.head.appendChild(styleTag);
/******************************************************************************/
var messager = vAPI.messaging.channel('scriptlets');
/******************************************************************************/
var stayOrLeave = (function() {
var timer = null;
var timeoutHandler = function(leaveNow) {
timer = null;
if ( leaveNow !== true ) {
if (
document.querySelector(largeMediaElementSelector) !== null ||
surveyMissingMediaElements() !== 0
) {
return;
}
}
// Leave
if ( styleTag !== null ) {
styleTag.parentNode.removeChild(styleTag);
styleTag = null;
}
vAPI.loadLargeMediaInteractive = false;
document.removeEventListener('error', onLoadError, true);
document.removeEventListener('click', onMouseClick, true);
if ( messager !== null ) {
messager.close();
messager = null;
}
};
return function(leaveNow) {
if ( timer !== null ) {
clearTimeout(timer);
}
if ( leaveNow ) {
timeoutHandler(true);
} else {
timer = vAPI.setTimeout(timeoutHandler, 5000);
}
};
})();
/******************************************************************************/
var onMouseClick = function(ev) {
if ( ev.button !== 0 ) {
return;
}
var elem = ev.target;
if ( elem.matches(largeMediaElementSelector) === false ) {
return;
}
if ( mediaNotLoaded(elem) === false ) {
elem.removeAttribute(largeMediaElementAttribute);
stayOrLeave();
return;
}
var src = elem.getAttribute('src');
elem.removeAttribute('src');
var onLargeMediaElementAllowed = function() {
elem.setAttribute('src', src);
elem.removeAttribute(largeMediaElementAttribute);
stayOrLeave();
};
messager.send({
what: 'temporarilyAllowLargeMediaElement'
}, onLargeMediaElementAllowed);
ev.preventDefault();
ev.stopPropagation();
};
document.addEventListener('click', onMouseClick, true);
/******************************************************************************/
var onLoad = function(ev) {
var elem = ev.target;
if ( elem.hasAttribute(largeMediaElementAttribute) ) {
elem.removeAttribute(largeMediaElementAttribute);
stayOrLeave();
}
};
document.addEventListener('load', onLoad, true);
/******************************************************************************/
var onLoadError = function(ev) {
var elem = ev.target;
if ( mediaNotLoaded(elem) ) {
elem.setAttribute(largeMediaElementAttribute, '');
}
};
document.addEventListener('error', onLoadError, true);
/******************************************************************************/
vAPI.shutdown.add(function() {
stayOrLeave(true);
});
/******************************************************************************/
})();
/******************************************************************************/

View File

@ -52,14 +52,14 @@ if ( typeof vAPI !== 'object' ) {
if (
document.querySelector('a[href^="abp:"],a[href^="https://subscribe.adblockplus.org/?"]') === null &&
window.location.href.startsWith('https://github.com/gorhill/uBlock/wiki/Filter-lists-from-around-the-web') === false
window.location.href.lastIndexOf('https://github.com/gorhill/uBlock/wiki/Filter-lists-from-around-the-web', 0) !== 0
) {
return;
}
/******************************************************************************/
var messager = vAPI.messaging.channel('subscriber.js');
var messager = vAPI.messaging.channel('scriptlets');
/******************************************************************************/

View File

@ -159,18 +159,41 @@ var changeUserSettings = function(name, value) {
/******************************************************************************/
var onInputChanged = function(ev) {
var input = ev.target;
var name = this.getAttribute('data-setting-name');
var value = input.value;
if ( name === 'largeMediaSize' ) {
value = Math.min(Math.max(Math.floor(parseInt(value, 10) || 0), 0), 1000000);
}
if ( value !== input.value ) {
input.value = value;
}
changeUserSettings(name, value);
};
/******************************************************************************/
// TODO: use data-* to declare simple settings
var onUserSettingsReceived = function(details) {
uDom('[data-setting-type="bool"]').forEach(function(uNode) {
var input = uNode.nodeAt(0);
uNode.prop('checked', details[input.getAttribute('data-setting-name')] === true)
uNode.prop('checked', details[uNode.attr('data-setting-name')] === true)
.on('change', function() {
changeUserSettings(
this.getAttribute('data-setting-name'),
this.checked
);
});
changeUserSettings(
this.getAttribute('data-setting-name'),
this.checked
);
});
});
uDom('[data-setting-name="noLargeMedia"] ~ label:first-of-type > input[type="number"]')
.attr('data-setting-name', 'largeMediaSize')
.attr('data-setting-type', 'input');
uDom('[data-setting-type="input"]').forEach(function(uNode) {
uNode.val(details[uNode.attr('data-setting-name')])
.on('change', onInputChanged);
});
uDom('#export').on('click', exportToFile);

View File

@ -76,7 +76,7 @@ var onAllReady = function() {
//quickProfiler.stop(0);
vAPI.onLoadAllCompleted();
µb.contextMenu.update(null);
µb.firstInstall = false;
};
@ -169,7 +169,6 @@ var onUserSettingsReady = function(fetched) {
// Disabling local mirroring for the time being
userSettings.experimentalEnabled = false;
µb.contextMenu.toggle(userSettings.contextMenuEnabled);
vAPI.browserSettings.set({
'hyperlinkAuditing': !userSettings.hyperlinkAuditingDisabled,
'prefetching': !userSettings.prefetchingDisabled,

View File

@ -784,7 +784,7 @@ vAPI.tabs.registerListeners();
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
tabIdToTimer[tabId] = vAPI.setTimeout(updateBadge.bind(this, tabId), 500);
tabIdToTimer[tabId] = vAPI.setTimeout(updateBadge.bind(this, tabId), 666);
};
})();

View File

@ -140,7 +140,7 @@ var onBeforeRequest = function(details) {
tabId,
'redirect',
'rr:' + µb.redirectEngine.resourceNameRegister,
'redirect',
requestType,
requestURL,
requestContext.rootHostname,
requestContext.pageHostname
@ -336,7 +336,9 @@ var onBeforeBehindTheSceneRequest = function(details) {
/******************************************************************************/
// To handle `inline-script`.
// To handle:
// - inline script tags
// - media elements larger than n kB
var onHeadersReceived = function(details) {
// Do not interfere with behind-the-scene requests.
@ -354,6 +356,10 @@ var onHeadersReceived = function(details) {
if ( requestType === 'sub_frame' ) {
return onFrameHeadersReceived(details);
}
if ( requestType === 'image' || requestType === 'media' ) {
return foilLargeMediaElement(details);
}
};
/******************************************************************************/
@ -449,6 +455,58 @@ var onFrameHeadersReceived = function(details) {
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/1163
// "Block elements by size"
var foilLargeMediaElement = function(details) {
var µb = µBlock;
var tabId = details.tabId;
var pageStore = µb.pageStoreFromTabId(tabId);
if ( pageStore === null ) {
return;
}
if ( pageStore.getNetFilteringSwitch() !== true ) {
return;
}
if ( Date.now() < pageStore.allowLargeMediaElementsUntil ) {
return;
}
if ( µb.hnSwitches.evaluateZ('no-large-media', pageStore.tabHostname) !== true ) {
return;
}
// Not all servers provide the Content-Length header: when this happens,
// assume the worse.
var contentLength = 1000000,
i = headerIndexFromName('content-length', details.responseHeaders);
if ( i !== -1 ) {
contentLength = parseInt(details.responseHeaders[i].value, 10);
if ( isNaN(contentLength) ) {
contentLength = 1000000;
}
}
if ( (contentLength >>> 10) < µb.userSettings.largeMediaSize ) {
return;
}
pageStore.logLargeMedia();
if ( µb.logger.isEnabled() ) {
µb.logger.writeOne(
tabId,
'net',
µb.hnSwitches.toResultString(),
details.type,
details.url,
pageStore.tabHostname,
pageStore.tabHostname
);
}
return { cancel: true };
};
/******************************************************************************/
var foilInlineScripts = function(headers) {
// Below is copy-pasta from uMatrix's project.
@ -567,8 +625,10 @@ vAPI.net.onHeadersReceived = {
'https://*/*'
],
types: [
"main_frame",
"sub_frame"
'main_frame',
'sub_frame',
'image',
'media'
],
extra: [ 'blocking', 'responseHeaders' ],
callback: onHeadersReceived

View File

@ -234,34 +234,44 @@ var matchWhitelistDirective = function(url, hostname, directive) {
/******************************************************************************/
// Return all settings if none specified.
µBlock.changeUserSettings = function(name, value) {
var us = this.userSettings;
// Return all settings if none specified.
if ( name === undefined ) {
return this.userSettings;
us = JSON.parse(JSON.stringify(us));
us.noCosmeticFiltering = this.hnSwitches.evaluate('no-cosmetic-filtering', '*') === 1;
us.noLargeMedia = this.hnSwitches.evaluate('no-large-media', '*') === 1;
us.noRemoteFonts = this.hnSwitches.evaluate('no-remote-fonts', '*') === 1;
return us;
}
if ( typeof name !== 'string' || name === '' ) {
return;
}
// Do not allow an unknown user setting to be created
if ( this.userSettings[name] === undefined ) {
return;
}
if ( value === undefined ) {
return this.userSettings[name];
return us[name];
}
// Pre-change
switch ( name ) {
case 'largeMediaSize':
if ( typeof value !== 'number' ) {
value = parseInt(value, 10) || 0;
}
value = Math.ceil(Math.max(value, 0));
break;
default:
break;
}
// Change
this.userSettings[name] = value;
// Change -- but only if the user setting actually exists.
var mustSave = us.hasOwnProperty(name) &&
value !== us[name];
if ( mustSave ) {
us[name] = value;
}
// Post-change
switch ( name ) {
@ -271,13 +281,28 @@ var matchWhitelistDirective = function(url, hostname, directive) {
}
break;
case 'contextMenuEnabled':
this.contextMenu.toggle(value);
this.contextMenu.update(null);
break;
case 'experimentalEnabled':
break;
case 'hyperlinkAuditingDisabled':
vAPI.browserSettings.set({ 'hyperlinkAuditing': !value });
break;
case 'noCosmeticFiltering':
if ( this.hnSwitches.toggle('no-cosmetic-filtering', '*', value ? 1 : 0) ) {
this.saveHostnameSwitches();
}
break;
case 'noLargeMedia':
if ( this.hnSwitches.toggle('no-large-media', '*', value ? 1 : 0) ) {
this.saveHostnameSwitches();
}
break;
case 'noRemoteFonts':
if ( this.hnSwitches.toggle('no-remote-fonts', '*', value ? 1 : 0) ) {
this.saveHostnameSwitches();
}
break;
case 'prefetchingDisabled':
vAPI.browserSettings.set({ 'prefetching': !value });
break;
@ -288,7 +313,9 @@ var matchWhitelistDirective = function(url, hostname, directive) {
break;
}
this.saveUserSettings();
if ( mustSave ) {
this.saveUserSettings();
}
};
/******************************************************************************/
@ -379,16 +406,22 @@ var matchWhitelistDirective = function(url, hostname, directive) {
}
// Take action if needed
if ( details.name === 'no-cosmetic-filtering' ) {
switch ( details.name ) {
case 'no-cosmetic-filtering':
this.scriptlets.injectDeep(
details.tabId,
details.state ? 'cosmetic-off' : 'cosmetic-on'
);
return;
break;
case 'no-large-media':
if ( details.state === false ) {
var pageStore = this.pageStoreFromTabId(details.tabId);
if ( pageStore !== null ) {
pageStore.temporarilyAllowLargeMediaElements();
}
}
break;
}
// Whatever else
// ...
};
/******************************************************************************/

View File

@ -28,7 +28,7 @@
<p class="statValue" id="popupHitDomainCount">&nbsp;</p>
<div id="extraTools">
<span id="no-popups" class="hnSwitch fa" data-i18n-tip="popupTipNoPopups">&#xf0c5;<span class="badge"></span><span></span></span>
<span id="no-strict-blocking" class="hnSwitch fa" data-i18n-tip="popupTipNoStrictBlocking">&#xf071;<span></span></span>
<span id="no-large-media" class="hnSwitch fa" data-i18n-tip="popupTipNoLargeMedia">&#xf008;<span class="badge"></span><span></span></span>
<span id="no-cosmetic-filtering" class="hnSwitch fa" data-i18n-tip="popupTipNoCosmeticFiltering">&#xf070;<span class="badge"></span><span></span></span>
<span id="no-remote-fonts" class="hnSwitch fa" data-i18n-tip="popupTipNoRemoteFonts">&#xf031;<span class="badge"></span><span></span></span>
</div>

View File

@ -23,6 +23,12 @@
<li><input id="hyperlink-auditing-disabled" type="checkbox" data-setting-name="hyperlinkAuditingDisabled" data-setting-type="bool"><label data-i18n="settingsHyperlinkAuditingDisabledPrompt" for="hyperlink-auditing-disabled"></label> <a class="fa info" href="http://www.wilderssecurity.com/threads/hyperlink-auditing-aka-a-ping-and-beacon-aka-navigator-sendbeacon.364904/" target="_blank">&#xf05a;</a>
<li><input id="webrtc-ipaddress-hidden" type="checkbox" data-setting-name="webrtcIPAddressHidden" data-setting-type="bool"><label data-i18n="settingsWebRTCIPAddressHiddenPrompt" for="webrtc-ipaddress-hidden"></label> <a class="fa info important" href="https://github.com/gorhill/uBlock/wiki/Prevent-WebRTC-from-leaking-local-IP-address" target="_blank">&#xf05a;</a>
</ul>
<li class="subgroup"><span data-i18n="settingPerSiteSwitchGroup"></span><ul>
<li><label class="synopsis"><span data-i18n="settingPerSiteSwitchGroupSynopsis"></span> <a class="fa info" href="https://github.com/gorhill/uBlock/wiki/Per-site-switches" target="_blank">&#xf05a;</a></label>
<li><input id="no-cosmetic-filtering" type="checkbox" data-setting-name="noCosmeticFiltering" data-setting-type="bool"><label data-i18n="settingsNoCosmeticFilteringPrompt" for="no-cosmetic-filtering"></label> <a class="fa info" href="https://github.com/gorhill/uBlock/wiki/Per-site-switches#no-cosmetic-filtering" target="_blank">&#xf05a;</a>
<li><input id="no-large-media" type="checkbox" data-setting-name="noLargeMedia" data-setting-type="bool"><label data-i18n="settingsNoLargeMediaPrompt" for="no-large-media"></label> <a class="fa info" href="https://github.com/gorhill/uBlock/wiki/Per-site-switches#no-large-media-elements" target="_blank">&#xf05a;</a>
<li><input id="no-remote-fonts" type="checkbox" data-setting-name="noRemoteFonts" data-setting-type="bool"><label data-i18n="settingsNoRemoteFontsPrompt" for="no-remote-fonts"></label> <a class="fa info" href="https://github.com/gorhill/uBlock/wiki/Per-site-switches#no-remote-fonts" target="_blank">&#xf05a;</a>
</ul>
</ul>
<div id="localData" style="margin: 0 1em;">

View File

@ -24,7 +24,7 @@ source_locale_dir = pj(build_dir, '_locales')
target_locale_dir = pj(build_dir, 'locale')
language_codes = []
descriptions = OrderedDict({})
title_case_strings = ['pickerContextMenuEntry']
title_case_strings = ['pickerContextMenuEntry', 'contextMenuTemporarilyAllowLargeMediaElements']
for alpha2 in sorted(os.listdir(source_locale_dir)):
locale_path = pj(source_locale_dir, alpha2, 'messages.json')