mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-23 19:03:02 +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
@ -275,10 +275,6 @@ vAPI.browserSettings = (function() {
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.tabs = {};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.isBehindTheSceneTabId = function(tabId) {
|
||||
return tabId < 0;
|
||||
};
|
||||
@ -286,166 +282,104 @@ vAPI.isBehindTheSceneTabId = function(tabId) {
|
||||
vAPI.unsetTabId = 0;
|
||||
vAPI.noTabId = -1; // definitely not any existing tab
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// To remove when tabId-as-integer has been tested enough.
|
||||
|
||||
var toChromiumTabId = function(tabId) {
|
||||
return typeof tabId === 'number' && !isNaN(tabId) && tabId > 0 ?
|
||||
tabId :
|
||||
0;
|
||||
const toChromiumTabId = function(tabId) {
|
||||
return typeof tabId === 'number' && isNaN(tabId) === false
|
||||
? tabId
|
||||
: 0;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
// https://developer.chrome.com/extensions/webNavigation
|
||||
// https://developer.chrome.com/extensions/tabs
|
||||
|
||||
vAPI.tabs.registerListeners = function() {
|
||||
// https://developer.chrome.com/extensions/webNavigation
|
||||
// [onCreatedNavigationTarget ->]
|
||||
// onBeforeNavigate ->
|
||||
// onCommitted ->
|
||||
// onDOMContentLoaded ->
|
||||
// onCompleted
|
||||
|
||||
// The chrome.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
|
||||
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 ) {
|
||||
vAPI.Tabs = class {
|
||||
constructor() {
|
||||
browser.webNavigation.onCreatedNavigationTarget.addListener(details => {
|
||||
if ( typeof details.url !== 'string' ) {
|
||||
details.url = '';
|
||||
}
|
||||
if ( /^https?:\/\//.test(details.url) === false ) {
|
||||
details.frameId = 0;
|
||||
details.url = this.sanitizeURL(details.url);
|
||||
this.onNavigation(details);
|
||||
}
|
||||
}
|
||||
if ( vAPI.tabs.onPopupCreated ) {
|
||||
vAPI.tabs.onPopupCreated(
|
||||
this.onCreated(
|
||||
details.tabId,
|
||||
details.sourceTabId
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
browser.webNavigation.onCommitted.addListener(details => {
|
||||
details.url = sanitizeURL(details.url);
|
||||
if ( this.onNavigation ) {
|
||||
browser.webNavigation.onCommitted.addListener(details => {
|
||||
details.url = this.sanitizeURL(details.url);
|
||||
this.onNavigation(details);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/3073
|
||||
// Fall back to `tab.url` when `changeInfo.url` is not set.
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if ( typeof changeInfo.url !== 'string' ) {
|
||||
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
|
||||
);
|
||||
// https://github.com/gorhill/uBlock/issues/3073
|
||||
// Fall back to `tab.url` when `changeInfo.url` is not set.
|
||||
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if ( typeof changeInfo.url !== 'string' ) {
|
||||
changeInfo.url = tab && tab.url;
|
||||
}
|
||||
);
|
||||
return;
|
||||
if ( changeInfo.url ) {
|
||||
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);
|
||||
if ( tabId === 0 ) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
// Properties of the details object:
|
||||
// - url: 'URL', => the address that will be opened
|
||||
// - index: -1, => undefined: end of the list, -1: following tab, or
|
||||
// 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) {
|
||||
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 = ( ) => {
|
||||
create(url, details) {
|
||||
if ( details.active === undefined ) {
|
||||
details.active = true;
|
||||
}
|
||||
|
||||
const subWrapper = ( ) => {
|
||||
const updateDetails = {
|
||||
url: targetURL,
|
||||
url: url,
|
||||
active: !!details.active
|
||||
};
|
||||
|
||||
@ -527,121 +461,162 @@ vAPI.tabs.open = function(details) {
|
||||
|
||||
subWrapper();
|
||||
});
|
||||
};
|
||||
|
||||
if ( !details.select ) {
|
||||
wrapper();
|
||||
return;
|
||||
}
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/3053#issuecomment-332276818
|
||||
// - Do not try to lookup uBO's own pages with FF 55 or less.
|
||||
if (
|
||||
vAPI.webextFlavor.soup.has('firefox') &&
|
||||
vAPI.webextFlavor.major < 56
|
||||
) {
|
||||
wrapper();
|
||||
return;
|
||||
}
|
||||
// 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
|
||||
|
||||
// https://developer.chrome.com/extensions/tabs#method-query
|
||||
// "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);
|
||||
open(details) {
|
||||
let targetURL = details.url;
|
||||
if ( typeof targetURL !== 'string' || targetURL === '' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
browser.tabs.query({ url: targetURLWithoutHash }, tabs => {
|
||||
void browser.runtime.lastError;
|
||||
const tab = Array.isArray(tabs) && tabs[0];
|
||||
if ( !tab ) {
|
||||
wrapper();
|
||||
// extension pages
|
||||
if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
|
||||
targetURL = vAPI.getURL(targetURL);
|
||||
}
|
||||
|
||||
if ( !details.select ) {
|
||||
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;
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/3053#issuecomment-332276818
|
||||
// Do not try to lookup uBO's own pages with FF 55 or less.
|
||||
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; }
|
||||
browser.windows.update(tab.windowId, { focused: true });
|
||||
|
||||
// https://developer.chrome.com/extensions/tabs#method-query
|
||||
// "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) {
|
||||
tabId = toChromiumTabId(tabId);
|
||||
if ( tabId === 0 ) { return; }
|
||||
let targetURL = url;
|
||||
|
||||
chrome.tabs.remove(tabId, vAPI.resetLastError);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
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);
|
||||
// extension pages
|
||||
if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
|
||||
targetURL = vAPI.getURL(targetURL);
|
||||
}
|
||||
};
|
||||
if ( tabId ) {
|
||||
chrome.tabs.executeScript(toChromiumTabId(tabId), details, onScriptExecuted);
|
||||
} else {
|
||||
chrome.tabs.executeScript(details, onScriptExecuted);
|
||||
|
||||
chrome.tabs.update(tabId, { url: targetURL }, vAPI.resetLastError);
|
||||
}
|
||||
|
||||
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}/`;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// 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 ) {
|
||||
candidate.opener.popunder = true;
|
||||
}
|
||||
if ( vAPI.tabs.onPopupUpdated(tabId, candidate.opener) === true ) {
|
||||
if ( µb.onPopupUpdated(tabId, candidate.opener) === true ) {
|
||||
candidate.destroy();
|
||||
} else {
|
||||
candidate.launchSelfDestruction();
|
||||
@ -183,7 +487,7 @@ housekeep itself.
|
||||
}
|
||||
};
|
||||
|
||||
vAPI.tabs.onPopupCreated = function(targetTabId, openerTabId) {
|
||||
const onTabCreated = function(targetTabId, openerTabId) {
|
||||
const popup = popupCandidates.get(targetTabId);
|
||||
if ( popup === undefined ) {
|
||||
popupCandidates.set(
|
||||
@ -472,364 +776,82 @@ housekeep itself.
|
||||
};
|
||||
|
||||
return {
|
||||
push: push,
|
||||
commit: commit,
|
||||
lookup: lookup,
|
||||
mustLookup: mustLookup,
|
||||
exists: exists,
|
||||
createContext: createContext
|
||||
push,
|
||||
commit,
|
||||
lookup,
|
||||
mustLookup,
|
||||
exists,
|
||||
createContext,
|
||||
onTabCreated,
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// When the DOM content of root frame is loaded, this means the tab
|
||||
// content has changed.
|
||||
vAPI.Tabs = class extends vAPI.Tabs {
|
||||
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) {
|
||||
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);
|
||||
onClosed(tabId) {
|
||||
super.onClosed(tabId);
|
||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||
µBlock.unbindTabFromPageStats(tabId);
|
||||
µBlock.contextMenu.update();
|
||||
}
|
||||
|
||||
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);
|
||||
if ( pageStore !== null && pageStore.getNetFilteringSwitch() ) {
|
||||
µb.scriptletFilteringEngine.injectNow(details);
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// 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();
|
||||
vAPI.tabs = new vAPI.Tabs();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
@ -934,21 +956,22 @@ vAPI.tabs.registerListeners();
|
||||
const tabIdToDetails = new Map();
|
||||
|
||||
const updateBadge = function(tabId) {
|
||||
const µb = µBlock;
|
||||
const parts = tabIdToDetails.get(tabId);
|
||||
tabIdToDetails.delete(tabId);
|
||||
|
||||
let state = 0;
|
||||
let badge = '';
|
||||
|
||||
let pageStore = this.pageStoreFromTabId(tabId);
|
||||
let pageStore = µb.pageStoreFromTabId(tabId);
|
||||
if ( pageStore !== null ) {
|
||||
state = pageStore.getNetFilteringSwitch() ? 1 : 0;
|
||||
if (
|
||||
state === 1 &&
|
||||
this.userSettings.showIconBadge &&
|
||||
µb.userSettings.showIconBadge &&
|
||||
pageStore.perLoadBlockedRequestCount
|
||||
) {
|
||||
badge = this.formatCount(pageStore.perLoadBlockedRequestCount);
|
||||
badge = µb.formatCount(pageStore.perLoadBlockedRequestCount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -958,13 +981,15 @@ vAPI.tabs.registerListeners();
|
||||
// parts: bit 0 = icon
|
||||
// bit 1 = badge
|
||||
|
||||
return function(tabId, newParts) {
|
||||
return function(tabId, newParts = 0b11) {
|
||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||
if ( newParts === undefined ) { newParts = 0x03; }
|
||||
let currentParts = tabIdToDetails.get(tabId);
|
||||
if ( currentParts === newParts ) { return; }
|
||||
if ( currentParts === undefined ) {
|
||||
vAPI.setTimeout(updateBadge.bind(this, tabId), 701);
|
||||
self.requestIdleCallback(
|
||||
( ) => updateBadge(tabId),
|
||||
{ timeout: 701 }
|
||||
);
|
||||
} else {
|
||||
newParts |= currentParts;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user