mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 09:07:54 +01:00
Ensure toolbar icon reflect updated whitelist directives
Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/680 Opportunistically, vAPI.tabs has been refactored toward ES6 syntax.
This commit is contained in:
parent
7e1868b1c3
commit
e1dd7f7043
2 changed files with 596 additions and 596 deletions
|
@ -275,10 +275,6 @@ vAPI.browserSettings = (function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.tabs = {};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.isBehindTheSceneTabId = function(tabId) {
|
vAPI.isBehindTheSceneTabId = function(tabId) {
|
||||||
return tabId < 0;
|
return tabId < 0;
|
||||||
};
|
};
|
||||||
|
@ -286,166 +282,104 @@ vAPI.isBehindTheSceneTabId = function(tabId) {
|
||||||
vAPI.unsetTabId = 0;
|
vAPI.unsetTabId = 0;
|
||||||
vAPI.noTabId = -1; // definitely not any existing tab
|
vAPI.noTabId = -1; // definitely not any existing tab
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// To remove when tabId-as-integer has been tested enough.
|
// To remove when tabId-as-integer has been tested enough.
|
||||||
|
|
||||||
var toChromiumTabId = function(tabId) {
|
const toChromiumTabId = function(tabId) {
|
||||||
return typeof tabId === 'number' && !isNaN(tabId) && tabId > 0 ?
|
return typeof tabId === 'number' && isNaN(tabId) === false
|
||||||
tabId :
|
? tabId
|
||||||
0;
|
: 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
// https://developer.chrome.com/extensions/webNavigation
|
||||||
|
// https://developer.chrome.com/extensions/tabs
|
||||||
|
|
||||||
vAPI.tabs.registerListeners = function() {
|
vAPI.Tabs = class {
|
||||||
// https://developer.chrome.com/extensions/webNavigation
|
constructor() {
|
||||||
// [onCreatedNavigationTarget ->]
|
browser.webNavigation.onCreatedNavigationTarget.addListener(details => {
|
||||||
// onBeforeNavigate ->
|
if ( typeof details.url !== 'string' ) {
|
||||||
// onCommitted ->
|
details.url = '';
|
||||||
// onDOMContentLoaded ->
|
}
|
||||||
// onCompleted
|
if ( /^https?:\/\//.test(details.url) === false ) {
|
||||||
|
details.frameId = 0;
|
||||||
// The chrome.webRequest.onBeforeRequest() won't be called for everything
|
details.url = this.sanitizeURL(details.url);
|
||||||
// else than `http`/`https`. Thus, in such case, we will bind the tab as
|
|
||||||
// early as possible in order to increase the likelihood of a context
|
|
||||||
// properly setup if network requests are fired from within the tab.
|
|
||||||
// Example: Chromium + case #6 at
|
|
||||||
// http://raymondhill.net/ublock/popup.html
|
|
||||||
const reGoodForWebRequestAPI = /^https?:\/\//;
|
|
||||||
|
|
||||||
// https://forums.lanik.us/viewtopic.php?f=62&t=32826
|
|
||||||
// Chromium-based browsers: sanitize target URL. I've seen data: URI with
|
|
||||||
// newline characters in standard fields, possibly as a way of evading
|
|
||||||
// filters. As per spec, there should be no whitespaces in a data: URI's
|
|
||||||
// standard fields.
|
|
||||||
const sanitizeURL = function(url) {
|
|
||||||
if ( url.startsWith('data:') === false ) { return url; }
|
|
||||||
const pos = url.indexOf(',');
|
|
||||||
if ( pos === -1 ) { return url; }
|
|
||||||
const s = url.slice(0, pos);
|
|
||||||
if ( s.search(/\s/) === -1 ) { return url; }
|
|
||||||
return s.replace(/\s+/, '') + url.slice(pos);
|
|
||||||
};
|
|
||||||
|
|
||||||
browser.webNavigation.onCreatedNavigationTarget.addListener(details => {
|
|
||||||
if ( typeof details.url !== 'string' ) {
|
|
||||||
details.url = '';
|
|
||||||
}
|
|
||||||
if ( reGoodForWebRequestAPI.test(details.url) === false ) {
|
|
||||||
details.frameId = 0;
|
|
||||||
details.url = sanitizeURL(details.url);
|
|
||||||
if ( this.onNavigation ) {
|
|
||||||
this.onNavigation(details);
|
this.onNavigation(details);
|
||||||
}
|
}
|
||||||
}
|
this.onCreated(
|
||||||
if ( vAPI.tabs.onPopupCreated ) {
|
|
||||||
vAPI.tabs.onPopupCreated(
|
|
||||||
details.tabId,
|
details.tabId,
|
||||||
details.sourceTabId
|
details.sourceTabId
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
browser.webNavigation.onCommitted.addListener(details => {
|
browser.webNavigation.onCommitted.addListener(details => {
|
||||||
details.url = sanitizeURL(details.url);
|
details.url = this.sanitizeURL(details.url);
|
||||||
if ( this.onNavigation ) {
|
|
||||||
this.onNavigation(details);
|
this.onNavigation(details);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/3073
|
// https://github.com/gorhill/uBlock/issues/3073
|
||||||
// Fall back to `tab.url` when `changeInfo.url` is not set.
|
// Fall back to `tab.url` when `changeInfo.url` is not set.
|
||||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||||
if ( typeof changeInfo.url !== 'string' ) {
|
if ( typeof changeInfo.url !== 'string' ) {
|
||||||
changeInfo.url = tab && tab.url;
|
changeInfo.url = tab && tab.url;
|
||||||
}
|
|
||||||
if ( changeInfo.url ) {
|
|
||||||
changeInfo.url = sanitizeURL(changeInfo.url);
|
|
||||||
}
|
|
||||||
if ( this.onUpdated ) {
|
|
||||||
this.onUpdated(tabId, changeInfo, tab);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.tabs.onActivated.addListener(( ) => {
|
|
||||||
if ( vAPI.contextMenu ) {
|
|
||||||
vAPI.contextMenu.onMustUpdate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
browser.tabs.onRemoved.addListener((tabId, details) => {
|
|
||||||
this.onClosed(tabId, details);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Caller must be prepared to deal with nil tab argument.
|
|
||||||
|
|
||||||
// https://code.google.com/p/chromium/issues/detail?id=410868#c8
|
|
||||||
|
|
||||||
vAPI.tabs.get = function(tabId, callback) {
|
|
||||||
if ( tabId === null ) {
|
|
||||||
chrome.tabs.query(
|
|
||||||
{ active: true, currentWindow: true },
|
|
||||||
tabs => {
|
|
||||||
void chrome.runtime.lastError;
|
|
||||||
callback(
|
|
||||||
Array.isArray(tabs) && tabs.length !== 0 ? tabs[0] : null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
if ( changeInfo.url ) {
|
||||||
return;
|
changeInfo.url = this.sanitizeURL(changeInfo.url);
|
||||||
|
}
|
||||||
|
this.onUpdated(tabId, changeInfo, tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.tabs.onActivated.addListener((tabId, details) => {
|
||||||
|
this.onActivated(tabId, details);
|
||||||
|
});
|
||||||
|
|
||||||
|
browser.tabs.onRemoved.addListener((tabId, details) => {
|
||||||
|
this.onClosed(tabId, details);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get(tabId, callback) {
|
||||||
|
if ( tabId === null ) {
|
||||||
|
chrome.tabs.query(
|
||||||
|
{ active: true, currentWindow: true },
|
||||||
|
tabs => {
|
||||||
|
void chrome.runtime.lastError;
|
||||||
|
callback(
|
||||||
|
Array.isArray(tabs) && tabs.length !== 0
|
||||||
|
? tabs[0]
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tabId = toChromiumTabId(tabId);
|
||||||
|
if ( tabId === 0 ) {
|
||||||
|
callback(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.tabs.get(tabId, function(tab) {
|
||||||
|
void chrome.runtime.lastError;
|
||||||
|
callback(tab);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tabId = toChromiumTabId(tabId);
|
// Properties of the details object:
|
||||||
if ( tabId === 0 ) {
|
// - url: 'URL', => the address that will be opened
|
||||||
callback(null);
|
// - index: -1, => undefined: end of the list, -1: following tab, or
|
||||||
return;
|
// after index
|
||||||
}
|
// - active: false, => opens the tab in background - true and undefined:
|
||||||
|
// foreground
|
||||||
|
// - popup: true => open in a new window
|
||||||
|
|
||||||
chrome.tabs.get(tabId, function(tab) {
|
create(url, details) {
|
||||||
void chrome.runtime.lastError;
|
|
||||||
callback(tab);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
|
|
||||||
Properties of the details object:
|
|
||||||
- url: 'URL', => the address that will be opened
|
|
||||||
- tabId: 1, => the tab is used if set, instead of creating a new one
|
|
||||||
- index: -1, => undefined: end of the list, -1: following tab, or
|
|
||||||
after index
|
|
||||||
- active: false, => opens the tab in background - true and undefined:
|
|
||||||
foreground
|
|
||||||
- select: true, => if a tab is already opened with that url, then select
|
|
||||||
it instead of opening a new one
|
|
||||||
- popup: true => open in a new window
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
vAPI.tabs.open = function(details) {
|
|
||||||
let targetURL = details.url;
|
|
||||||
if ( typeof targetURL !== 'string' || targetURL === '' ) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// extension pages
|
|
||||||
if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
|
|
||||||
targetURL = vAPI.getURL(targetURL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// dealing with Chrome's asynchronous API
|
|
||||||
const wrapper = ( ) => {
|
|
||||||
if ( details.active === undefined ) {
|
if ( details.active === undefined ) {
|
||||||
details.active = true;
|
details.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subWrapper = ( ) => {
|
const subWrapper = ( ) => {
|
||||||
const updateDetails = {
|
const updateDetails = {
|
||||||
url: targetURL,
|
url: url,
|
||||||
active: !!details.active
|
active: !!details.active
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -527,121 +461,162 @@ vAPI.tabs.open = function(details) {
|
||||||
|
|
||||||
subWrapper();
|
subWrapper();
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
if ( !details.select ) {
|
|
||||||
wrapper();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/3053#issuecomment-332276818
|
// Properties of the details object:
|
||||||
// - Do not try to lookup uBO's own pages with FF 55 or less.
|
// - url: 'URL', => the address that will be opened
|
||||||
if (
|
// - tabId: 1, => the tab is used if set, instead of creating a new one
|
||||||
vAPI.webextFlavor.soup.has('firefox') &&
|
// - index: -1, => undefined: end of the list, -1: following tab, or
|
||||||
vAPI.webextFlavor.major < 56
|
// after index
|
||||||
) {
|
// - active: false, => opens the tab in background - true and undefined:
|
||||||
wrapper();
|
// foreground
|
||||||
return;
|
// - select: true, => if a tab is already opened with that url, then select
|
||||||
}
|
// it instead of opening a new one
|
||||||
|
// - popup: true => open in a new window
|
||||||
|
|
||||||
// https://developer.chrome.com/extensions/tabs#method-query
|
open(details) {
|
||||||
// "Note that fragment identifiers are not matched."
|
let targetURL = details.url;
|
||||||
// It's a lie, fragment identifiers ARE matched. So we need to remove
|
if ( typeof targetURL !== 'string' || targetURL === '' ) {
|
||||||
// the fragment.
|
return null;
|
||||||
const pos = targetURL.indexOf('#');
|
}
|
||||||
const targetURLWithoutHash = pos === -1
|
|
||||||
? targetURL
|
|
||||||
: targetURL.slice(0, pos);
|
|
||||||
|
|
||||||
browser.tabs.query({ url: targetURLWithoutHash }, tabs => {
|
// extension pages
|
||||||
void browser.runtime.lastError;
|
if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
|
||||||
const tab = Array.isArray(tabs) && tabs[0];
|
targetURL = vAPI.getURL(targetURL);
|
||||||
if ( !tab ) {
|
}
|
||||||
wrapper();
|
|
||||||
|
if ( !details.select ) {
|
||||||
|
this.create(targetURL, details);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const updateDetails = { active: true };
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/592
|
// https://github.com/gorhill/uBlock/issues/3053#issuecomment-332276818
|
||||||
if ( tab.url.startsWith(targetURL) === false ) {
|
// Do not try to lookup uBO's own pages with FF 55 or less.
|
||||||
updateDetails.url = targetURL;
|
if (
|
||||||
|
vAPI.webextFlavor.soup.has('firefox') &&
|
||||||
|
vAPI.webextFlavor.major < 56
|
||||||
|
) {
|
||||||
|
this.create(targetURL, details);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
browser.tabs.update(tab.id, updateDetails, tab => {
|
|
||||||
if ( browser.windows instanceof Object === false ) { return; }
|
// https://developer.chrome.com/extensions/tabs#method-query
|
||||||
browser.windows.update(tab.windowId, { focused: true });
|
// "Note that fragment identifiers are not matched."
|
||||||
|
// It's a lie, fragment identifiers ARE matched. So we need to remove
|
||||||
|
// the fragment.
|
||||||
|
const pos = targetURL.indexOf('#');
|
||||||
|
const targetURLWithoutHash = pos === -1
|
||||||
|
? targetURL
|
||||||
|
: targetURL.slice(0, pos);
|
||||||
|
|
||||||
|
browser.tabs.query({ url: targetURLWithoutHash }, tabs => {
|
||||||
|
void browser.runtime.lastError;
|
||||||
|
const tab = Array.isArray(tabs) && tabs[0];
|
||||||
|
if ( !tab ) {
|
||||||
|
this.create(targetURL, details);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const updateDetails = { active: true };
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/592
|
||||||
|
if ( tab.url.startsWith(targetURL) === false ) {
|
||||||
|
updateDetails.url = targetURL;
|
||||||
|
}
|
||||||
|
browser.tabs.update(tab.id, updateDetails, tab => {
|
||||||
|
if ( browser.windows instanceof Object === false ) { return; }
|
||||||
|
browser.windows.update(tab.windowId, { focused: true });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Replace the URL of a tab. Noop if the tab does not exist.
|
|
||||||
|
|
||||||
vAPI.tabs.replace = function(tabId, url) {
|
|
||||||
tabId = toChromiumTabId(tabId);
|
|
||||||
if ( tabId === 0 ) { return; }
|
|
||||||
|
|
||||||
var targetURL = url;
|
|
||||||
|
|
||||||
// extension pages
|
|
||||||
if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
|
|
||||||
targetURL = vAPI.getURL(targetURL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.tabs.update(tabId, { url: targetURL }, vAPI.resetLastError);
|
// Replace the URL of a tab. Noop if the tab does not exist.
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
replace(tabId, url) {
|
||||||
|
tabId = toChromiumTabId(tabId);
|
||||||
|
if ( tabId === 0 ) { return; }
|
||||||
|
|
||||||
vAPI.tabs.remove = function(tabId) {
|
let targetURL = url;
|
||||||
tabId = toChromiumTabId(tabId);
|
|
||||||
if ( tabId === 0 ) { return; }
|
|
||||||
|
|
||||||
chrome.tabs.remove(tabId, vAPI.resetLastError);
|
// extension pages
|
||||||
};
|
if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
|
||||||
|
targetURL = vAPI.getURL(targetURL);
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.tabs.reload = function(tabId, bypassCache = false) {
|
|
||||||
tabId = toChromiumTabId(tabId);
|
|
||||||
if ( tabId === 0 ) { return; }
|
|
||||||
|
|
||||||
chrome.tabs.reload(
|
|
||||||
tabId,
|
|
||||||
{ bypassCache: bypassCache === true },
|
|
||||||
vAPI.resetLastError
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Select a specific tab.
|
|
||||||
|
|
||||||
vAPI.tabs.select = function(tabId) {
|
|
||||||
tabId = toChromiumTabId(tabId);
|
|
||||||
if ( tabId === 0 ) { return; }
|
|
||||||
|
|
||||||
chrome.tabs.update(tabId, { active: true }, function(tab) {
|
|
||||||
void chrome.runtime.lastError;
|
|
||||||
if ( !tab ) { return; }
|
|
||||||
if ( chrome.windows instanceof Object === false ) { return; }
|
|
||||||
chrome.windows.update(tab.windowId, { focused: true });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.tabs.injectScript = function(tabId, details, callback) {
|
|
||||||
var onScriptExecuted = function() {
|
|
||||||
// https://code.google.com/p/chromium/issues/detail?id=410868#c8
|
|
||||||
void chrome.runtime.lastError;
|
|
||||||
if ( typeof callback === 'function' ) {
|
|
||||||
callback.apply(null, arguments);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if ( tabId ) {
|
chrome.tabs.update(tabId, { url: targetURL }, vAPI.resetLastError);
|
||||||
chrome.tabs.executeScript(toChromiumTabId(tabId), details, onScriptExecuted);
|
}
|
||||||
} else {
|
|
||||||
chrome.tabs.executeScript(details, onScriptExecuted);
|
remove(tabId) {
|
||||||
|
tabId = toChromiumTabId(tabId);
|
||||||
|
if ( tabId === 0 ) { return; }
|
||||||
|
|
||||||
|
chrome.tabs.remove(tabId, vAPI.resetLastError);
|
||||||
|
}
|
||||||
|
|
||||||
|
reload(tabId, bypassCache = false) {
|
||||||
|
tabId = toChromiumTabId(tabId);
|
||||||
|
if ( tabId === 0 ) { return; }
|
||||||
|
|
||||||
|
chrome.tabs.reload(
|
||||||
|
tabId,
|
||||||
|
{ bypassCache: bypassCache === true },
|
||||||
|
vAPI.resetLastError
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
select(tabId) {
|
||||||
|
tabId = toChromiumTabId(tabId);
|
||||||
|
if ( tabId === 0 ) { return; }
|
||||||
|
|
||||||
|
chrome.tabs.update(tabId, { active: true }, function(tab) {
|
||||||
|
void chrome.runtime.lastError;
|
||||||
|
if ( !tab ) { return; }
|
||||||
|
if ( chrome.windows instanceof Object === false ) { return; }
|
||||||
|
chrome.windows.update(tab.windowId, { focused: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
injectScript(tabId, details, callback) {
|
||||||
|
const onScriptExecuted = function() {
|
||||||
|
// https://code.google.com/p/chromium/issues/detail?id=410868#c8
|
||||||
|
void chrome.runtime.lastError;
|
||||||
|
if ( typeof callback === 'function' ) {
|
||||||
|
callback.apply(null, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if ( tabId ) {
|
||||||
|
chrome.tabs.executeScript(toChromiumTabId(tabId), details, onScriptExecuted);
|
||||||
|
} else {
|
||||||
|
chrome.tabs.executeScript(details, onScriptExecuted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://forums.lanik.us/viewtopic.php?f=62&t=32826
|
||||||
|
// Chromium-based browsers: sanitize target URL. I've seen data: URI with
|
||||||
|
// newline characters in standard fields, possibly as a way of evading
|
||||||
|
// filters. As per spec, there should be no whitespaces in a data: URI's
|
||||||
|
// standard fields.
|
||||||
|
|
||||||
|
sanitizeURL(url) {
|
||||||
|
if ( url.startsWith('data:') === false ) { return url; }
|
||||||
|
const pos = url.indexOf(',');
|
||||||
|
if ( pos === -1 ) { return url; }
|
||||||
|
const s = url.slice(0, pos);
|
||||||
|
if ( s.search(/\s/) === -1 ) { return url; }
|
||||||
|
return s.replace(/\s+/, '') + url.slice(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivated(/* details */) {
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed(/* tabId, details */) {
|
||||||
|
}
|
||||||
|
|
||||||
|
onCreated(/* openedTabId, openerTabId */) {
|
||||||
|
}
|
||||||
|
|
||||||
|
onNavigation(/* details */) {
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdated(/* tabId, changeInfo, tab */) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
733
src/js/tab.js
733
src/js/tab.js
|
@ -54,6 +54,310 @@
|
||||||
return `http://${fakeHostname}/`;
|
return `http://${fakeHostname}/`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/99
|
||||||
|
// https://github.com/gorhill/uBlock/issues/991
|
||||||
|
//
|
||||||
|
// popup:
|
||||||
|
// Test/close target URL
|
||||||
|
// popunder:
|
||||||
|
// Test/close opener URL
|
||||||
|
//
|
||||||
|
// popup filter match:
|
||||||
|
// 0 = false
|
||||||
|
// 1 = true
|
||||||
|
//
|
||||||
|
// opener: 0 0 1 1
|
||||||
|
// target: 0 1 0 1
|
||||||
|
// ---- ---- ---- ----
|
||||||
|
// result: a b c d
|
||||||
|
//
|
||||||
|
// a: do nothing
|
||||||
|
// b: close target
|
||||||
|
// c: close opener
|
||||||
|
// d: close target
|
||||||
|
|
||||||
|
µBlock.onPopupUpdated = (( ) => {
|
||||||
|
const µb = µBlock;
|
||||||
|
// The same context object will be reused everytime. This also allows to
|
||||||
|
// remember whether a popup or popunder was matched.
|
||||||
|
const fctxt = µBlock.filteringContext.setFilter(undefined);
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/commit/1d448b85b2931412508aa01bf899e0b6f0033626#commitcomment-14944764
|
||||||
|
// See if two URLs are different, disregarding scheme -- because the
|
||||||
|
// scheme can be unilaterally changed by the browser.
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1378
|
||||||
|
// Maybe no link element was clicked.
|
||||||
|
// https://github.com/gorhill/uBlock/issues/3287
|
||||||
|
// Do not bail out if the target URL has no hostname.
|
||||||
|
const areDifferentURLs = function(a, b) {
|
||||||
|
if ( b === '' ) { return true; }
|
||||||
|
if ( b.startsWith('about:') ) { return false; }
|
||||||
|
let pos = a.indexOf('://');
|
||||||
|
if ( pos === -1 ) { return false; }
|
||||||
|
a = a.slice(pos);
|
||||||
|
pos = b.indexOf('://');
|
||||||
|
if ( pos !== -1 ) {
|
||||||
|
b = b.slice(pos);
|
||||||
|
}
|
||||||
|
return b !== a;
|
||||||
|
};
|
||||||
|
|
||||||
|
const popupMatch = function(openerURL, targetURL, popupType) {
|
||||||
|
fctxt.setTabOriginFromURL(openerURL)
|
||||||
|
.setDocOriginFromURL(openerURL)
|
||||||
|
.setURL(targetURL)
|
||||||
|
.setType('popup');
|
||||||
|
let result;
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1735
|
||||||
|
// Do not bail out on `data:` URI, they are commonly used for popups.
|
||||||
|
// https://github.com/uBlockOrigin/uAssets/issues/255
|
||||||
|
// Do not bail out on `about:blank`: an `about:blank` popup can be
|
||||||
|
// opened, with the sole purpose to serve as an intermediary in
|
||||||
|
// a sequence of chained popups.
|
||||||
|
// https://github.com/uBlockOrigin/uAssets/issues/263#issuecomment-272615772
|
||||||
|
// Do not bail out, period: the static filtering engine must be
|
||||||
|
// able to examine all sorts of URLs for popup filtering purpose.
|
||||||
|
|
||||||
|
// Dynamic filtering makes sense only when we have a valid opener
|
||||||
|
// hostname.
|
||||||
|
// https://github.com/gorhill/uBlock/commit/1d448b85b2931412508aa01bf899e0b6f0033626#commitcomment-14944764
|
||||||
|
// Ignore bad target URL. On Firefox, an `about:blank` tab may be
|
||||||
|
// opened for a new tab before it is filled in with the real target
|
||||||
|
// URL.
|
||||||
|
if ( fctxt.getTabHostname() !== '' && targetURL !== 'about:blank' ) {
|
||||||
|
// Check per-site switch first
|
||||||
|
// https://github.com/gorhill/uBlock/issues/3060
|
||||||
|
// - The no-popups switch must apply only to popups, not to
|
||||||
|
// popunders.
|
||||||
|
if (
|
||||||
|
popupType === 'popup' &&
|
||||||
|
µb.sessionSwitches.evaluateZ(
|
||||||
|
'no-popups',
|
||||||
|
fctxt.getTabHostname()
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fctxt.filter = {
|
||||||
|
raw: 'no-popups: ' + µb.sessionSwitches.z + ' true',
|
||||||
|
result: 1,
|
||||||
|
source: 'switch'
|
||||||
|
};
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/581
|
||||||
|
// Take into account popup-specific rules in dynamic URL
|
||||||
|
// filtering, OR generic allow rules.
|
||||||
|
result = µb.sessionURLFiltering.evaluateZ(
|
||||||
|
fctxt.getTabHostname(),
|
||||||
|
targetURL,
|
||||||
|
popupType
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
result === 1 && µb.sessionURLFiltering.type === popupType ||
|
||||||
|
result === 2
|
||||||
|
) {
|
||||||
|
fctxt.filter = µb.sessionURLFiltering.toLogData();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/581
|
||||||
|
// Take into account `allow` rules in dynamic filtering: `block`
|
||||||
|
// rules are ignored, as block rules are not meant to block
|
||||||
|
// specific types like `popup` (just like with static filters).
|
||||||
|
result = µb.sessionFirewall.evaluateCellZY(
|
||||||
|
fctxt.getTabHostname(),
|
||||||
|
fctxt.getHostname(),
|
||||||
|
popupType
|
||||||
|
);
|
||||||
|
if ( result === 2 ) {
|
||||||
|
fctxt.filter = µb.sessionFirewall.toLogData();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/323
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/1142
|
||||||
|
// Don't block if uBlock is turned off in popup's context
|
||||||
|
if ( µb.getNetFilteringSwitch(targetURL) ) {
|
||||||
|
fctxt.type = popupType;
|
||||||
|
result = µb.staticNetFilteringEngine.matchString(fctxt, 0b0001);
|
||||||
|
if ( result !== 0 ) {
|
||||||
|
fctxt.filter = µb.staticNetFilteringEngine.toLogData();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapPopunderResult = function(popunderURL, popunderHostname, result) {
|
||||||
|
if (
|
||||||
|
fctxt.filter === undefined ||
|
||||||
|
fctxt.filter !== 'static' ||
|
||||||
|
fctxt.filter.token === µb.staticNetFilteringEngine.noTokenHash
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if ( fctxt.filter.token === µb.staticNetFilteringEngine.dotTokenHash ) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const re = new RegExp(fctxt.filter.regex, 'i');
|
||||||
|
const matches = re.exec(popunderURL);
|
||||||
|
if ( matches === null ) { return 0; }
|
||||||
|
const beg = matches.index;
|
||||||
|
const end = beg + matches[0].length;
|
||||||
|
const pos = popunderURL.indexOf(popunderHostname);
|
||||||
|
if ( pos === -1 ) { return 0; }
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1471
|
||||||
|
// We test whether the opener hostname as at least one character
|
||||||
|
// within matched portion of URL.
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1903
|
||||||
|
// Ignore filters which cause a match before the start of the
|
||||||
|
// hostname in the URL.
|
||||||
|
return beg >= pos && beg < pos + popunderHostname.length && end > pos
|
||||||
|
? result
|
||||||
|
: 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const popunderMatch = function(openerURL, targetURL) {
|
||||||
|
let result = popupMatch(targetURL, openerURL, 'popunder');
|
||||||
|
if ( result === 1 ) { return result; }
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1010#issuecomment-186824878
|
||||||
|
// Check the opener tab as if it were the newly opened tab: if there
|
||||||
|
// is a hit against a popup filter, and if the matching filter is not
|
||||||
|
// a broad one, we will consider the opener tab to be a popunder tab.
|
||||||
|
// For now, a "broad" filter is one which does not touch any part of
|
||||||
|
// the hostname part of the opener URL.
|
||||||
|
let popunderURL = openerURL,
|
||||||
|
popunderHostname = µb.URI.hostnameFromURI(popunderURL);
|
||||||
|
if ( popunderHostname === '' ) { return 0; }
|
||||||
|
|
||||||
|
result = mapPopunderResult(
|
||||||
|
popunderURL,
|
||||||
|
popunderHostname,
|
||||||
|
popupMatch(targetURL, popunderURL, 'popup')
|
||||||
|
);
|
||||||
|
if ( result !== 0 ) { return result; }
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1598
|
||||||
|
// Try to find a match against origin part of the opener URL.
|
||||||
|
popunderURL = µb.URI.originFromURI(popunderURL);
|
||||||
|
if ( popunderURL === '' ) { return 0; }
|
||||||
|
|
||||||
|
return mapPopunderResult(
|
||||||
|
popunderURL,
|
||||||
|
popunderHostname,
|
||||||
|
popupMatch(targetURL, popunderURL, 'popup')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return function(targetTabId, openerDetails) {
|
||||||
|
// Opener details.
|
||||||
|
const openerTabId = openerDetails.tabId;
|
||||||
|
let tabContext = µb.tabContextManager.lookup(openerTabId);
|
||||||
|
if ( tabContext === null ) { return; }
|
||||||
|
const openerURL = tabContext.rawURL;
|
||||||
|
if ( openerURL === '' ) { return; }
|
||||||
|
|
||||||
|
// Popup details.
|
||||||
|
tabContext = µb.tabContextManager.lookup(targetTabId);
|
||||||
|
if ( tabContext === null ) { return; }
|
||||||
|
let targetURL = tabContext.rawURL;
|
||||||
|
if ( targetURL === '' ) { return; }
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/341
|
||||||
|
// Allow popups if uBlock is turned off in opener's context.
|
||||||
|
if ( µb.getNetFilteringSwitch(openerURL) === false ) { return; }
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1538
|
||||||
|
if (
|
||||||
|
µb.getNetFilteringSwitch(µb.normalizePageURL(
|
||||||
|
openerTabId,
|
||||||
|
openerURL)
|
||||||
|
) === false
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the page URL is that of our "blocked page" URL, extract the URL of
|
||||||
|
// the page which was blocked.
|
||||||
|
if ( targetURL.startsWith(vAPI.getURL('document-blocked.html')) ) {
|
||||||
|
const matches = /details=([^&]+)/.exec(targetURL);
|
||||||
|
if ( matches !== null ) {
|
||||||
|
targetURL = JSON.parse(atob(matches[1])).url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popup test.
|
||||||
|
let popupType = 'popup',
|
||||||
|
result = 0;
|
||||||
|
// https://github.com/gorhill/uBlock/issues/2919
|
||||||
|
// - If the target tab matches a clicked link, assume it's legit.
|
||||||
|
if ( areDifferentURLs(targetURL, openerDetails.trustedURL) ) {
|
||||||
|
result = popupMatch(openerURL, targetURL, 'popup');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popunder test.
|
||||||
|
if ( result === 0 && openerDetails.popunder ) {
|
||||||
|
result = popunderMatch(openerURL, targetURL);
|
||||||
|
if ( result === 1 ) {
|
||||||
|
popupType = 'popunder';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log only for when there was a hit against an actual filter (allow or block).
|
||||||
|
// https://github.com/gorhill/uBlock/issues/2776
|
||||||
|
if ( µb.logger.enabled ) {
|
||||||
|
fctxt.setRealm('network').setType(popupType);
|
||||||
|
if ( popupType === 'popup' ) {
|
||||||
|
fctxt.setURL(targetURL)
|
||||||
|
.setTabId(openerTabId)
|
||||||
|
.setTabOriginFromURL(openerURL)
|
||||||
|
.setDocOriginFromURL(openerURL);
|
||||||
|
} else {
|
||||||
|
fctxt.setURL(openerURL)
|
||||||
|
.setTabId(targetTabId)
|
||||||
|
.setTabOriginFromURL(targetURL)
|
||||||
|
.setDocOriginFromURL(targetURL);
|
||||||
|
}
|
||||||
|
fctxt.toLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not blocked
|
||||||
|
if ( result !== 1 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only if a popup was blocked do we report it in the dynamic
|
||||||
|
// filtering pane.
|
||||||
|
const pageStore = µb.pageStoreFromTabId(openerTabId);
|
||||||
|
if ( pageStore ) {
|
||||||
|
pageStore.journalAddRequest(fctxt.getHostname(), result);
|
||||||
|
pageStore.popupBlockedCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocked
|
||||||
|
if ( µb.userSettings.showIconBadge ) {
|
||||||
|
µb.updateToolbarIcon(openerTabId, 0x02);
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is a popup, block and remove the tab.
|
||||||
|
if ( popupType === 'popup' ) {
|
||||||
|
µb.unbindTabFromPageStats(targetTabId);
|
||||||
|
vAPI.tabs.remove(targetTabId, false);
|
||||||
|
} else {
|
||||||
|
µb.unbindTabFromPageStats(openerTabId);
|
||||||
|
vAPI.tabs.remove(openerTabId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
|
|
||||||
|
@ -175,7 +479,7 @@ housekeep itself.
|
||||||
if ( targetTabId === candidate.opener.tabId ) {
|
if ( targetTabId === candidate.opener.tabId ) {
|
||||||
candidate.opener.popunder = true;
|
candidate.opener.popunder = true;
|
||||||
}
|
}
|
||||||
if ( vAPI.tabs.onPopupUpdated(tabId, candidate.opener) === true ) {
|
if ( µb.onPopupUpdated(tabId, candidate.opener) === true ) {
|
||||||
candidate.destroy();
|
candidate.destroy();
|
||||||
} else {
|
} else {
|
||||||
candidate.launchSelfDestruction();
|
candidate.launchSelfDestruction();
|
||||||
|
@ -183,7 +487,7 @@ housekeep itself.
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.tabs.onPopupCreated = function(targetTabId, openerTabId) {
|
const onTabCreated = function(targetTabId, openerTabId) {
|
||||||
const popup = popupCandidates.get(targetTabId);
|
const popup = popupCandidates.get(targetTabId);
|
||||||
if ( popup === undefined ) {
|
if ( popup === undefined ) {
|
||||||
popupCandidates.set(
|
popupCandidates.set(
|
||||||
|
@ -472,364 +776,82 @@ housekeep itself.
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
push: push,
|
push,
|
||||||
commit: commit,
|
commit,
|
||||||
lookup: lookup,
|
lookup,
|
||||||
mustLookup: mustLookup,
|
mustLookup,
|
||||||
exists: exists,
|
exists,
|
||||||
createContext: createContext
|
createContext,
|
||||||
|
onTabCreated,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// When the DOM content of root frame is loaded, this means the tab
|
vAPI.Tabs = class extends vAPI.Tabs {
|
||||||
// content has changed.
|
onActivated(details) {
|
||||||
|
super.onActivated(details);
|
||||||
|
if ( vAPI.isBehindTheSceneTabId(details.tabId) ) { return; }
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/680
|
||||||
|
µBlock.updateToolbarIcon(details.tabId);
|
||||||
|
µBlock.contextMenu.update(details.tabId);
|
||||||
|
}
|
||||||
|
|
||||||
vAPI.tabs.onNavigation = function(details) {
|
onClosed(tabId) {
|
||||||
const µb = µBlock;
|
super.onClosed(tabId);
|
||||||
if ( details.frameId === 0 ) {
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||||
µb.tabContextManager.commit(details.tabId, details.url);
|
µBlock.unbindTabFromPageStats(tabId);
|
||||||
let pageStore = µb.bindTabToPageStats(details.tabId, 'tabCommitted');
|
µBlock.contextMenu.update();
|
||||||
if ( pageStore ) {
|
}
|
||||||
pageStore.journalAddRootFrame('committed', details.url);
|
|
||||||
|
onCreated(targetTabId, openerTabId) {
|
||||||
|
super.onCreated(targetTabId, openerTabId);
|
||||||
|
µBlock.tabContextManager.onTabCreated(targetTabId, openerTabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the DOM content of root frame is loaded, this means the tab
|
||||||
|
// content has changed.
|
||||||
|
//
|
||||||
|
// The webRequest.onBeforeRequest() won't be called for everything
|
||||||
|
// else than http/https. Thus, in such case, we will bind the tab as
|
||||||
|
// early as possible in order to increase the likelihood of a context
|
||||||
|
// properly setup if network requests are fired from within the tab.
|
||||||
|
// Example: Chromium + case #6 at
|
||||||
|
// http://raymondhill.net/ublock/popup.html
|
||||||
|
|
||||||
|
onNavigation(details) {
|
||||||
|
super.onNavigation(details);
|
||||||
|
const µb = µBlock;
|
||||||
|
if ( details.frameId === 0 ) {
|
||||||
|
µb.tabContextManager.commit(details.tabId, details.url);
|
||||||
|
let pageStore = µb.bindTabToPageStats(details.tabId, 'tabCommitted');
|
||||||
|
if ( pageStore ) {
|
||||||
|
pageStore.journalAddRootFrame('committed', details.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( µb.canInjectScriptletsNow ) {
|
||||||
|
let pageStore = µb.pageStoreFromTabId(details.tabId);
|
||||||
|
if ( pageStore !== null && pageStore.getNetFilteringSwitch() ) {
|
||||||
|
µb.scriptletFilteringEngine.injectNow(details);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( µb.canInjectScriptletsNow ) {
|
|
||||||
let pageStore = µb.pageStoreFromTabId(details.tabId);
|
// It may happen the URL in the tab changes, while the page's document
|
||||||
if ( pageStore !== null && pageStore.getNetFilteringSwitch() ) {
|
// stays the same (for instance, Google Maps). Without this listener,
|
||||||
µb.scriptletFilteringEngine.injectNow(details);
|
// the extension icon won't be properly refreshed.
|
||||||
}
|
|
||||||
|
onUpdated(tabId, changeInfo, tab) {
|
||||||
|
super.onUpdated(tabId, changeInfo, tab);
|
||||||
|
if ( !tab.url || tab.url === '' ) { return; }
|
||||||
|
if ( !changeInfo.url ) { return; }
|
||||||
|
µBlock.tabContextManager.commit(tabId, changeInfo.url);
|
||||||
|
µBlock.bindTabToPageStats(tabId, 'tabUpdated');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
vAPI.tabs = new vAPI.Tabs();
|
||||||
|
|
||||||
// It may happen the URL in the tab changes, while the page's document
|
|
||||||
// stays the same (for instance, Google Maps). Without this listener,
|
|
||||||
// the extension icon won't be properly refreshed.
|
|
||||||
|
|
||||||
vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
|
|
||||||
if ( !tab.url || tab.url === '' ) { return; }
|
|
||||||
if ( !changeInfo.url ) { return; }
|
|
||||||
µBlock.tabContextManager.commit(tabId, changeInfo.url);
|
|
||||||
µBlock.bindTabToPageStats(tabId, 'tabUpdated');
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.tabs.onClosed = function(tabId) {
|
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
|
||||||
µBlock.unbindTabFromPageStats(tabId);
|
|
||||||
µBlock.contextMenu.update();
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/99
|
|
||||||
// https://github.com/gorhill/uBlock/issues/991
|
|
||||||
//
|
|
||||||
// popup:
|
|
||||||
// Test/close target URL
|
|
||||||
// popunder:
|
|
||||||
// Test/close opener URL
|
|
||||||
//
|
|
||||||
// popup filter match:
|
|
||||||
// 0 = false
|
|
||||||
// 1 = true
|
|
||||||
//
|
|
||||||
// opener: 0 0 1 1
|
|
||||||
// target: 0 1 0 1
|
|
||||||
// ---- ---- ---- ----
|
|
||||||
// result: a b c d
|
|
||||||
//
|
|
||||||
// a: do nothing
|
|
||||||
// b: close target
|
|
||||||
// c: close opener
|
|
||||||
// d: close target
|
|
||||||
|
|
||||||
vAPI.tabs.onPopupUpdated = (( ) => {
|
|
||||||
const µb = µBlock;
|
|
||||||
// The same context object will be reused everytime. This also allows to
|
|
||||||
// remember whether a popup or popunder was matched.
|
|
||||||
const fctxt = µBlock.filteringContext.setFilter(undefined);
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/commit/1d448b85b2931412508aa01bf899e0b6f0033626#commitcomment-14944764
|
|
||||||
// See if two URLs are different, disregarding scheme -- because the
|
|
||||||
// scheme can be unilaterally changed by the browser.
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1378
|
|
||||||
// Maybe no link element was clicked.
|
|
||||||
// https://github.com/gorhill/uBlock/issues/3287
|
|
||||||
// Do not bail out if the target URL has no hostname.
|
|
||||||
const areDifferentURLs = function(a, b) {
|
|
||||||
if ( b === '' ) { return true; }
|
|
||||||
if ( b.startsWith('about:') ) { return false; }
|
|
||||||
let pos = a.indexOf('://');
|
|
||||||
if ( pos === -1 ) { return false; }
|
|
||||||
a = a.slice(pos);
|
|
||||||
pos = b.indexOf('://');
|
|
||||||
if ( pos !== -1 ) {
|
|
||||||
b = b.slice(pos);
|
|
||||||
}
|
|
||||||
return b !== a;
|
|
||||||
};
|
|
||||||
|
|
||||||
const popupMatch = function(openerURL, targetURL, popupType) {
|
|
||||||
fctxt.setTabOriginFromURL(openerURL)
|
|
||||||
.setDocOriginFromURL(openerURL)
|
|
||||||
.setURL(targetURL)
|
|
||||||
.setType('popup');
|
|
||||||
let result;
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1735
|
|
||||||
// Do not bail out on `data:` URI, they are commonly used for popups.
|
|
||||||
// https://github.com/uBlockOrigin/uAssets/issues/255
|
|
||||||
// Do not bail out on `about:blank`: an `about:blank` popup can be
|
|
||||||
// opened, with the sole purpose to serve as an intermediary in
|
|
||||||
// a sequence of chained popups.
|
|
||||||
// https://github.com/uBlockOrigin/uAssets/issues/263#issuecomment-272615772
|
|
||||||
// Do not bail out, period: the static filtering engine must be
|
|
||||||
// able to examine all sorts of URLs for popup filtering purpose.
|
|
||||||
|
|
||||||
// Dynamic filtering makes sense only when we have a valid opener
|
|
||||||
// hostname.
|
|
||||||
// https://github.com/gorhill/uBlock/commit/1d448b85b2931412508aa01bf899e0b6f0033626#commitcomment-14944764
|
|
||||||
// Ignore bad target URL. On Firefox, an `about:blank` tab may be
|
|
||||||
// opened for a new tab before it is filled in with the real target
|
|
||||||
// URL.
|
|
||||||
if ( fctxt.getTabHostname() !== '' && targetURL !== 'about:blank' ) {
|
|
||||||
// Check per-site switch first
|
|
||||||
// https://github.com/gorhill/uBlock/issues/3060
|
|
||||||
// - The no-popups switch must apply only to popups, not to
|
|
||||||
// popunders.
|
|
||||||
if (
|
|
||||||
popupType === 'popup' &&
|
|
||||||
µb.sessionSwitches.evaluateZ(
|
|
||||||
'no-popups',
|
|
||||||
fctxt.getTabHostname()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
fctxt.filter = {
|
|
||||||
raw: 'no-popups: ' + µb.sessionSwitches.z + ' true',
|
|
||||||
result: 1,
|
|
||||||
source: 'switch'
|
|
||||||
};
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/581
|
|
||||||
// Take into account popup-specific rules in dynamic URL
|
|
||||||
// filtering, OR generic allow rules.
|
|
||||||
result = µb.sessionURLFiltering.evaluateZ(
|
|
||||||
fctxt.getTabHostname(),
|
|
||||||
targetURL,
|
|
||||||
popupType
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
result === 1 && µb.sessionURLFiltering.type === popupType ||
|
|
||||||
result === 2
|
|
||||||
) {
|
|
||||||
fctxt.filter = µb.sessionURLFiltering.toLogData();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/581
|
|
||||||
// Take into account `allow` rules in dynamic filtering: `block`
|
|
||||||
// rules are ignored, as block rules are not meant to block
|
|
||||||
// specific types like `popup` (just like with static filters).
|
|
||||||
result = µb.sessionFirewall.evaluateCellZY(
|
|
||||||
fctxt.getTabHostname(),
|
|
||||||
fctxt.getHostname(),
|
|
||||||
popupType
|
|
||||||
);
|
|
||||||
if ( result === 2 ) {
|
|
||||||
fctxt.filter = µb.sessionFirewall.toLogData();
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/323
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/1142
|
|
||||||
// Don't block if uBlock is turned off in popup's context
|
|
||||||
if ( µb.getNetFilteringSwitch(targetURL) ) {
|
|
||||||
fctxt.type = popupType;
|
|
||||||
result = µb.staticNetFilteringEngine.matchString(fctxt, 0b0001);
|
|
||||||
if ( result !== 0 ) {
|
|
||||||
fctxt.filter = µb.staticNetFilteringEngine.toLogData();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapPopunderResult = function(popunderURL, popunderHostname, result) {
|
|
||||||
if (
|
|
||||||
fctxt.filter === undefined ||
|
|
||||||
fctxt.filter !== 'static' ||
|
|
||||||
fctxt.filter.token === µb.staticNetFilteringEngine.noTokenHash
|
|
||||||
) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if ( fctxt.filter.token === µb.staticNetFilteringEngine.dotTokenHash ) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
const re = new RegExp(fctxt.filter.regex, 'i');
|
|
||||||
const matches = re.exec(popunderURL);
|
|
||||||
if ( matches === null ) { return 0; }
|
|
||||||
const beg = matches.index;
|
|
||||||
const end = beg + matches[0].length;
|
|
||||||
const pos = popunderURL.indexOf(popunderHostname);
|
|
||||||
if ( pos === -1 ) { return 0; }
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1471
|
|
||||||
// We test whether the opener hostname as at least one character
|
|
||||||
// within matched portion of URL.
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1903
|
|
||||||
// Ignore filters which cause a match before the start of the
|
|
||||||
// hostname in the URL.
|
|
||||||
return beg >= pos && beg < pos + popunderHostname.length && end > pos
|
|
||||||
? result
|
|
||||||
: 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const popunderMatch = function(openerURL, targetURL) {
|
|
||||||
let result = popupMatch(targetURL, openerURL, 'popunder');
|
|
||||||
if ( result === 1 ) { return result; }
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1010#issuecomment-186824878
|
|
||||||
// Check the opener tab as if it were the newly opened tab: if there
|
|
||||||
// is a hit against a popup filter, and if the matching filter is not
|
|
||||||
// a broad one, we will consider the opener tab to be a popunder tab.
|
|
||||||
// For now, a "broad" filter is one which does not touch any part of
|
|
||||||
// the hostname part of the opener URL.
|
|
||||||
let popunderURL = openerURL,
|
|
||||||
popunderHostname = µb.URI.hostnameFromURI(popunderURL);
|
|
||||||
if ( popunderHostname === '' ) { return 0; }
|
|
||||||
|
|
||||||
result = mapPopunderResult(
|
|
||||||
popunderURL,
|
|
||||||
popunderHostname,
|
|
||||||
popupMatch(targetURL, popunderURL, 'popup')
|
|
||||||
);
|
|
||||||
if ( result !== 0 ) { return result; }
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1598
|
|
||||||
// Try to find a match against origin part of the opener URL.
|
|
||||||
popunderURL = µb.URI.originFromURI(popunderURL);
|
|
||||||
if ( popunderURL === '' ) { return 0; }
|
|
||||||
|
|
||||||
return mapPopunderResult(
|
|
||||||
popunderURL,
|
|
||||||
popunderHostname,
|
|
||||||
popupMatch(targetURL, popunderURL, 'popup')
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return function(targetTabId, openerDetails) {
|
|
||||||
// Opener details.
|
|
||||||
const openerTabId = openerDetails.tabId;
|
|
||||||
let tabContext = µb.tabContextManager.lookup(openerTabId);
|
|
||||||
if ( tabContext === null ) { return; }
|
|
||||||
const openerURL = tabContext.rawURL;
|
|
||||||
if ( openerURL === '' ) { return; }
|
|
||||||
|
|
||||||
// Popup details.
|
|
||||||
tabContext = µb.tabContextManager.lookup(targetTabId);
|
|
||||||
if ( tabContext === null ) { return; }
|
|
||||||
let targetURL = tabContext.rawURL;
|
|
||||||
if ( targetURL === '' ) { return; }
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/341
|
|
||||||
// Allow popups if uBlock is turned off in opener's context.
|
|
||||||
if ( µb.getNetFilteringSwitch(openerURL) === false ) { return; }
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1538
|
|
||||||
if (
|
|
||||||
µb.getNetFilteringSwitch(µb.normalizePageURL(
|
|
||||||
openerTabId,
|
|
||||||
openerURL)
|
|
||||||
) === false
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the page URL is that of our "blocked page" URL, extract the URL of
|
|
||||||
// the page which was blocked.
|
|
||||||
if ( targetURL.startsWith(vAPI.getURL('document-blocked.html')) ) {
|
|
||||||
const matches = /details=([^&]+)/.exec(targetURL);
|
|
||||||
if ( matches !== null ) {
|
|
||||||
targetURL = JSON.parse(atob(matches[1])).url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popup test.
|
|
||||||
let popupType = 'popup',
|
|
||||||
result = 0;
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2919
|
|
||||||
// - If the target tab matches a clicked link, assume it's legit.
|
|
||||||
if ( areDifferentURLs(targetURL, openerDetails.trustedURL) ) {
|
|
||||||
result = popupMatch(openerURL, targetURL, 'popup');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popunder test.
|
|
||||||
if ( result === 0 && openerDetails.popunder ) {
|
|
||||||
result = popunderMatch(openerURL, targetURL);
|
|
||||||
if ( result === 1 ) {
|
|
||||||
popupType = 'popunder';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log only for when there was a hit against an actual filter (allow or block).
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2776
|
|
||||||
if ( µb.logger.enabled ) {
|
|
||||||
fctxt.setRealm('network').setType(popupType);
|
|
||||||
if ( popupType === 'popup' ) {
|
|
||||||
fctxt.setURL(targetURL)
|
|
||||||
.setTabId(openerTabId)
|
|
||||||
.setTabOriginFromURL(openerURL)
|
|
||||||
.setDocOriginFromURL(openerURL);
|
|
||||||
} else {
|
|
||||||
fctxt.setURL(openerURL)
|
|
||||||
.setTabId(targetTabId)
|
|
||||||
.setTabOriginFromURL(targetURL)
|
|
||||||
.setDocOriginFromURL(targetURL);
|
|
||||||
}
|
|
||||||
fctxt.toLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not blocked
|
|
||||||
if ( result !== 1 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only if a popup was blocked do we report it in the dynamic
|
|
||||||
// filtering pane.
|
|
||||||
const pageStore = µb.pageStoreFromTabId(openerTabId);
|
|
||||||
if ( pageStore ) {
|
|
||||||
pageStore.journalAddRequest(fctxt.getHostname(), result);
|
|
||||||
pageStore.popupBlockedCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blocked
|
|
||||||
if ( µb.userSettings.showIconBadge ) {
|
|
||||||
µb.updateToolbarIcon(openerTabId, 0x02);
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is a popup, block and remove the tab.
|
|
||||||
if ( popupType === 'popup' ) {
|
|
||||||
µb.unbindTabFromPageStats(targetTabId);
|
|
||||||
vAPI.tabs.remove(targetTabId, false);
|
|
||||||
} else {
|
|
||||||
µb.unbindTabFromPageStats(openerTabId);
|
|
||||||
vAPI.tabs.remove(openerTabId, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
vAPI.tabs.registerListeners();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -934,21 +956,22 @@ vAPI.tabs.registerListeners();
|
||||||
const tabIdToDetails = new Map();
|
const tabIdToDetails = new Map();
|
||||||
|
|
||||||
const updateBadge = function(tabId) {
|
const updateBadge = function(tabId) {
|
||||||
|
const µb = µBlock;
|
||||||
const parts = tabIdToDetails.get(tabId);
|
const parts = tabIdToDetails.get(tabId);
|
||||||
tabIdToDetails.delete(tabId);
|
tabIdToDetails.delete(tabId);
|
||||||
|
|
||||||
let state = 0;
|
let state = 0;
|
||||||
let badge = '';
|
let badge = '';
|
||||||
|
|
||||||
let pageStore = this.pageStoreFromTabId(tabId);
|
let pageStore = µb.pageStoreFromTabId(tabId);
|
||||||
if ( pageStore !== null ) {
|
if ( pageStore !== null ) {
|
||||||
state = pageStore.getNetFilteringSwitch() ? 1 : 0;
|
state = pageStore.getNetFilteringSwitch() ? 1 : 0;
|
||||||
if (
|
if (
|
||||||
state === 1 &&
|
state === 1 &&
|
||||||
this.userSettings.showIconBadge &&
|
µb.userSettings.showIconBadge &&
|
||||||
pageStore.perLoadBlockedRequestCount
|
pageStore.perLoadBlockedRequestCount
|
||||||
) {
|
) {
|
||||||
badge = this.formatCount(pageStore.perLoadBlockedRequestCount);
|
badge = µb.formatCount(pageStore.perLoadBlockedRequestCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -958,13 +981,15 @@ vAPI.tabs.registerListeners();
|
||||||
// parts: bit 0 = icon
|
// parts: bit 0 = icon
|
||||||
// bit 1 = badge
|
// bit 1 = badge
|
||||||
|
|
||||||
return function(tabId, newParts) {
|
return function(tabId, newParts = 0b11) {
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||||
if ( newParts === undefined ) { newParts = 0x03; }
|
|
||||||
let currentParts = tabIdToDetails.get(tabId);
|
let currentParts = tabIdToDetails.get(tabId);
|
||||||
if ( currentParts === newParts ) { return; }
|
if ( currentParts === newParts ) { return; }
|
||||||
if ( currentParts === undefined ) {
|
if ( currentParts === undefined ) {
|
||||||
vAPI.setTimeout(updateBadge.bind(this, tabId), 701);
|
self.requestIdleCallback(
|
||||||
|
( ) => updateBadge(tabId),
|
||||||
|
{ timeout: 701 }
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
newParts |= currentParts;
|
newParts |= currentParts;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue