mirror of
https://github.com/gorhill/uMatrix.git
synced 2024-05-19 03:33:17 +12:00
Bring uMatrix up to date
Notably: - Import logger improvements from uBO - Import CNAME uncloaking from uBO - Import more improvements from uBO - Make use of modern JS features This should un-stall further development of uMatrix.
This commit is contained in:
parent
1d936742a7
commit
9b292304d3
|
@ -2,11 +2,13 @@
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"eqeqeq": true,
|
"eqeqeq": true,
|
||||||
"esnext": true,
|
"esversion": 8,
|
||||||
"globals": {
|
"globals": {
|
||||||
"browser": false, // global variable in Firefox, Edge
|
"browser": false, // global variable in Firefox, Edge
|
||||||
"self": false,
|
"self": false,
|
||||||
"chrome": false,
|
"chrome": false,
|
||||||
|
"log": false,
|
||||||
|
"webext": false,
|
||||||
"vAPI": false,
|
"vAPI": false,
|
||||||
"µMatrix": false
|
"µMatrix": false
|
||||||
},
|
},
|
||||||
|
|
2
dist/version
vendored
2
dist/version
vendored
|
@ -1 +1 @@
|
||||||
1.4.0
|
1.4.1.0
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["http://*/*", "https://*/*"],
|
"matches": ["http://*/*", "https://*/*"],
|
||||||
"js": ["/js/vapi-client.js", "/js/contentscript-start.js"],
|
"js": ["/js/vapi.js", "/js/vapi-client.js", "/js/contentscript-start.js"],
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"all_frames": true
|
"all_frames": true
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load diff
308
platform/chromium/vapi-client-extra.js
Normal file
308
platform/chromium/vapi-client-extra.js
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2019-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
// For non-background page
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Direct messaging connection ability
|
||||||
|
|
||||||
|
(( ) => {
|
||||||
|
// >>>>>>>> start of private namespace
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof vAPI !== 'object' ||
|
||||||
|
vAPI.messaging instanceof Object === false ||
|
||||||
|
vAPI.MessagingConnection instanceof Function
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listeners = new Set();
|
||||||
|
const connections = new Map();
|
||||||
|
|
||||||
|
vAPI.MessagingConnection = class {
|
||||||
|
constructor(handler, details) {
|
||||||
|
this.messaging = vAPI.messaging;
|
||||||
|
this.handler = handler;
|
||||||
|
this.id = details.id;
|
||||||
|
this.to = details.to;
|
||||||
|
this.toToken = details.toToken;
|
||||||
|
this.from = details.from;
|
||||||
|
this.fromToken = details.fromToken;
|
||||||
|
this.checkTimer = undefined;
|
||||||
|
// On Firefox it appears ports are not automatically disconnected
|
||||||
|
// when navigating to another page.
|
||||||
|
const ctor = vAPI.MessagingConnection;
|
||||||
|
if ( ctor.pagehide !== undefined ) { return; }
|
||||||
|
ctor.pagehide = ( ) => {
|
||||||
|
for ( const connection of connections.values() ) {
|
||||||
|
connection.disconnect();
|
||||||
|
connection.handler(
|
||||||
|
connection.toDetails('connectionBroken')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('pagehide', ctor.pagehide);
|
||||||
|
}
|
||||||
|
toDetails(what, payload) {
|
||||||
|
return {
|
||||||
|
what: what,
|
||||||
|
id: this.id,
|
||||||
|
from: this.from,
|
||||||
|
fromToken: this.fromToken,
|
||||||
|
to: this.to,
|
||||||
|
toToken: this.toToken,
|
||||||
|
payload: payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
disconnect() {
|
||||||
|
if ( this.checkTimer !== undefined ) {
|
||||||
|
clearTimeout(this.checkTimer);
|
||||||
|
this.checkTimer = undefined;
|
||||||
|
}
|
||||||
|
connections.delete(this.id);
|
||||||
|
const port = this.messaging.getPort();
|
||||||
|
if ( port === null ) { return; }
|
||||||
|
port.postMessage({
|
||||||
|
channel: 'vapi',
|
||||||
|
msg: this.toDetails('connectionBroken'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
checkAsync() {
|
||||||
|
if ( this.checkTimer !== undefined ) {
|
||||||
|
clearTimeout(this.checkTimer);
|
||||||
|
}
|
||||||
|
this.checkTimer = vAPI.setTimeout(
|
||||||
|
( ) => { this.check(); },
|
||||||
|
499
|
||||||
|
);
|
||||||
|
}
|
||||||
|
check() {
|
||||||
|
this.checkTimer = undefined;
|
||||||
|
if ( connections.has(this.id) === false ) { return; }
|
||||||
|
const port = this.messaging.getPort();
|
||||||
|
if ( port === null ) { return; }
|
||||||
|
port.postMessage({
|
||||||
|
channel: 'vapi',
|
||||||
|
msg: this.toDetails('connectionCheck'),
|
||||||
|
});
|
||||||
|
this.checkAsync();
|
||||||
|
}
|
||||||
|
receive(details) {
|
||||||
|
switch ( details.what ) {
|
||||||
|
case 'connectionAccepted':
|
||||||
|
this.toToken = details.toToken;
|
||||||
|
this.handler(details);
|
||||||
|
this.checkAsync();
|
||||||
|
break;
|
||||||
|
case 'connectionBroken':
|
||||||
|
connections.delete(this.id);
|
||||||
|
this.handler(details);
|
||||||
|
break;
|
||||||
|
case 'connectionMessage':
|
||||||
|
this.handler(details);
|
||||||
|
this.checkAsync();
|
||||||
|
break;
|
||||||
|
case 'connectionCheck':
|
||||||
|
const port = this.messaging.getPort();
|
||||||
|
if ( port === null ) { return; }
|
||||||
|
if ( connections.has(this.id) ) {
|
||||||
|
this.checkAsync();
|
||||||
|
} else {
|
||||||
|
details.what = 'connectionBroken';
|
||||||
|
port.postMessage({ channel: 'vapi', msg: details });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'connectionRefused':
|
||||||
|
connections.delete(this.id);
|
||||||
|
this.handler(details);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send(payload) {
|
||||||
|
const port = this.messaging.getPort();
|
||||||
|
if ( port === null ) { return; }
|
||||||
|
port.postMessage({
|
||||||
|
channel: 'vapi',
|
||||||
|
msg: this.toDetails('connectionMessage', payload),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static addListener(listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
static async connectTo(from, to, handler) {
|
||||||
|
const port = vAPI.messaging.getPort();
|
||||||
|
if ( port === null ) { return; }
|
||||||
|
const connection = new vAPI.MessagingConnection(handler, {
|
||||||
|
id: `${from}-${to}-${vAPI.sessionId}`,
|
||||||
|
to: to,
|
||||||
|
from: from,
|
||||||
|
fromToken: port.name
|
||||||
|
});
|
||||||
|
connections.set(connection.id, connection);
|
||||||
|
port.postMessage({
|
||||||
|
channel: 'vapi',
|
||||||
|
msg: {
|
||||||
|
what: 'connectionRequested',
|
||||||
|
id: connection.id,
|
||||||
|
from: from,
|
||||||
|
fromToken: port.name,
|
||||||
|
to: to,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return connection.id;
|
||||||
|
}
|
||||||
|
static disconnectFrom(connectionId) {
|
||||||
|
const connection = connections.get(connectionId);
|
||||||
|
if ( connection === undefined ) { return; }
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
static sendTo(connectionId, payload) {
|
||||||
|
const connection = connections.get(connectionId);
|
||||||
|
if ( connection === undefined ) { return; }
|
||||||
|
connection.send(payload);
|
||||||
|
}
|
||||||
|
static canDestroyPort() {
|
||||||
|
return listeners.length === 0 && connections.size === 0;
|
||||||
|
}
|
||||||
|
static mustDestroyPort() {
|
||||||
|
if ( connections.size === 0 ) { return; }
|
||||||
|
for ( const connection of connections.values() ) {
|
||||||
|
connection.receive({ what: 'connectionBroken' });
|
||||||
|
}
|
||||||
|
connections.clear();
|
||||||
|
}
|
||||||
|
static canProcessMessage(details) {
|
||||||
|
if ( details.channel !== 'vapi' ) { return; }
|
||||||
|
switch ( details.msg.what ) {
|
||||||
|
case 'connectionAccepted':
|
||||||
|
case 'connectionBroken':
|
||||||
|
case 'connectionCheck':
|
||||||
|
case 'connectionMessage':
|
||||||
|
case 'connectionRefused': {
|
||||||
|
const connection = connections.get(details.msg.id);
|
||||||
|
if ( connection === undefined ) { break; }
|
||||||
|
connection.receive(details.msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 'connectionRequested':
|
||||||
|
if ( listeners.length === 0 ) { return; }
|
||||||
|
const port = vAPI.messaging.getPort();
|
||||||
|
if ( port === null ) { break; }
|
||||||
|
let listener, result;
|
||||||
|
for ( listener of listeners ) {
|
||||||
|
result = listener(details.msg);
|
||||||
|
if ( result !== undefined ) { break; }
|
||||||
|
}
|
||||||
|
if ( result === undefined ) { break; }
|
||||||
|
if ( result === true ) {
|
||||||
|
details.msg.what = 'connectionAccepted';
|
||||||
|
details.msg.toToken = port.name;
|
||||||
|
const connection = new vAPI.MessagingConnection(
|
||||||
|
listener,
|
||||||
|
details.msg
|
||||||
|
);
|
||||||
|
connections.set(connection.id, connection);
|
||||||
|
} else {
|
||||||
|
details.msg.what = 'connectionRefused';
|
||||||
|
}
|
||||||
|
port.postMessage(details);
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vAPI.messaging.extensions.push(vAPI.MessagingConnection);
|
||||||
|
|
||||||
|
// <<<<<<<< end of private namespace
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Broadcast listening ability
|
||||||
|
|
||||||
|
(( ) => {
|
||||||
|
// >>>>>>>> start of private namespace
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof vAPI !== 'object' ||
|
||||||
|
vAPI.messaging instanceof Object === false ||
|
||||||
|
vAPI.broadcastListener instanceof Object
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listeners = new Set();
|
||||||
|
|
||||||
|
vAPI.broadcastListener = {
|
||||||
|
add: function(listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
vAPI.messaging.getPort();
|
||||||
|
},
|
||||||
|
remove: function(listener) {
|
||||||
|
listeners.delete(listener);
|
||||||
|
},
|
||||||
|
canDestroyPort() {
|
||||||
|
return listeners.size === 0;
|
||||||
|
},
|
||||||
|
mustDestroyPort() {
|
||||||
|
listeners.clear();
|
||||||
|
},
|
||||||
|
canProcessMessage(details) {
|
||||||
|
if ( details.broadcast === false ) { return; }
|
||||||
|
for ( const listener of listeners ) {
|
||||||
|
listener(details.msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
vAPI.messaging.extensions.push(vAPI.broadcastListener);
|
||||||
|
|
||||||
|
// <<<<<<<< end of private namespace
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
DO NOT:
|
||||||
|
- Remove the following code
|
||||||
|
- Add code beyond the following code
|
||||||
|
Reason:
|
||||||
|
- https://github.com/gorhill/uBlock/pull/3721
|
||||||
|
- uBO never uses the return value from injected content scripts
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
void 0;
|
|
@ -1,7 +1,8 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uMatrix - a browser extension to block requests.
|
uBlock Origin - a browser extension to block requests.
|
||||||
Copyright (C) 2014-2018 The uMatrix/uBlock Origin authors
|
Copyright (C) 2014-2015 The uBlock Origin authors
|
||||||
|
Copyright (C) 2014-present Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,60 +17,61 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
Home: https://github.com/gorhill/uMatrix
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// For non background pages
|
// For non-background page
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function(self) {
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10
|
|
||||||
if ( self.vAPI === undefined || self.vAPI.uMatrix !== true ) {
|
|
||||||
self.vAPI = { uMatrix: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
var vAPI = self.vAPI;
|
|
||||||
var chrome = self.chrome;
|
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/456
|
// https://github.com/chrisaljoudi/uBlock/issues/456
|
||||||
// Already injected?
|
// Skip if already injected.
|
||||||
if ( vAPI.vapiClientInjected ) {
|
|
||||||
//console.debug('vapi-client.js already injected: skipping.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
vAPI.vapiClientInjected = true;
|
|
||||||
|
|
||||||
vAPI.sessionId = String.fromCharCode(Date.now() % 26 + 97) +
|
// >>>>>>>> start of HUGE-IF-BLOCK
|
||||||
Math.random().toString(36).slice(2);
|
if (
|
||||||
|
typeof vAPI === 'object' &&
|
||||||
|
vAPI.randomToken instanceof Function === false
|
||||||
|
) {
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
vAPI.randomToken = function() {
|
||||||
|
const now = Date.now();
|
||||||
|
return String.fromCharCode(now % 26 + 97) +
|
||||||
|
Math.floor((1 + Math.random()) * now).toString(36);
|
||||||
|
};
|
||||||
|
|
||||||
|
vAPI.sessionId = vAPI.randomToken();
|
||||||
|
vAPI.setTimeout = vAPI.setTimeout || self.setTimeout.bind(self);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.shutdown = (function() {
|
vAPI.shutdown = {
|
||||||
var jobs = [];
|
jobs: [],
|
||||||
|
add: function(job) {
|
||||||
var add = function(job) {
|
this.jobs.push(job);
|
||||||
jobs.push(job);
|
},
|
||||||
};
|
exec: function() {
|
||||||
|
// Shutdown asynchronously, to ensure shutdown jobs are called from
|
||||||
var exec = function() {
|
// the top context.
|
||||||
//console.debug('Shutting down...');
|
self.requestIdleCallback(( ) => {
|
||||||
var job;
|
const jobs = this.jobs.slice();
|
||||||
while ( (job = jobs.pop()) ) {
|
this.jobs.length = 0;
|
||||||
job();
|
while ( jobs.length !== 0 ) {
|
||||||
|
(jobs.pop())();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
remove: function(job) {
|
||||||
|
let pos;
|
||||||
|
while ( (pos = this.jobs.indexOf(job)) !== -1 ) {
|
||||||
|
this.jobs.splice(pos, 1);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
return {
|
|
||||||
add: add,
|
|
||||||
exec: exec
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -77,9 +79,10 @@ vAPI.messaging = {
|
||||||
port: null,
|
port: null,
|
||||||
portTimer: null,
|
portTimer: null,
|
||||||
portTimerDelay: 10000,
|
portTimerDelay: 10000,
|
||||||
listeners: new Set(),
|
extended: undefined,
|
||||||
|
extensions: [],
|
||||||
|
msgIdGenerator: 1,
|
||||||
pending: new Map(),
|
pending: new Map(),
|
||||||
auxProcessId: 1,
|
|
||||||
shuttingDown: false,
|
shuttingDown: false,
|
||||||
|
|
||||||
shutdown: function() {
|
shutdown: function() {
|
||||||
|
@ -87,41 +90,53 @@ vAPI.messaging = {
|
||||||
this.destroyPort();
|
this.destroyPort();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/403
|
||||||
|
// Spurious disconnection can happen, so do not consider such events
|
||||||
|
// as world-ending, i.e. stay around. Except for embedded frames.
|
||||||
|
|
||||||
disconnectListener: function() {
|
disconnectListener: function() {
|
||||||
this.port = null;
|
this.port = null;
|
||||||
vAPI.shutdown.exec();
|
if ( window !== window.top ) {
|
||||||
|
vAPI.shutdown.exec();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
disconnectListenerBound: null,
|
disconnectListenerBound: null,
|
||||||
|
|
||||||
messageListener: function(details) {
|
messageListener: function(details) {
|
||||||
if ( !details ) { return; }
|
if ( details instanceof Object === false ) { return; }
|
||||||
|
|
||||||
// Sent to all listeners
|
|
||||||
if ( details.broadcast ) {
|
|
||||||
this.sendToListeners(details.msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response to specific message previously sent
|
// Response to specific message previously sent
|
||||||
var listener;
|
if ( details.msgId !== undefined ) {
|
||||||
if ( details.auxProcessId ) {
|
const resolver = this.pending.get(details.msgId);
|
||||||
listener = this.pending.get(details.auxProcessId);
|
if ( resolver !== undefined ) {
|
||||||
if ( listener !== undefined ) {
|
this.pending.delete(details.msgId);
|
||||||
this.pending.delete(details.auxProcessId);
|
resolver(details.msg);
|
||||||
listener(details.msg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unhandled messages
|
||||||
|
this.extensions.every(ext => ext.canProcessMessage(details) !== true);
|
||||||
|
},
|
||||||
|
messageListenerBound: null,
|
||||||
|
|
||||||
|
canDestroyPort: function() {
|
||||||
|
return this.pending.size === 0 &&
|
||||||
|
(
|
||||||
|
this.extensions.length === 0 ||
|
||||||
|
this.extensions.every(e => e.canDestroyPort())
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
mustDestroyPort: function() {
|
||||||
|
if ( this.extensions.length === 0 ) { return; }
|
||||||
|
this.extensions.forEach(e => e.mustDestroyPort());
|
||||||
|
this.extensions.length = 0;
|
||||||
},
|
},
|
||||||
messageListenerCallback: null,
|
|
||||||
|
|
||||||
portPoller: function() {
|
portPoller: function() {
|
||||||
this.portTimer = null;
|
this.portTimer = null;
|
||||||
if (
|
if ( this.port !== null && this.canDestroyPort() ) {
|
||||||
this.port !== null &&
|
|
||||||
this.listeners.size === 0 &&
|
|
||||||
this.pending.size === 0
|
|
||||||
) {
|
|
||||||
return this.destroyPort();
|
return this.destroyPort();
|
||||||
}
|
}
|
||||||
this.portTimer = vAPI.setTimeout(this.portPollerBound, this.portTimerDelay);
|
this.portTimer = vAPI.setTimeout(this.portPollerBound, this.portTimerDelay);
|
||||||
|
@ -134,48 +149,50 @@ vAPI.messaging = {
|
||||||
clearTimeout(this.portTimer);
|
clearTimeout(this.portTimer);
|
||||||
this.portTimer = null;
|
this.portTimer = null;
|
||||||
}
|
}
|
||||||
var port = this.port;
|
const port = this.port;
|
||||||
if ( port !== null ) {
|
if ( port !== null ) {
|
||||||
port.disconnect();
|
port.disconnect();
|
||||||
port.onMessage.removeListener(this.messageListenerCallback);
|
port.onMessage.removeListener(this.messageListenerBound);
|
||||||
port.onDisconnect.removeListener(this.disconnectListenerBound);
|
port.onDisconnect.removeListener(this.disconnectListenerBound);
|
||||||
this.port = null;
|
this.port = null;
|
||||||
}
|
}
|
||||||
this.listeners.clear();
|
this.mustDestroyPort();
|
||||||
// service pending callbacks
|
// service pending callbacks
|
||||||
if ( this.pending.size !== 0 ) {
|
if ( this.pending.size !== 0 ) {
|
||||||
var pending = this.pending;
|
const pending = this.pending;
|
||||||
this.pending = new Map();
|
this.pending = new Map();
|
||||||
for ( var callback of pending.values() ) {
|
for ( const resolver of pending.values() ) {
|
||||||
if ( typeof callback === 'function' ) {
|
resolver();
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
createPort: function() {
|
createPort: function() {
|
||||||
if ( this.shuttingDown ) { return null; }
|
if ( this.shuttingDown ) { return null; }
|
||||||
if ( this.messageListenerCallback === null ) {
|
if ( this.messageListenerBound === null ) {
|
||||||
this.messageListenerCallback = this.messageListener.bind(this);
|
this.messageListenerBound = this.messageListener.bind(this);
|
||||||
this.disconnectListenerBound = this.disconnectListener.bind(this);
|
this.disconnectListenerBound = this.disconnectListener.bind(this);
|
||||||
this.portPollerBound = this.portPoller.bind(this);
|
this.portPollerBound = this.portPoller.bind(this);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.port = chrome.runtime.connect({name: vAPI.sessionId}) || null;
|
this.port = browser.runtime.connect({name: vAPI.sessionId}) || null;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this.port = null;
|
this.port = null;
|
||||||
}
|
}
|
||||||
if ( this.port !== null ) {
|
// Not having a valid port at this point means the main process is
|
||||||
this.port.onMessage.addListener(this.messageListenerCallback);
|
// not available: no point keeping the content scripts alive.
|
||||||
this.port.onDisconnect.addListener(this.disconnectListenerBound);
|
if ( this.port === null ) {
|
||||||
this.portTimerDelay = 10000;
|
vAPI.shutdown.exec();
|
||||||
if ( this.portTimer === null ) {
|
return null;
|
||||||
this.portTimer = vAPI.setTimeout(
|
}
|
||||||
this.portPollerBound,
|
this.port.onMessage.addListener(this.messageListenerBound);
|
||||||
this.portTimerDelay
|
this.port.onDisconnect.addListener(this.disconnectListenerBound);
|
||||||
);
|
this.portTimerDelay = 10000;
|
||||||
}
|
if ( this.portTimer === null ) {
|
||||||
|
this.portTimer = vAPI.setTimeout(
|
||||||
|
this.portPollerBound,
|
||||||
|
this.portTimerDelay
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return this.port;
|
return this.port;
|
||||||
},
|
},
|
||||||
|
@ -184,70 +201,68 @@ vAPI.messaging = {
|
||||||
return this.port !== null ? this.port : this.createPort();
|
return this.port !== null ? this.port : this.createPort();
|
||||||
},
|
},
|
||||||
|
|
||||||
send: function(channelName, message, callback) {
|
send: function(channel, msg) {
|
||||||
// Too large a gap between the last request and the last response means
|
// Too large a gap between the last request and the last response means
|
||||||
// the main process is no longer reachable: memory leaks and bad
|
// the main process is no longer reachable: memory leaks and bad
|
||||||
// performance become a risk -- especially for long-lived, dynamic
|
// performance become a risk -- especially for long-lived, dynamic
|
||||||
// pages. Guard against this.
|
// pages. Guard against this.
|
||||||
if ( this.pending.size > 25 ) {
|
if ( this.pending.size > 50 ) {
|
||||||
vAPI.shutdown.exec();
|
vAPI.shutdown.exec();
|
||||||
}
|
}
|
||||||
var port = this.getPort();
|
const port = this.getPort();
|
||||||
if ( port === null ) {
|
if ( port === null ) {
|
||||||
if ( typeof callback === 'function' ) { callback(); }
|
return Promise.resolve();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
var auxProcessId;
|
const msgId = this.msgIdGenerator++;
|
||||||
if ( callback ) {
|
const promise = new Promise(resolve => {
|
||||||
auxProcessId = this.auxProcessId++;
|
this.pending.set(msgId, resolve);
|
||||||
this.pending.set(auxProcessId, callback);
|
|
||||||
}
|
|
||||||
port.postMessage({
|
|
||||||
channelName: channelName,
|
|
||||||
auxProcessId: auxProcessId,
|
|
||||||
msg: message
|
|
||||||
});
|
});
|
||||||
|
port.postMessage({ channel, msgId, msg });
|
||||||
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
addListener: function(listener) {
|
// Dynamically extend capabilities.
|
||||||
this.listeners.add(listener);
|
extend: function() {
|
||||||
this.getPort();
|
if ( this.extended === undefined ) {
|
||||||
},
|
this.extended = vAPI.messaging.send('vapi', {
|
||||||
|
what: 'extendClient'
|
||||||
removeListener: function(listener) {
|
}).then(( ) => {
|
||||||
this.listeners.delete(listener);
|
return self.vAPI instanceof Object &&
|
||||||
},
|
this.extensions.length !== 0;
|
||||||
|
}).catch(( ) => {
|
||||||
removeAllListeners: function() {
|
});
|
||||||
this.listeners.clear();
|
|
||||||
},
|
|
||||||
|
|
||||||
sendToListeners: function(msg) {
|
|
||||||
for ( var listener of this.listeners ) {
|
|
||||||
listener(msg);
|
|
||||||
}
|
}
|
||||||
}
|
return this.extended;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
vAPI.shutdown.add(( ) => {
|
||||||
|
vAPI.messaging.shutdown();
|
||||||
|
window.vAPI = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// No need to have vAPI client linger around after shutdown if
|
|
||||||
// we are not a top window (because element picker can still
|
|
||||||
// be injected in top window).
|
|
||||||
if ( window !== window.top ) {
|
|
||||||
vAPI.shutdown.add(function() {
|
|
||||||
vAPI = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
// <<<<<<<< end of HUGE-IF-BLOCK
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.setTimeout = vAPI.setTimeout || function(callback, delay) {
|
|
||||||
setTimeout(function() { callback(); }, delay);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
})(this); // jshint ignore: line
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
DO NOT:
|
||||||
|
- Remove the following code
|
||||||
|
- Add code beyond the following code
|
||||||
|
Reason:
|
||||||
|
- https://github.com/gorhill/uBlock/pull/3721
|
||||||
|
- uBO never uses the return value from injected content scripts
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
void 0;
|
||||||
|
|
|
@ -23,32 +23,14 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
if ( self.browser instanceof Object ) {
|
|
||||||
self.chrome = self.browser;
|
|
||||||
} else {
|
|
||||||
self.browser = self.chrome;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function(self) {
|
vAPI.T0 = Date.now();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10
|
vAPI.setTimeout = vAPI.setTimeout || self.setTimeout.bind(self);
|
||||||
if ( self.vAPI === undefined || self.vAPI.uMatrix !== true ) {
|
|
||||||
self.vAPI = { uMatrix: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
var vAPI = self.vAPI;
|
|
||||||
var chrome = self.chrome;
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.setTimeout = vAPI.setTimeout || window.setTimeout.bind(window);
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -57,113 +39,182 @@ vAPI.webextFlavor = {
|
||||||
soup: new Set()
|
soup: new Set()
|
||||||
};
|
};
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
var ua = navigator.userAgent,
|
const ua = navigator.userAgent;
|
||||||
flavor = vAPI.webextFlavor,
|
const flavor = vAPI.webextFlavor;
|
||||||
soup = flavor.soup;
|
const soup = flavor.soup;
|
||||||
var dispatch = function() {
|
const dispatch = function() {
|
||||||
window.dispatchEvent(new CustomEvent('webextFlavor'));
|
window.dispatchEvent(new CustomEvent('webextFlavor'));
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is always true.
|
// This is always true.
|
||||||
soup.add('ublock');
|
soup.add('ublock').add('webext');
|
||||||
|
|
||||||
|
// Whether this is a dev build.
|
||||||
|
if ( /^\d+\.\d+\.\d+\D/.test(browser.runtime.getManifest().version) ) {
|
||||||
|
soup.add('devbuild');
|
||||||
|
}
|
||||||
|
|
||||||
if ( /\bMobile\b/.test(ua) ) {
|
if ( /\bMobile\b/.test(ua) ) {
|
||||||
soup.add('mobile');
|
soup.add('mobile');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asynchronous
|
// Asynchronous
|
||||||
var async = self.browser instanceof Object &&
|
if (
|
||||||
typeof self.browser.runtime.getBrowserInfo === 'function';
|
browser instanceof Object &&
|
||||||
if ( async ) {
|
typeof browser.runtime.getBrowserInfo === 'function'
|
||||||
self.browser.runtime.getBrowserInfo().then(function(info) {
|
) {
|
||||||
flavor.major = parseInt(info.version, 10) || 0;
|
browser.runtime.getBrowserInfo().then(info => {
|
||||||
|
flavor.major = parseInt(info.version, 10) || 60;
|
||||||
soup.add(info.vendor.toLowerCase())
|
soup.add(info.vendor.toLowerCase())
|
||||||
.add(info.name.toLowerCase());
|
.add(info.name.toLowerCase());
|
||||||
if ( flavor.major >= 53 ) { soup.add('user_stylesheet'); }
|
if ( soup.has('firefox') && flavor.major < 57 ) {
|
||||||
if ( flavor.major >= 57 ) { soup.add('html_filtering'); }
|
soup.delete('html_filtering');
|
||||||
|
}
|
||||||
dispatch();
|
dispatch();
|
||||||
});
|
});
|
||||||
|
if ( browser.runtime.getURL('').startsWith('moz-extension://') ) {
|
||||||
|
soup.add('mozilla')
|
||||||
|
.add('firefox')
|
||||||
|
.add('user_stylesheet')
|
||||||
|
.add('html_filtering');
|
||||||
|
flavor.major = 60;
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous
|
// Synchronous -- order of tests is important
|
||||||
var match = /Firefox\/([\d.]+)/.exec(ua);
|
let match;
|
||||||
if ( match !== null ) {
|
if ( (match = /\bEdge\/(\d+)/.exec(ua)) !== null ) {
|
||||||
flavor.major = parseInt(match[1], 10) || 0;
|
flavor.major = parseInt(match[1], 10) || 0;
|
||||||
soup.add('mozilla')
|
soup.add('microsoft').add('edge');
|
||||||
.add('firefox');
|
} else if ( (match = /\bOPR\/(\d+)/.exec(ua)) !== null ) {
|
||||||
if ( flavor.major >= 53 ) { soup.add('user_stylesheet'); }
|
const reEx = /\bChrom(?:e|ium)\/([\d.]+)/;
|
||||||
if ( flavor.major >= 57 ) { soup.add('html_filtering'); }
|
if ( reEx.test(ua) ) { match = reEx.exec(ua); }
|
||||||
} else {
|
flavor.major = parseInt(match[1], 10) || 0;
|
||||||
match = /OPR\/([\d.]+)/.exec(ua);
|
soup.add('opera').add('chromium');
|
||||||
if ( match !== null ) {
|
} else if ( (match = /\bChromium\/(\d+)/.exec(ua)) !== null ) {
|
||||||
var reEx = /Chrom(?:e|ium)\/([\d.]+)/;
|
flavor.major = parseInt(match[1], 10) || 0;
|
||||||
if ( reEx.test(ua) ) { match = reEx.exec(ua); }
|
soup.add('chromium');
|
||||||
flavor.major = parseInt(match[1], 10) || 0;
|
} else if ( (match = /\bChrome\/(\d+)/.exec(ua)) !== null ) {
|
||||||
soup.add('opera').add('chromium');
|
flavor.major = parseInt(match[1], 10) || 0;
|
||||||
} else {
|
soup.add('google').add('chromium');
|
||||||
match = /Chromium\/([\d.]+)/.exec(ua);
|
} else if ( (match = /\bSafari\/(\d+)/.exec(ua)) !== null ) {
|
||||||
if ( match !== null ) {
|
flavor.major = parseInt(match[1], 10) || 0;
|
||||||
flavor.major = parseInt(match[1], 10) || 0;
|
soup.add('apple').add('safari');
|
||||||
soup.add('chromium');
|
}
|
||||||
} else {
|
|
||||||
match = /Chrome\/([\d.]+)/.exec(ua);
|
// https://github.com/gorhill/uBlock/issues/3588
|
||||||
if ( match !== null ) {
|
if ( soup.has('chromium') && flavor.major >= 66 ) {
|
||||||
flavor.major = parseInt(match[1], 10) || 0;
|
soup.add('user_stylesheet');
|
||||||
soup.add('google').add('chromium');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// https://github.com/gorhill/uBlock/issues/3588
|
|
||||||
if ( soup.has('chromium') && flavor.major >= 67 ) {
|
|
||||||
soup.add('user_stylesheet');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't starve potential listeners
|
// Don't starve potential listeners
|
||||||
if ( !async ) {
|
vAPI.setTimeout(dispatch, 97);
|
||||||
vAPI.setTimeout(dispatch, 97);
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// http://www.w3.org/International/questions/qa-scripts#directions
|
{
|
||||||
|
const punycode = self.punycode;
|
||||||
|
const reCommonHostnameFromURL = /^https?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//;
|
||||||
|
const reAuthorityFromURI = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/;
|
||||||
|
const reHostFromNakedAuthority = /^[0-9a-z._-]+[0-9a-z]$/i;
|
||||||
|
const reHostFromAuthority = /^(?:[^@]*@)?([^:]+)(?::\d*)?$/;
|
||||||
|
const reIPv6FromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]+\])(?::\d*)?$/i;
|
||||||
|
const reMustNormalizeHostname = /[^0-9a-z._-]/;
|
||||||
|
|
||||||
var setScriptDirection = function(language) {
|
vAPI.hostnameFromURI = function(uri) {
|
||||||
document.body.setAttribute(
|
let matches = reCommonHostnameFromURL.exec(uri);
|
||||||
'dir',
|
if ( matches !== null ) { return matches[1]; }
|
||||||
['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) !== -1 ? 'rtl' : 'ltr'
|
matches = reAuthorityFromURI.exec(uri);
|
||||||
);
|
if ( matches === null ) { return ''; }
|
||||||
};
|
const authority = matches[1].slice(2);
|
||||||
|
if ( reHostFromNakedAuthority.test(authority) ) {
|
||||||
|
return authority.toLowerCase();
|
||||||
|
}
|
||||||
|
matches = reHostFromAuthority.exec(authority);
|
||||||
|
if ( matches === null ) {
|
||||||
|
matches = reIPv6FromAuthority.exec(authority);
|
||||||
|
if ( matches === null ) { return ''; }
|
||||||
|
}
|
||||||
|
let hostname = matches[1];
|
||||||
|
while ( hostname.endsWith('.') ) {
|
||||||
|
hostname = hostname.slice(0, -1);
|
||||||
|
}
|
||||||
|
if ( reMustNormalizeHostname.test(hostname) ) {
|
||||||
|
hostname = punycode.toASCII(hostname.toLowerCase());
|
||||||
|
}
|
||||||
|
return hostname;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reHostnameFromNetworkURL =
|
||||||
|
/^(?:http|ws|ftp)s?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//;
|
||||||
|
|
||||||
|
vAPI.hostnameFromNetworkURL = function(url) {
|
||||||
|
const matches = reHostnameFromNetworkURL.exec(url);
|
||||||
|
return matches !== null ? matches[1] : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const psl = self.publicSuffixList;
|
||||||
|
const reIPAddressNaive = /^\d+\.\d+\.\d+\.\d+$|^\[[\da-zA-Z:]+\]$/;
|
||||||
|
|
||||||
|
vAPI.domainFromHostname = function(hostname) {
|
||||||
|
return reIPAddressNaive.test(hostname)
|
||||||
|
? hostname
|
||||||
|
: psl.getDomain(hostname);
|
||||||
|
};
|
||||||
|
|
||||||
|
vAPI.domainFromURI = function(uri) {
|
||||||
|
return uri !== ''
|
||||||
|
? vAPI.domainFromHostname(vAPI.hostnameFromURI(uri))
|
||||||
|
: '';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.download = function(details) {
|
vAPI.download = function(details) {
|
||||||
if ( !details.url ) {
|
if ( !details.url ) { return; }
|
||||||
return;
|
const a = document.createElement('a');
|
||||||
}
|
|
||||||
|
|
||||||
var a = document.createElement('a');
|
|
||||||
a.href = details.url;
|
a.href = details.url;
|
||||||
a.setAttribute('download', details.filename || '');
|
a.setAttribute('download', details.filename || '');
|
||||||
|
a.setAttribute('type', 'text/plain');
|
||||||
a.dispatchEvent(new MouseEvent('click'));
|
a.dispatchEvent(new MouseEvent('click'));
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.getURL = chrome.runtime.getURL;
|
vAPI.getURL = browser.runtime.getURL;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.i18n = chrome.i18n.getMessage;
|
vAPI.i18n = browser.i18n.getMessage;
|
||||||
|
|
||||||
setScriptDirection(vAPI.i18n('@@ui_locale'));
|
// http://www.w3.org/International/questions/qa-scripts#directions
|
||||||
|
document.body.setAttribute(
|
||||||
|
'dir',
|
||||||
|
['ar', 'he', 'fa', 'ps', 'ur'].indexOf(vAPI.i18n('@@ui_locale')) !== -1
|
||||||
|
? 'rtl'
|
||||||
|
: 'ltr'
|
||||||
|
);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/3057
|
||||||
|
// - webNavigation.onCreatedNavigationTarget become broken on Firefox when we
|
||||||
|
// try to make the popup panel close itself using the original
|
||||||
|
// `window.open('', '_self').close()`.
|
||||||
|
|
||||||
vAPI.closePopup = function() {
|
vAPI.closePopup = function() {
|
||||||
window.close();
|
if ( vAPI.webextFlavor.soup.has('firefox') ) {
|
||||||
|
window.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: try to figure why this was used instead of a plain window.close().
|
||||||
|
// https://github.com/gorhill/uBlock/commit/b301ac031e0c2e9a99cb6f8953319d44e22f33d2#diff-bc664f26b9c453e0d43a9379e8135c6a
|
||||||
|
window.open('', '_self').close();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -207,8 +258,22 @@ vAPI.localStorage = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
})(this);
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
DO NOT:
|
||||||
|
- Remove the following code
|
||||||
|
- Add code beyond the following code
|
||||||
|
Reason:
|
||||||
|
- https://github.com/gorhill/uBlock/pull/3721
|
||||||
|
- uBO never uses the return value from injected content scripts
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
void 0;
|
||||||
|
|
|
@ -25,40 +25,17 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/407
|
||||||
|
if ( vAPI.webextFlavor.soup.has('chromium') === false ) { return; }
|
||||||
|
|
||||||
const extToTypeMap = new Map([
|
const extToTypeMap = new Map([
|
||||||
['eot','font'],['otf','font'],['svg','font'],['ttf','font'],['woff','font'],['woff2','font'],
|
['eot','font'],['otf','font'],['svg','font'],['ttf','font'],['woff','font'],['woff2','font'],
|
||||||
['mp3','media'],['mp4','media'],['webm','media'],
|
['mp3','media'],['mp4','media'],['webm','media'],
|
||||||
['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['webp','image']
|
['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['webp','image']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// https://www.reddit.com/r/uBlockOrigin/comments/9vcrk3/bug_in_ubo_1173_betas_when_saving_files_hosted_on/
|
const headerValue = (headers, name) => {
|
||||||
// Some types can be mapped from 'other', thus include 'other' if and
|
|
||||||
// only if the caller is interested in at least one of those types.
|
|
||||||
const denormalizeTypes = function(aa) {
|
|
||||||
if ( aa.length === 0 ) {
|
|
||||||
return Array.from(vAPI.net.validTypes);
|
|
||||||
}
|
|
||||||
const out = new Set();
|
|
||||||
let i = aa.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
const type = aa[i];
|
|
||||||
if ( vAPI.net.validTypes.has(type) ) {
|
|
||||||
out.add(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( out.has('other') === false ) {
|
|
||||||
for ( const type of extToTypeMap.values() ) {
|
|
||||||
if ( out.has(type) ) {
|
|
||||||
out.add('other');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(out);
|
|
||||||
};
|
|
||||||
|
|
||||||
const headerValue = function(headers, name) {
|
|
||||||
let i = headers.length;
|
let i = headers.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
if ( headers[i].name.toLowerCase() === name ) {
|
if ( headers[i].name.toLowerCase() === name ) {
|
||||||
|
@ -70,128 +47,131 @@
|
||||||
|
|
||||||
const parsedURL = new URL('https://www.example.org/');
|
const parsedURL = new URL('https://www.example.org/');
|
||||||
|
|
||||||
vAPI.net.normalizeDetails = function(details) {
|
// Extend base class to normalize as per platform.
|
||||||
let type = details.type;
|
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uMatrix-issues/issues/156#issuecomment-494427094
|
vAPI.Net = class extends vAPI.Net {
|
||||||
if ( type === 'main_frame' ) {
|
constructor() {
|
||||||
details.documentUrl = details.url;
|
super();
|
||||||
|
this.suspendedTabIds = new Set();
|
||||||
}
|
}
|
||||||
// Chromium 63+ supports the `initiator` property, which contains
|
normalizeDetails(details) {
|
||||||
// the URL of the origin from which the network request was made.
|
// Chromium 63+ supports the `initiator` property, which contains
|
||||||
else if (
|
// the URL of the origin from which the network request was made.
|
||||||
typeof details.initiator === 'string' &&
|
if (
|
||||||
details.initiator !== 'null'
|
typeof details.initiator === 'string' &&
|
||||||
) {
|
details.initiator !== 'null'
|
||||||
details.documentUrl = details.initiator;
|
) {
|
||||||
}
|
details.documentUrl = details.initiator;
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/1493
|
|
||||||
// Chromium 49+/WebExtensions support a new request type: `ping`,
|
|
||||||
// which is fired as a result of using `navigator.sendBeacon`.
|
|
||||||
if ( type === 'ping' ) {
|
|
||||||
details.type = 'beacon';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( type === 'imageset' ) {
|
|
||||||
details.type = 'image';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The rest of the function code is to normalize type
|
|
||||||
if ( type !== 'other' ) { return; }
|
|
||||||
|
|
||||||
// Try to map known "extension" part of URL to request type.
|
|
||||||
parsedURL.href = details.url;
|
|
||||||
const path = parsedURL.pathname,
|
|
||||||
pos = path.indexOf('.', path.length - 6);
|
|
||||||
if ( pos !== -1 && (type = extToTypeMap.get(path.slice(pos + 1))) ) {
|
|
||||||
details.type = type;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to extract type from response headers if present.
|
|
||||||
if ( details.responseHeaders ) {
|
|
||||||
type = headerValue(details.responseHeaders, 'content-type');
|
|
||||||
if ( type.startsWith('font/') ) {
|
|
||||||
details.type = 'font';
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if ( type.startsWith('image/') ) {
|
|
||||||
|
let type = details.type;
|
||||||
|
|
||||||
|
if ( type === 'imageset' ) {
|
||||||
details.type = 'image';
|
details.type = 'image';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( type.startsWith('audio/') || type.startsWith('video/') ) {
|
|
||||||
details.type = 'media';
|
// The rest of the function code is to normalize type
|
||||||
|
if ( type !== 'other' ) { return; }
|
||||||
|
|
||||||
|
// Try to map known "extension" part of URL to request type.
|
||||||
|
parsedURL.href = details.url;
|
||||||
|
const path = parsedURL.pathname,
|
||||||
|
pos = path.indexOf('.', path.length - 6);
|
||||||
|
if ( pos !== -1 && (type = extToTypeMap.get(path.slice(pos + 1))) ) {
|
||||||
|
details.type = type;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
vAPI.net.denormalizeFilters = function(filters) {
|
// Try to extract type from response headers if present.
|
||||||
const urls = filters.urls || [ '<all_urls>' ];
|
if ( details.responseHeaders ) {
|
||||||
let types = filters.types;
|
type = headerValue(details.responseHeaders, 'content-type');
|
||||||
if ( Array.isArray(types) ) {
|
if ( type.startsWith('font/') ) {
|
||||||
types = denormalizeTypes(types);
|
details.type = 'font';
|
||||||
}
|
return;
|
||||||
if (
|
}
|
||||||
(vAPI.net.validTypes.has('websocket')) &&
|
if ( type.startsWith('image/') ) {
|
||||||
(types === undefined || types.indexOf('websocket') !== -1) &&
|
details.type = 'image';
|
||||||
(urls.indexOf('<all_urls>') === -1)
|
return;
|
||||||
) {
|
}
|
||||||
if ( urls.indexOf('ws://*/*') === -1 ) {
|
if ( type.startsWith('audio/') || type.startsWith('video/') ) {
|
||||||
urls.push('ws://*/*');
|
details.type = 'media';
|
||||||
}
|
return;
|
||||||
if ( urls.indexOf('wss://*/*') === -1 ) {
|
}
|
||||||
urls.push('wss://*/*');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { types, urls };
|
// https://www.reddit.com/r/uBlockOrigin/comments/9vcrk3/
|
||||||
};
|
// Some types can be mapped from 'other', thus include 'other' if and
|
||||||
})();
|
// only if the caller is interested in at least one of those types.
|
||||||
|
denormalizeTypes(types) {
|
||||||
/******************************************************************************/
|
if ( types.length === 0 ) {
|
||||||
|
return Array.from(this.validTypes);
|
||||||
// https://github.com/gorhill/uBlock/issues/2067
|
}
|
||||||
// Experimental: Block everything until uBO is fully ready.
|
const out = new Set();
|
||||||
|
for ( const type of types ) {
|
||||||
vAPI.net.onBeforeReady = (function() {
|
if ( this.validTypes.has(type) ) {
|
||||||
let pendings;
|
out.add(type);
|
||||||
|
}
|
||||||
const handler = function(details) {
|
}
|
||||||
if ( pendings === undefined ) { return; }
|
if ( out.has('other') === false ) {
|
||||||
if ( details.tabId < 0 ) { return; }
|
for ( const type of extToTypeMap.values() ) {
|
||||||
|
if ( out.has(type) ) {
|
||||||
pendings.add(details.tabId);
|
out.add('other');
|
||||||
|
break;
|
||||||
//console.log(`Aborting tab ${details.tabId}: ${details.type} ${details.url}`);
|
}
|
||||||
|
}
|
||||||
return { cancel: true };
|
}
|
||||||
};
|
return Array.from(out);
|
||||||
|
}
|
||||||
return {
|
suspendOneRequest(details) {
|
||||||
experimental: true,
|
this.suspendedTabIds.add(details.tabId);
|
||||||
start: function() {
|
return { cancel: true };
|
||||||
pendings = new Set();
|
}
|
||||||
browser.webRequest.onBeforeRequest.addListener(
|
unsuspendAllRequests() {
|
||||||
handler,
|
for ( const tabId of this.suspendedTabIds ) {
|
||||||
{ urls: [ 'http://*/*', 'https://*/*' ] },
|
|
||||||
[ 'blocking' ]
|
|
||||||
);
|
|
||||||
},
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2067
|
|
||||||
// Force-reload tabs for which network requests were blocked
|
|
||||||
// during launch. This can happen only if tabs were "suspended".
|
|
||||||
stop: function() {
|
|
||||||
if ( pendings === undefined ) { return; }
|
|
||||||
browser.webRequest.onBeforeRequest.removeListener(handler);
|
|
||||||
for ( const tabId of pendings ) {
|
|
||||||
//console.log(`Reloading tab ${tabId}`);
|
|
||||||
vAPI.tabs.reload(tabId);
|
vAPI.tabs.reload(tabId);
|
||||||
}
|
}
|
||||||
pendings = undefined;
|
this.suspendedTabIds.clear();
|
||||||
},
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/548
|
||||||
|
// Use `X-DNS-Prefetch-Control` to workaround Chromium's disregard of the
|
||||||
|
// setting "Predict network actions to improve page load performance".
|
||||||
|
|
||||||
|
vAPI.prefetching = (( ) => {
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/407
|
||||||
|
if ( vAPI.webextFlavor.soup.has('chromium') === false ) { return; }
|
||||||
|
|
||||||
|
let listening = false;
|
||||||
|
|
||||||
|
const onHeadersReceived = function(details) {
|
||||||
|
details.responseHeaders.push({
|
||||||
|
name: 'X-DNS-Prefetch-Control',
|
||||||
|
value: 'off'
|
||||||
|
});
|
||||||
|
return { responseHeaders: details.responseHeaders };
|
||||||
|
};
|
||||||
|
|
||||||
|
return state => {
|
||||||
|
const wr = chrome.webRequest;
|
||||||
|
if ( state && listening ) {
|
||||||
|
wr.onHeadersReceived.removeListener(onHeadersReceived);
|
||||||
|
listening = false;
|
||||||
|
} else if ( !state && !listening ) {
|
||||||
|
wr.onHeadersReceived.addListener(
|
||||||
|
onHeadersReceived,
|
||||||
|
{
|
||||||
|
urls: [ 'http://*/*', 'https://*/*' ],
|
||||||
|
types: [ 'main_frame', 'sub_frame' ]
|
||||||
|
},
|
||||||
|
[ 'blocking', 'responseHeaders' ]
|
||||||
|
);
|
||||||
|
listening = true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
86
platform/chromium/vapi.js
Normal file
86
platform/chromium/vapi.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uMatrix - a browser extension to block requests.
|
||||||
|
Copyright (C) 2017-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* global HTMLDocument, XMLDocument */
|
||||||
|
|
||||||
|
// For background page, auxiliary pages, and content scripts.
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
if ( self.browser instanceof Object ) {
|
||||||
|
self.chrome = self.browser;
|
||||||
|
} else {
|
||||||
|
self.browser = self.chrome;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1408996#c9
|
||||||
|
var vAPI = self.vAPI; // jshint ignore:line
|
||||||
|
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/464
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/1528
|
||||||
|
// A XMLDocument can be a valid HTML document.
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/1124
|
||||||
|
// Looks like `contentType` is on track to be standardized:
|
||||||
|
// https://dom.spec.whatwg.org/#concept-document-content-type
|
||||||
|
|
||||||
|
// https://forums.lanik.us/viewtopic.php?f=64&t=31522
|
||||||
|
// Skip text/plain documents.
|
||||||
|
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
document instanceof HTMLDocument ||
|
||||||
|
document instanceof XMLDocument &&
|
||||||
|
document.createElement('div') instanceof HTMLDivElement
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
/^image\/|^text\/plain/.test(document.contentType || '') === false
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
self.vAPI instanceof Object === false || vAPI.uMatrix !== true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
vAPI = self.vAPI = { uMatrix: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
DO NOT:
|
||||||
|
- Remove the following code
|
||||||
|
- Add code beyond the following code
|
||||||
|
Reason:
|
||||||
|
- https://github.com/gorhill/uBlock/pull/3721
|
||||||
|
- uMatrix never uses the return value from injected content scripts
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
void 0;
|
176
platform/chromium/webext.js
Normal file
176
platform/chromium/webext.js
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2019-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// `webext` is a promisified api of `chrome`. Entries are added as
|
||||||
|
// the promisification of uBO progress.
|
||||||
|
|
||||||
|
const webext = (( ) => { // jshint ignore:line
|
||||||
|
// >>>>> start of private scope
|
||||||
|
|
||||||
|
const noopFunc = ( ) => { };
|
||||||
|
|
||||||
|
const promisifyNoFail = function(thisArg, fnName, outFn = r => r) {
|
||||||
|
const fn = thisArg[fnName];
|
||||||
|
return function() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
fn.call(thisArg, ...arguments, function() {
|
||||||
|
if ( chrome.runtime.lastError instanceof Object ) {
|
||||||
|
void chrome.runtime.lastError.message;
|
||||||
|
}
|
||||||
|
resolve(outFn(...arguments));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const promisify = function(thisArg, fnName) {
|
||||||
|
const fn = thisArg[fnName];
|
||||||
|
return function() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fn.call(thisArg, ...arguments, function() {
|
||||||
|
const lastError = chrome.runtime.lastError;
|
||||||
|
if ( lastError instanceof Object ) {
|
||||||
|
return reject(lastError.message);
|
||||||
|
}
|
||||||
|
resolve(...arguments);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const webext = {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserAction
|
||||||
|
browserAction: {
|
||||||
|
setBadgeBackgroundColor: promisifyNoFail(chrome.browserAction, 'setBadgeBackgroundColor'),
|
||||||
|
setBadgeText: promisifyNoFail(chrome.browserAction, 'setBadgeText'),
|
||||||
|
setBadgeTextColor: noopFunc,
|
||||||
|
setIcon: promisifyNoFail(chrome.browserAction, 'setIcon'),
|
||||||
|
setTitle: promisifyNoFail(chrome.browserAction, 'setTitle'),
|
||||||
|
},
|
||||||
|
cookies: {
|
||||||
|
getAll: promisifyNoFail(chrome.cookies, 'getAll'),
|
||||||
|
remove: promisifyNoFail(chrome.cookies, 'remove'),
|
||||||
|
},
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus
|
||||||
|
/*
|
||||||
|
menus: {
|
||||||
|
create: function() {
|
||||||
|
return chrome.contextMenus.create(...arguments, ( ) => {
|
||||||
|
void chrome.runtime.lastError;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onClicked: chrome.contextMenus.onClicked,
|
||||||
|
remove: promisifyNoFail(chrome.contextMenus, 'remove'),
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy
|
||||||
|
privacy: {
|
||||||
|
},
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage
|
||||||
|
storage: {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/local
|
||||||
|
local: {
|
||||||
|
clear: promisify(chrome.storage.local, 'clear'),
|
||||||
|
get: promisify(chrome.storage.local, 'get'),
|
||||||
|
getBytesInUse: promisify(chrome.storage.local, 'getBytesInUse'),
|
||||||
|
remove: promisify(chrome.storage.local, 'remove'),
|
||||||
|
set: promisify(chrome.storage.local, 'set'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs
|
||||||
|
tabs: {
|
||||||
|
get: promisifyNoFail(chrome.tabs, 'get', tab => tab instanceof Object ? tab : null),
|
||||||
|
executeScript: promisifyNoFail(chrome.tabs, 'executeScript'),
|
||||||
|
insertCSS: promisifyNoFail(chrome.tabs, 'insertCSS'),
|
||||||
|
query: promisifyNoFail(chrome.tabs, 'query', tabs => Array.isArray(tabs) ? tabs : []),
|
||||||
|
reload: promisifyNoFail(chrome.tabs, 'reload'),
|
||||||
|
remove: promisifyNoFail(chrome.tabs, 'remove'),
|
||||||
|
update: promisifyNoFail(chrome.tabs, 'update', tab => tab instanceof Object ? tab : null),
|
||||||
|
},
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation
|
||||||
|
webNavigation: {
|
||||||
|
getFrame: promisify(chrome.webNavigation, 'getFrame'),
|
||||||
|
},
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/windows
|
||||||
|
windows: {
|
||||||
|
get: promisifyNoFail(chrome.windows, 'get', win => win instanceof Object ? win : null),
|
||||||
|
create: promisifyNoFail(chrome.windows, 'create', win => win instanceof Object ? win : null),
|
||||||
|
update: promisifyNoFail(chrome.windows, 'update', win => win instanceof Object ? win : null),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// browser.privacy entries
|
||||||
|
{
|
||||||
|
const settings = [
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy/network
|
||||||
|
[ 'network', 'networkPredictionEnabled' ],
|
||||||
|
[ 'network', 'webRTCIPHandlingPolicy' ],
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy/websites
|
||||||
|
[ 'websites', 'hyperlinkAuditingEnabled' ],
|
||||||
|
];
|
||||||
|
for ( const [ category, setting ] of settings ) {
|
||||||
|
let categoryEntry = webext.privacy[category];
|
||||||
|
if ( categoryEntry instanceof Object === false ) {
|
||||||
|
categoryEntry = webext.privacy[category] = {};
|
||||||
|
}
|
||||||
|
const settingEntry = categoryEntry[setting] = {};
|
||||||
|
const thisArg = chrome.privacy[category][setting];
|
||||||
|
settingEntry.clear = promisifyNoFail(thisArg, 'clear');
|
||||||
|
settingEntry.get = promisifyNoFail(thisArg, 'get');
|
||||||
|
settingEntry.set = promisifyNoFail(thisArg, 'set');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/managed
|
||||||
|
if ( chrome.storage.managed instanceof Object ) {
|
||||||
|
webext.storage.managed = {
|
||||||
|
get: promisify(chrome.storage.managed, 'get'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/sync
|
||||||
|
if ( chrome.storage.sync instanceof Object ) {
|
||||||
|
webext.storage.sync = {
|
||||||
|
QUOTA_BYTES: chrome.storage.sync.QUOTA_BYTES,
|
||||||
|
QUOTA_BYTES_PER_ITEM: chrome.storage.sync.QUOTA_BYTES_PER_ITEM,
|
||||||
|
MAX_ITEMS: chrome.storage.sync.MAX_ITEMS,
|
||||||
|
MAX_WRITE_OPERATIONS_PER_HOUR: chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_HOUR,
|
||||||
|
MAX_WRITE_OPERATIONS_PER_MINUTE: chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_MINUTE,
|
||||||
|
|
||||||
|
clear: promisify(chrome.storage.sync, 'clear'),
|
||||||
|
get: promisify(chrome.storage.sync, 'get'),
|
||||||
|
getBytesInUse: promisify(chrome.storage.sync, 'getBytesInUse'),
|
||||||
|
remove: promisify(chrome.storage.sync, 'remove'),
|
||||||
|
set: promisify(chrome.storage.sync, 'set'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=608854
|
||||||
|
if ( chrome.tabs.removeCSS instanceof Function ) {
|
||||||
|
webext.tabs.removeCSS = promisifyNoFail(chrome.tabs, 'removeCSS');
|
||||||
|
}
|
||||||
|
|
||||||
|
return webext;
|
||||||
|
|
||||||
|
// <<<<< end of private scope
|
||||||
|
})();
|
|
@ -46,6 +46,7 @@
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"browsingData",
|
"browsingData",
|
||||||
"cookies",
|
"cookies",
|
||||||
|
"dns",
|
||||||
"privacy",
|
"privacy",
|
||||||
"storage",
|
"storage",
|
||||||
"tabs",
|
"tabs",
|
||||||
|
|
|
@ -1,263 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
|
|
||||||
uMatrix - a browser extension to block requests.
|
|
||||||
Copyright (C) 2016-2017 The uBlock Origin authors
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
||||||
|
|
||||||
Home: https://github.com/gorhill/uBlock
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* global indexedDB, IDBDatabase */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// The code below has been originally manually imported from:
|
|
||||||
// Commit: https://github.com/nikrolls/uBlock-Edge/commit/d1538ea9bea89d507219d3219592382eee306134
|
|
||||||
// Commit date: 29 October 2016
|
|
||||||
// Commit author: https://github.com/nikrolls
|
|
||||||
// Commit message: "Implement cacheStorage using IndexedDB"
|
|
||||||
|
|
||||||
// The original imported code has been subsequently modified as it was not
|
|
||||||
// compatible with Firefox.
|
|
||||||
// (a Promise thing, see https://github.com/dfahlander/Dexie.js/issues/317)
|
|
||||||
// Furthermore, code to migrate from browser.storage.local to vAPI.cacheStorage
|
|
||||||
// has been added, for seamless migration of cache-related entries into
|
|
||||||
// indexedDB.
|
|
||||||
|
|
||||||
// Imported from uBlock Origin project.
|
|
||||||
|
|
||||||
vAPI.cacheStorage = (function() {
|
|
||||||
const STORAGE_NAME = 'uMatrixCacheStorage';
|
|
||||||
var db;
|
|
||||||
var pending = [];
|
|
||||||
|
|
||||||
// prime the db so that it's ready asap for next access.
|
|
||||||
getDb(noopfn);
|
|
||||||
|
|
||||||
return { get, set, remove, clear, getBytesInUse };
|
|
||||||
|
|
||||||
function get(input, callback) {
|
|
||||||
if ( typeof callback !== 'function' ) { return; }
|
|
||||||
if ( input === null ) {
|
|
||||||
return getAllFromDb(callback);
|
|
||||||
}
|
|
||||||
var toRead, output = {};
|
|
||||||
if ( typeof input === 'string' ) {
|
|
||||||
toRead = [ input ];
|
|
||||||
} else if ( Array.isArray(input) ) {
|
|
||||||
toRead = input;
|
|
||||||
} else /* if ( typeof input === 'object' ) */ {
|
|
||||||
toRead = Object.keys(input);
|
|
||||||
output = input;
|
|
||||||
}
|
|
||||||
return getFromDb(toRead, output, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
function set(input, callback) {
|
|
||||||
putToDb(input, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(key, callback) {
|
|
||||||
deleteFromDb(key, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear(callback) {
|
|
||||||
clearDb(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBytesInUse(keys, callback) {
|
|
||||||
// TODO: implement this
|
|
||||||
callback(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function genericErrorHandler(error) {
|
|
||||||
console.error('[%s]', STORAGE_NAME, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
function noopfn() {
|
|
||||||
}
|
|
||||||
|
|
||||||
function processPendings() {
|
|
||||||
var cb;
|
|
||||||
while ( (cb = pending.shift()) ) {
|
|
||||||
cb(db);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDb(callback) {
|
|
||||||
if ( pending === undefined ) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
if ( pending.length !== 0 ) {
|
|
||||||
return pending.push(callback);
|
|
||||||
}
|
|
||||||
if ( db instanceof IDBDatabase ) {
|
|
||||||
return callback(db);
|
|
||||||
}
|
|
||||||
pending.push(callback);
|
|
||||||
if ( pending.length !== 1 ) { return; }
|
|
||||||
// https://github.com/gorhill/uBlock/issues/3156
|
|
||||||
// I have observed that no event was fired in Tor Browser 7.0.7 +
|
|
||||||
// medium security level after the request to open the database was
|
|
||||||
// created. When this occurs, I have also observed that the `error`
|
|
||||||
// property was already set, so this means uBO can detect here whether
|
|
||||||
// the database can be opened successfully. A try-catch block is
|
|
||||||
// necessary when reading the `error` property because we are not
|
|
||||||
// allowed to read this propery outside of event handlers in newer
|
|
||||||
// implementation of IDBRequest (my understanding).
|
|
||||||
var req;
|
|
||||||
try {
|
|
||||||
req = indexedDB.open(STORAGE_NAME, 1);
|
|
||||||
if ( req.error ) {
|
|
||||||
console.log(req.error);
|
|
||||||
req = undefined;
|
|
||||||
}
|
|
||||||
} catch(ex) {
|
|
||||||
}
|
|
||||||
if ( req === undefined ) {
|
|
||||||
processPendings();
|
|
||||||
pending = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
req.onupgradeneeded = function(ev) {
|
|
||||||
req = undefined;
|
|
||||||
db = ev.target.result;
|
|
||||||
db.onerror = db.onabort = genericErrorHandler;
|
|
||||||
var table = db.createObjectStore(STORAGE_NAME, { keyPath: 'key' });
|
|
||||||
table.createIndex('value', 'value', { unique: false });
|
|
||||||
};
|
|
||||||
req.onsuccess = function(ev) {
|
|
||||||
req = undefined;
|
|
||||||
db = ev.target.result;
|
|
||||||
db.onerror = db.onabort = genericErrorHandler;
|
|
||||||
processPendings();
|
|
||||||
};
|
|
||||||
req.onerror = req.onblocked = function() {
|
|
||||||
req = undefined;
|
|
||||||
console.log(this.error);
|
|
||||||
processPendings();
|
|
||||||
pending = undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFromDb(keys, store, callback) {
|
|
||||||
if ( typeof callback !== 'function' ) { return; }
|
|
||||||
if ( keys.length === 0 ) { return callback(store); }
|
|
||||||
var gotOne = function() {
|
|
||||||
if ( typeof this.result === 'object' ) {
|
|
||||||
store[this.result.key] = this.result.value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getDb(function(db) {
|
|
||||||
if ( !db ) { return callback(); }
|
|
||||||
var transaction = db.transaction(STORAGE_NAME);
|
|
||||||
transaction.oncomplete =
|
|
||||||
transaction.onerror =
|
|
||||||
transaction.onabort = function() {
|
|
||||||
return callback(store);
|
|
||||||
};
|
|
||||||
var table = transaction.objectStore(STORAGE_NAME);
|
|
||||||
for ( var key of keys ) {
|
|
||||||
var req = table.get(key);
|
|
||||||
req.onsuccess = gotOne;
|
|
||||||
req.onerror = noopfn;
|
|
||||||
req = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAllFromDb(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
callback = noopfn;
|
|
||||||
}
|
|
||||||
getDb(function(db) {
|
|
||||||
if ( !db ) { return callback(); }
|
|
||||||
var output = {};
|
|
||||||
var transaction = db.transaction(STORAGE_NAME);
|
|
||||||
transaction.oncomplete =
|
|
||||||
transaction.onerror =
|
|
||||||
transaction.onabort = function() {
|
|
||||||
callback(output);
|
|
||||||
};
|
|
||||||
var table = transaction.objectStore(STORAGE_NAME),
|
|
||||||
req = table.openCursor();
|
|
||||||
req.onsuccess = function(ev) {
|
|
||||||
var cursor = ev.target.result;
|
|
||||||
if ( !cursor ) { return; }
|
|
||||||
output[cursor.key] = cursor.value;
|
|
||||||
cursor.continue();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function putToDb(input, callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
callback = noopfn;
|
|
||||||
}
|
|
||||||
var keys = Object.keys(input);
|
|
||||||
if ( keys.length === 0 ) { return callback(); }
|
|
||||||
getDb(function(db) {
|
|
||||||
if ( !db ) { return callback(); }
|
|
||||||
var transaction = db.transaction(STORAGE_NAME, 'readwrite');
|
|
||||||
transaction.oncomplete =
|
|
||||||
transaction.onerror =
|
|
||||||
transaction.onabort = callback;
|
|
||||||
var table = transaction.objectStore(STORAGE_NAME);
|
|
||||||
for ( var key of keys ) {
|
|
||||||
var entry = {};
|
|
||||||
entry.key = key;
|
|
||||||
entry.value = input[key];
|
|
||||||
table.put(entry);
|
|
||||||
entry = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteFromDb(input, callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
callback = noopfn;
|
|
||||||
}
|
|
||||||
var keys = Array.isArray(input) ? input.slice() : [ input ];
|
|
||||||
if ( keys.length === 0 ) { return callback(); }
|
|
||||||
getDb(function(db) {
|
|
||||||
if ( !db ) { return callback(); }
|
|
||||||
var transaction = db.transaction(STORAGE_NAME, 'readwrite');
|
|
||||||
transaction.oncomplete =
|
|
||||||
transaction.onerror =
|
|
||||||
transaction.onabort = callback;
|
|
||||||
var table = transaction.objectStore(STORAGE_NAME);
|
|
||||||
for ( var key of keys ) {
|
|
||||||
table.delete(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearDb(callback) {
|
|
||||||
if ( typeof callback !== 'function' ) {
|
|
||||||
callback = noopfn;
|
|
||||||
}
|
|
||||||
getDb(function(db) {
|
|
||||||
if ( !db ) { return callback(); }
|
|
||||||
var req = db.transaction(STORAGE_NAME, 'readwrite')
|
|
||||||
.objectStore(STORAGE_NAME)
|
|
||||||
.clear();
|
|
||||||
req.onsuccess = req.onerror = callback;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
|
@ -25,12 +25,14 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/407
|
||||||
|
if ( vAPI.webextFlavor.soup.has('firefox') === false ) { return; }
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/2950
|
// https://github.com/gorhill/uBlock/issues/2950
|
||||||
// Firefox 56 does not normalize URLs to ASCII, uBO must do this itself.
|
// Firefox 56 does not normalize URLs to ASCII, uBO must do this itself.
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=945240
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=945240
|
||||||
const evalMustPunycode = function() {
|
const evalMustPunycode = ( ) => {
|
||||||
return vAPI.webextFlavor.soup.has('firefox') &&
|
return vAPI.webextFlavor.soup.has('firefox') &&
|
||||||
vAPI.webextFlavor.major < 57;
|
vAPI.webextFlavor.major < 57;
|
||||||
};
|
};
|
||||||
|
@ -43,142 +45,218 @@
|
||||||
mustPunycode = evalMustPunycode();
|
mustPunycode = evalMustPunycode();
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
const denormalizeTypes = function(aa) {
|
|
||||||
if ( aa.length === 0 ) {
|
|
||||||
return Array.from(vAPI.net.validTypes);
|
|
||||||
}
|
|
||||||
const out = new Set();
|
|
||||||
let i = aa.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
let type = aa[i];
|
|
||||||
if ( vAPI.net.validTypes.has(type) ) {
|
|
||||||
out.add(type);
|
|
||||||
}
|
|
||||||
if ( type === 'image' && vAPI.net.validTypes.has('imageset') ) {
|
|
||||||
out.add('imageset');
|
|
||||||
}
|
|
||||||
if ( type === 'sub_frame' ) {
|
|
||||||
out.add('object');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Array.from(out);
|
|
||||||
};
|
|
||||||
|
|
||||||
const punycode = self.punycode;
|
const punycode = self.punycode;
|
||||||
const reAsciiHostname = /^https?:\/\/[0-9a-z_.:@-]+[/?#]/;
|
const reAsciiHostname = /^https?:\/\/[0-9a-z_.:@-]+[/?#]/;
|
||||||
const parsedURL = new URL('about:blank');
|
const parsedURL = new URL('about:blank');
|
||||||
|
|
||||||
vAPI.net.normalizeDetails = function(details) {
|
// Related issues:
|
||||||
if ( mustPunycode && !reAsciiHostname.test(details.url) ) {
|
// - https://github.com/gorhill/uBlock/issues/1327
|
||||||
parsedURL.href = details.url;
|
// - https://github.com/uBlockOrigin/uBlock-issues/issues/128
|
||||||
details.url = details.url.replace(
|
// - https://bugzilla.mozilla.org/show_bug.cgi?id=1503721
|
||||||
parsedURL.hostname,
|
|
||||||
punycode.toASCII(parsedURL.hostname)
|
// Extend base class to normalize as per platform.
|
||||||
);
|
|
||||||
|
vAPI.Net = class extends vAPI.Net {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.pendingRequests = [];
|
||||||
|
this.cnames = new Map([ [ '', '' ] ]);
|
||||||
|
this.cnameIgnoreList = null;
|
||||||
|
this.cnameIgnore1stParty = true;
|
||||||
|
this.cnameIgnoreExceptions = true;
|
||||||
|
this.cnameIgnoreRootDocument = true;
|
||||||
|
this.cnameMaxTTL = 60;
|
||||||
|
this.cnameReplayFullURL = false;
|
||||||
|
this.cnameTimer = undefined;
|
||||||
|
this.canRevealCNAME = browser.dns instanceof Object;
|
||||||
}
|
}
|
||||||
|
setOptions(options) {
|
||||||
const type = details.type;
|
super.setOptions(options);
|
||||||
|
this.cnameIgnoreList = this.regexFromStrList(options.cnameIgnoreList);
|
||||||
// https://github.com/gorhill/uBlock/issues/1493
|
this.cnameIgnore1stParty = options.cnameIgnore1stParty !== false;
|
||||||
// Chromium 49+/WebExtensions support a new request type: `ping`,
|
this.cnameIgnoreExceptions = options.cnameIgnoreExceptions !== false;
|
||||||
// which is fired as a result of using `navigator.sendBeacon`.
|
this.cnameIgnoreRootDocument = options.cnameIgnoreRootDocument !== false;
|
||||||
if ( type === 'ping' ) {
|
this.cnameMaxTTL = options.cnameMaxTTL || 120;
|
||||||
details.type = 'beacon';
|
this.cnameReplayFullURL = options.cnameReplayFullURL === true;
|
||||||
return;
|
this.cnames.clear(); this.cnames.set('', '');
|
||||||
}
|
}
|
||||||
|
normalizeDetails(details) {
|
||||||
|
if ( mustPunycode && !reAsciiHostname.test(details.url) ) {
|
||||||
|
parsedURL.href = details.url;
|
||||||
|
details.url = details.url.replace(
|
||||||
|
parsedURL.hostname,
|
||||||
|
punycode.toASCII(parsedURL.hostname)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ( type === 'imageset' ) {
|
const type = details.type;
|
||||||
details.type = 'image';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/345
|
if ( type === 'imageset' ) {
|
||||||
// Re-categorize an embedded object as a `sub_frame` if its
|
details.type = 'image';
|
||||||
// content type is that of a HTML document.
|
return;
|
||||||
if ( type === 'object' && Array.isArray(details.responseHeaders) ) {
|
}
|
||||||
for ( const header of details.responseHeaders ) {
|
|
||||||
if ( header.name.toLowerCase() === 'content-type' ) {
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/345
|
||||||
if ( header.value.startsWith('text/html') ) {
|
// Re-categorize an embedded object as a `sub_frame` if its
|
||||||
details.type = 'sub_frame';
|
// content type is that of a HTML document.
|
||||||
|
if ( type === 'object' && Array.isArray(details.responseHeaders) ) {
|
||||||
|
for ( const header of details.responseHeaders ) {
|
||||||
|
if ( header.name.toLowerCase() === 'content-type' ) {
|
||||||
|
if ( header.value.startsWith('text/html') ) {
|
||||||
|
details.type = 'sub_frame';
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
denormalizeTypes(types) {
|
||||||
|
if ( types.length === 0 ) {
|
||||||
vAPI.net.denormalizeFilters = function(filters) {
|
return Array.from(this.validTypes);
|
||||||
const urls = filters.urls || [ '<all_urls>' ];
|
|
||||||
let types = filters.types;
|
|
||||||
if ( Array.isArray(types) ) {
|
|
||||||
types = denormalizeTypes(types);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
(vAPI.net.validTypes.has('websocket')) &&
|
|
||||||
(types === undefined || types.indexOf('websocket') !== -1) &&
|
|
||||||
(urls.indexOf('<all_urls>') === -1)
|
|
||||||
) {
|
|
||||||
if ( urls.indexOf('ws://*/*') === -1 ) {
|
|
||||||
urls.push('ws://*/*');
|
|
||||||
}
|
}
|
||||||
if ( urls.indexOf('wss://*/*') === -1 ) {
|
const out = new Set();
|
||||||
urls.push('wss://*/*');
|
for ( const type of types ) {
|
||||||
|
if ( this.validTypes.has(type) ) {
|
||||||
|
out.add(type);
|
||||||
|
}
|
||||||
|
if ( type === 'image' && this.validTypes.has('imageset') ) {
|
||||||
|
out.add('imageset');
|
||||||
|
}
|
||||||
|
if ( type === 'sub_frame' ) {
|
||||||
|
out.add('object');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return Array.from(out);
|
||||||
}
|
}
|
||||||
return { types, urls };
|
processCanonicalName(hn, cn, details) {
|
||||||
};
|
const hnBeg = details.url.indexOf(hn);
|
||||||
})();
|
if ( hnBeg === -1 ) { return; }
|
||||||
|
const oldURL = details.url;
|
||||||
/******************************************************************************/
|
let newURL = oldURL.slice(0, hnBeg) + cn;
|
||||||
|
const hnEnd = hnBeg + hn.length;
|
||||||
// Related issues:
|
if ( this.cnameReplayFullURL ) {
|
||||||
// - https://github.com/gorhill/uBlock/issues/1327
|
newURL += oldURL.slice(hnEnd);
|
||||||
// - https://github.com/uBlockOrigin/uBlock-issues/issues/128
|
} else {
|
||||||
// - https://bugzilla.mozilla.org/show_bug.cgi?id=1503721
|
const pathBeg = oldURL.indexOf('/', hnEnd);
|
||||||
|
if ( pathBeg !== -1 ) {
|
||||||
vAPI.net.onBeforeReady = (function() {
|
newURL += oldURL.slice(hnEnd, pathBeg + 1);
|
||||||
let pendings;
|
}
|
||||||
|
}
|
||||||
const handler = function(details) {
|
details.url = newURL;
|
||||||
if ( pendings === undefined ) { return; }
|
details.aliasURL = oldURL;
|
||||||
if ( details.tabId < 0 ) { return; }
|
return super.onBeforeSuspendableRequest(details);
|
||||||
|
}
|
||||||
//console.log(`Deferring tab ${details.tabId}: ${details.type} ${details.url}`);
|
recordCanonicalName(hn, record) {
|
||||||
|
let cname =
|
||||||
const pending = {
|
typeof record.canonicalName === 'string' &&
|
||||||
details: Object.assign({}, details),
|
record.canonicalName !== hn
|
||||||
resolve: undefined,
|
? record.canonicalName
|
||||||
promise: undefined
|
: '';
|
||||||
};
|
if (
|
||||||
|
cname !== '' &&
|
||||||
pending.promise = new Promise(function(resolve) {
|
this.cnameIgnore1stParty &&
|
||||||
pending.resolve = resolve;
|
vAPI.domainFromHostname(cname) === vAPI.domainFromHostname(hn)
|
||||||
});
|
) {
|
||||||
|
cname = '';
|
||||||
pendings.push(pending);
|
}
|
||||||
|
if (
|
||||||
return pending.promise;
|
cname !== '' &&
|
||||||
};
|
this.cnameIgnoreList !== null &&
|
||||||
|
this.cnameIgnoreList.test(cname)
|
||||||
return {
|
) {
|
||||||
start: function() {
|
cname = '';
|
||||||
pendings = [];
|
}
|
||||||
browser.webRequest.onBeforeRequest.addListener(
|
this.cnames.set(hn, cname);
|
||||||
handler,
|
if ( this.cnameTimer === undefined ) {
|
||||||
{ urls: [ 'http://*/*', 'https://*/*' ] },
|
this.cnameTimer = self.setTimeout(
|
||||||
[ 'blocking' ]
|
( ) => {
|
||||||
|
this.cnameTimer = undefined;
|
||||||
|
this.cnames.clear(); this.cnames.set('', '');
|
||||||
|
},
|
||||||
|
this.cnameMaxTTL * 60000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return cname;
|
||||||
|
}
|
||||||
|
regexFromStrList(list) {
|
||||||
|
if (
|
||||||
|
typeof list !== 'string' ||
|
||||||
|
list.length === 0 ||
|
||||||
|
list === 'unset' ||
|
||||||
|
browser.dns instanceof Object === false
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ( list === '*' ) {
|
||||||
|
return /^./;
|
||||||
|
}
|
||||||
|
return new RegExp(
|
||||||
|
'(?:^|\.)(?:' +
|
||||||
|
list.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.map(a => a.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
||||||
|
.join('|') +
|
||||||
|
')$'
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
stop: function(resolver) {
|
onBeforeSuspendableRequest(details) {
|
||||||
if ( pendings === undefined ) { return; }
|
const r = super.onBeforeSuspendableRequest(details);
|
||||||
for ( const pending of pendings ) {
|
if ( this.canRevealCNAME === false ) { return r; }
|
||||||
const details = pending.details;
|
if ( r !== undefined ) {
|
||||||
vAPI.net.normalizeDetails(details);
|
if ( r.cancel === false ) { return; }
|
||||||
//console.log(`Processing tab ${details.tabId}: ${details.type} ${details.url}`);
|
if (
|
||||||
pending.resolve(resolver(details));
|
r.cancel === true ||
|
||||||
|
r.redirectUrl !== undefined ||
|
||||||
|
this.cnameIgnoreExceptions
|
||||||
|
) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pendings = undefined;
|
if (
|
||||||
},
|
details.type === 'main_frame' &&
|
||||||
|
this.cnameIgnoreRootDocument
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hn = vAPI.hostnameFromNetworkURL(details.url);
|
||||||
|
const cname = this.cnames.get(hn);
|
||||||
|
if ( cname === '' ) { return; }
|
||||||
|
if ( cname !== undefined ) {
|
||||||
|
return this.processCanonicalName(hn, cname, details);
|
||||||
|
}
|
||||||
|
return browser.dns.resolve(hn, [ 'canonical_name' ]).then(
|
||||||
|
rec => {
|
||||||
|
const cname = this.recordCanonicalName(hn, rec);
|
||||||
|
if ( cname === '' ) { return; }
|
||||||
|
return this.processCanonicalName(hn, cname, details);
|
||||||
|
},
|
||||||
|
( ) => {
|
||||||
|
this.cnames.set(hn, '');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
suspendOneRequest(details) {
|
||||||
|
const pending = {
|
||||||
|
details: Object.assign({}, details),
|
||||||
|
resolve: undefined,
|
||||||
|
promise: undefined
|
||||||
|
};
|
||||||
|
pending.promise = new Promise(resolve => {
|
||||||
|
pending.resolve = resolve;
|
||||||
|
});
|
||||||
|
this.pendingRequests.push(pending);
|
||||||
|
return pending.promise;
|
||||||
|
}
|
||||||
|
unsuspendAllRequests() {
|
||||||
|
const pendingRequests = this.pendingRequests;
|
||||||
|
this.pendingRequests = [];
|
||||||
|
for ( const entry of pendingRequests ) {
|
||||||
|
entry.resolve(this.onBeforeSuspendableRequest(entry.details));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
canSuspend() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
24
platform/firefox/webext.js
Normal file
24
platform/firefox/webext.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2019-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const webext = browser; // jshint ignore:line
|
|
@ -73,8 +73,8 @@
|
||||||
"message": "script",
|
"message": "script",
|
||||||
"description": "HAS TO FIT IN MATRIX HEADER!"
|
"description": "HAS TO FIT IN MATRIX HEADER!"
|
||||||
},
|
},
|
||||||
"xhrPrettyName": {
|
"fetchPrettyName": {
|
||||||
"message": "XHR",
|
"message": "fetch",
|
||||||
"description": "HAS TO FIT IN MATRIX HEADER!"
|
"description": "HAS TO FIT IN MATRIX HEADER!"
|
||||||
},
|
},
|
||||||
"framePrettyName": {
|
"framePrettyName": {
|
||||||
|
@ -139,6 +139,10 @@
|
||||||
"message": "Spoof <code><noscript></code> tags",
|
"message": "Spoof <code><noscript></code> tags",
|
||||||
"description": "A menu entry in the matrix popup"
|
"description": "A menu entry in the matrix popup"
|
||||||
},
|
},
|
||||||
|
"matrixSwitchRevealCname" : {
|
||||||
|
"message": "Reveal canonical names",
|
||||||
|
"description": "A menu entry in the matrix popup"
|
||||||
|
},
|
||||||
"matrixRevertAllEntry" : {
|
"matrixRevertAllEntry" : {
|
||||||
"message": "Revert all temporary changes",
|
"message": "Revert all temporary changes",
|
||||||
"description": "A menu entry in the matrix popup"
|
"description": "A menu entry in the matrix popup"
|
||||||
|
@ -284,8 +288,154 @@
|
||||||
"message": "Refresh",
|
"message": "Refresh",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
|
"logAll":{
|
||||||
|
"message":"All",
|
||||||
|
"description":"Appears in the logger's tab selector"
|
||||||
|
},
|
||||||
|
"logBehindTheScene":{
|
||||||
|
"message":"Tabless",
|
||||||
|
"description":"Pretty name for behind-the-scene network requests"
|
||||||
|
},
|
||||||
|
"loggerCurrentTab":{
|
||||||
|
"message":"Current tab",
|
||||||
|
"description":"Appears in the logger's tab selector"
|
||||||
|
},
|
||||||
|
"loggerReloadTip":{
|
||||||
|
"message":"Reload the tab content",
|
||||||
|
"description":"Tooltip for the reload button in the logger page"
|
||||||
|
},
|
||||||
|
"loggerFilterInputPlaceholder" : {
|
||||||
|
"message": "filter expression(s)",
|
||||||
|
"description": "Appears in the input filed where filter expressions are entered"
|
||||||
|
},
|
||||||
|
"loggerEntryCookieDeleted" : {
|
||||||
|
"message": "cookie deleted: {{value}}",
|
||||||
|
"description": "An entry for when a cookie is deleted"
|
||||||
|
},
|
||||||
|
"loggerEntryDeleteCookieError" : {
|
||||||
|
"message": "failed to delete cookie: {{value}}",
|
||||||
|
"description": "An entry for when the browser cache is cleared"
|
||||||
|
},
|
||||||
|
"loggerEntryBrowserCacheCleared" : {
|
||||||
|
"message": "browser cache cleared",
|
||||||
|
"description": "An entry for when a cookie can't be deleted"
|
||||||
|
},
|
||||||
|
"loggerEntryAssetUpdated" : {
|
||||||
|
"message": "asset updated: {{value}}",
|
||||||
|
"description": "An entry for when an asset was updated"
|
||||||
|
},
|
||||||
|
"loggerRowFiltererButtonTip":{
|
||||||
|
"message":"Toggle logger filtering",
|
||||||
|
"description":"Tooltip for the row filterer button in the logger page"
|
||||||
|
},
|
||||||
|
"logFilterPrompt":{
|
||||||
|
"message":"filter logger content",
|
||||||
|
"description": "Placeholder string for logger output filtering input field"
|
||||||
|
},
|
||||||
|
"loggerPopupPanelTip":{
|
||||||
|
"message":"Toggle the popup panel",
|
||||||
|
"description":"Tooltip for the popup panel button in the logger page"
|
||||||
|
},
|
||||||
|
"loggerInfoTip":{
|
||||||
|
"message":"uBlock Origin wiki: The logger",
|
||||||
|
"description":"Tooltip for the top-right info label in the logger page"
|
||||||
|
},
|
||||||
|
"loggerClearTip":{
|
||||||
|
"message":"Clear logger",
|
||||||
|
"description":"Tooltip for the eraser in the logger page; used to blank the content of the logger"
|
||||||
|
},
|
||||||
|
"loggerPauseTip":{
|
||||||
|
"message":"Pause logger (discard all incoming data)",
|
||||||
|
"description":"Tooltip for the pause button in the logger page"
|
||||||
|
},
|
||||||
|
"loggerUnpauseTip":{
|
||||||
|
"message":"Unpause logger",
|
||||||
|
"description":"Tooltip for the play button in the logger page"
|
||||||
|
},
|
||||||
|
"loggerRowFiltererBuiltinTip":{
|
||||||
|
"message":"Logger filtering options",
|
||||||
|
"description":"Tooltip for the button to bring up logger output filtering options"
|
||||||
|
},
|
||||||
|
"loggerRowFiltererBuiltinNot":{
|
||||||
|
"message":"Not",
|
||||||
|
"description":"A keyword in the built-in row filtering expression"
|
||||||
|
},
|
||||||
|
"loggerRowFiltererBuiltinBlocked":{
|
||||||
|
"message":"blocked",
|
||||||
|
"description":"A keyword in the built-in row filtering expression"
|
||||||
|
},
|
||||||
|
"loggerRowFiltererBuiltinInfo":{
|
||||||
|
"message":"info",
|
||||||
|
"description":"A keyword in the built-in row filtering expression"
|
||||||
|
},
|
||||||
|
"loggerRowFiltererBuiltin1p":{
|
||||||
|
"message":"1st-party",
|
||||||
|
"description":"A keyword in the built-in row filtering expression"
|
||||||
|
},
|
||||||
|
"loggerRowFiltererBuiltin3p":{
|
||||||
|
"message":"3rd-party",
|
||||||
|
"description":"A keyword in the built-in row filtering expression"
|
||||||
|
},
|
||||||
|
"loggerEntryDetailsHeader":{
|
||||||
|
"message":"Details",
|
||||||
|
"description":"Small header to identify the 'Details' pane for a specific logger entry"
|
||||||
|
},
|
||||||
|
"loggerEntryDetailsContext":{
|
||||||
|
"message":"Context",
|
||||||
|
"description":"Label to identify a context field (typically a hostname)"
|
||||||
|
},
|
||||||
|
"loggerEntryDetailsPartyness":{
|
||||||
|
"message":"Partyness",
|
||||||
|
"description":"Label to identify a field providing partyness information"
|
||||||
|
},
|
||||||
|
"loggerEntryDetailsType":{
|
||||||
|
"message":"Type",
|
||||||
|
"description":"Label to identify the type of an entry"
|
||||||
|
},
|
||||||
|
"loggerEntryDetailsURL":{
|
||||||
|
"message":"URL",
|
||||||
|
"description":"Label to identify the URL of an entry"
|
||||||
|
},
|
||||||
|
"loggerEntryRuleHeader":{
|
||||||
|
"message":"Rule",
|
||||||
|
"description":"Small header to identify the 'Rule' pane for a specific logger entry"
|
||||||
|
},
|
||||||
|
"loggerSettingDiscardPrompt":{
|
||||||
|
"message":"Logger entries which do not fulfill all three conditions below will be automatically discarded:",
|
||||||
|
"description":"Logger setting: A sentence to describe the purpose of the settings below"
|
||||||
|
},
|
||||||
|
"loggerSettingPerEntryMaxAge":{
|
||||||
|
"message":"Preserve entries from the last {{input}} minutes",
|
||||||
|
"description":"A logger setting"
|
||||||
|
},
|
||||||
|
"loggerSettingPerTabMaxLoads":{
|
||||||
|
"message":"Preserve at most {{input}} page loads per tab",
|
||||||
|
"description":"A logger setting"
|
||||||
|
},
|
||||||
|
"loggerSettingPerTabMaxEntries":{
|
||||||
|
"message":"Preserve at most {{input}} entries per tab",
|
||||||
|
"description":"A logger setting"
|
||||||
|
},
|
||||||
|
"loggerSettingPerEntryLineCount":{
|
||||||
|
"message":"Use {{input}} lines per entry in vertically expanded mode",
|
||||||
|
"description":"A logger setting"
|
||||||
|
},
|
||||||
|
"loggerExportFormatList":{
|
||||||
|
"message":"List",
|
||||||
|
"description":"Label for radio-button to pick export format"
|
||||||
|
},
|
||||||
|
"loggerExportFormatTable":{
|
||||||
|
"message":"Table",
|
||||||
|
"description":"Label for radio-button to pick export format"
|
||||||
|
},
|
||||||
|
"loggerExportEncodePlain":{
|
||||||
|
"message":"Plain",
|
||||||
|
"description":"Label for radio-button to pick export text format"
|
||||||
|
},
|
||||||
|
"loggerExportEncodeMarkdown":{
|
||||||
|
"message":"Markdown",
|
||||||
|
"description":"Label for radio-button to pick export text format"
|
||||||
|
},
|
||||||
"settingsPageTitle" : {
|
"settingsPageTitle" : {
|
||||||
"message": "uMatrix – Settings",
|
"message": "uMatrix – Settings",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
@ -622,31 +772,6 @@
|
||||||
"description": "Message asking user to confirm reset"
|
"description": "Message asking user to confirm reset"
|
||||||
},
|
},
|
||||||
|
|
||||||
"loggerFilterInputPlaceholder" : {
|
|
||||||
"message": "filter expression(s)",
|
|
||||||
"description": "Appears in the input filed where filter expressions are entered"
|
|
||||||
},
|
|
||||||
"loggerMaxEntriesTip" : {
|
|
||||||
"message": "Maximum number of entries",
|
|
||||||
"description": "Appears as a tooltip when hovering the input field"
|
|
||||||
},
|
|
||||||
"loggerEntryCookieDeleted" : {
|
|
||||||
"message": "cookie deleted: {{value}}",
|
|
||||||
"description": "An entry for when a cookie is deleted"
|
|
||||||
},
|
|
||||||
"loggerEntryDeleteCookieError" : {
|
|
||||||
"message": "failed to delete cookie: {{value}}",
|
|
||||||
"description": "An entry for when the browser cache is cleared"
|
|
||||||
},
|
|
||||||
"loggerEntryBrowserCacheCleared" : {
|
|
||||||
"message": "browser cache cleared",
|
|
||||||
"description": "An entry for when a cookie can't be deleted"
|
|
||||||
},
|
|
||||||
"loggerEntryAssetUpdated" : {
|
|
||||||
"message": "asset updated: {{value}}",
|
|
||||||
"description": "An entry for when an asset was updated"
|
|
||||||
},
|
|
||||||
|
|
||||||
"mainBlockedPrompt1": {
|
"mainBlockedPrompt1": {
|
||||||
"message": "uMatrix has prevented the following page from loading:",
|
"message": "uMatrix has prevented the following page from loading:",
|
||||||
"description": "English: uMatrix has prevented the following page from loading:"
|
"description": "English: uMatrix has prevented the following page from loading:"
|
||||||
|
@ -752,5 +877,9 @@
|
||||||
"genericApplyChanges": {
|
"genericApplyChanges": {
|
||||||
"message": "Apply changes",
|
"message": "Apply changes",
|
||||||
"description": "for generic 'Apply changes' buttons"
|
"description": "for generic 'Apply changes' buttons"
|
||||||
|
},
|
||||||
|
"genericCopyToClipboard":{
|
||||||
|
"message":"Copy to clipboard",
|
||||||
|
"description":"Label for buttons used to copy something to the clipboard"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||||
<style>
|
<style>
|
||||||
|
div.body > ul {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
ul {
|
ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +21,8 @@ ul {
|
||||||
|
|
||||||
<h3>uMatrix <span id="aboutVersion"></span></h3>
|
<h3>uMatrix <span id="aboutVersion"></span></h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Copyright (c) Raymond Hill 2013-present<br>
|
||||||
|
<li>
|
||||||
<li><span id="aboutStorageUsed"></span><br>
|
<li><span id="aboutStorageUsed"></span><br>
|
||||||
<li>
|
<li>
|
||||||
<li><span data-i18n="aboutChangelog"></span><br>
|
<li><span data-i18n="aboutChangelog"></span><br>
|
||||||
|
@ -55,6 +61,7 @@ ul {
|
||||||
<span data-i18n="aboutResetConfirm"></span>
|
<span data-i18n="aboutResetConfirm"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
|
|
|
@ -4,18 +4,45 @@
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>uMatrix — Asset viewer</title>
|
<title>uMatrix — Asset viewer</title>
|
||||||
|
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
|
||||||
|
<link rel="stylesheet" href="lib/codemirror/addon/search/matchesonscrollbar.css">
|
||||||
|
<link rel="stylesheet" href="css/fa-icons.css">
|
||||||
|
<link rel="stylesheet" href="css/codemirror.css">
|
||||||
<style>
|
<style>
|
||||||
|
body {
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
#content {
|
#content {
|
||||||
font: 12px monospace;
|
height: 100vh;
|
||||||
white-space: pre;
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://github.com/uBlockOrigin/uBlock-issues/issues/292 */
|
||||||
|
.CodeMirror-wrap pre {
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="content"></div>
|
<div id="content" class="codeMirrorContainer"></div>
|
||||||
|
|
||||||
|
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/display/panel.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/selection/active-line.js"></script>
|
||||||
|
|
||||||
|
<script src="js/codemirror/search.js"></script>
|
||||||
|
|
||||||
|
<script src="js/fa-icons.js"></script>
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
|
<script src="js/dashboard-common.js"></script>
|
||||||
<script src="js/asset-viewer.js"></script>
|
<script src="js/asset-viewer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -5,33 +5,34 @@
|
||||||
<title>uMatrix</title>
|
<title>uMatrix</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<script src="js/console.js"></script>
|
||||||
|
<script src="lib/lz4/lz4-block-codec-any.js"></script>
|
||||||
<script src="lib/punycode.js"></script>
|
<script src="lib/punycode.js"></script>
|
||||||
<script src="lib/publicsuffixlist.js"></script>
|
<script src="lib/publicsuffixlist/publicsuffixlist.js"></script>
|
||||||
|
<script src="js/webext.js"></script>
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-background.js"></script>
|
<script src="js/vapi-background.js"></script>
|
||||||
|
|
||||||
<!-- Forks can pick the chromium, firefox, or their own implementation -->
|
|
||||||
<script src="js/vapi-webrequest.js"></script>
|
<script src="js/vapi-webrequest.js"></script>
|
||||||
|
|
||||||
<!-- Optional -->
|
|
||||||
<script src="js/vapi-cachestorage.js"></script>
|
|
||||||
|
|
||||||
<script src="js/background.js"></script>
|
<script src="js/background.js"></script>
|
||||||
|
<script src="js/traffic.js"></script>
|
||||||
|
<script src="js/hntrie.js"></script>
|
||||||
|
<script src="js/utils.js"></script>
|
||||||
|
<script src="js/uritools.js"></script>
|
||||||
|
<script src="js/lz4.js"></script>
|
||||||
<script src="js/usersettings.js"></script>
|
<script src="js/usersettings.js"></script>
|
||||||
<script src="js/liquid-dict.js"></script>
|
|
||||||
<script src="js/matrix.js"></script>
|
<script src="js/matrix.js"></script>
|
||||||
<script src="js/recipe-manager.js"></script>
|
<script src="js/recipe-manager.js"></script>
|
||||||
<script src="js/utils.js"></script>
|
<script src="js/cachestorage.js"></script>
|
||||||
<script src="js/assets.js"></script>
|
<script src="js/assets.js"></script>
|
||||||
|
<script src="js/filtering-context.js"></script>
|
||||||
<script src="js/httpsb.js"></script>
|
<script src="js/httpsb.js"></script>
|
||||||
<script src="js/uritools.js"></script>
|
|
||||||
<script src="js/cookies.js"></script>
|
<script src="js/cookies.js"></script>
|
||||||
<script src="js/logger.js"></script>
|
<script src="js/logger.js"></script>
|
||||||
<script src="js/messaging.js"></script>
|
<script src="js/messaging.js"></script>
|
||||||
<script src="js/storage.js"></script>
|
<script src="js/storage.js"></script>
|
||||||
<script src="js/pagestats.js"></script>
|
<script src="js/pagestats.js"></script>
|
||||||
<script src="js/tab.js"></script>
|
<script src="js/tab.js"></script>
|
||||||
<script src="js/traffic.js"></script>
|
|
||||||
<script src="js/browsercache.js"></script>
|
<script src="js/browsercache.js"></script>
|
||||||
<script src="js/start.js"></script>
|
<script src="js/start.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
.codeMirrorContainer {
|
.codeMirrorContainer {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.25;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +17,73 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-s-default .cm-comment { color: #777; }
|
||||||
|
.cm-directive { color: #333; font-weight: bold; }
|
||||||
|
.cm-staticext { color: #008; }
|
||||||
|
.cm-staticnetBlock { color: #800; }
|
||||||
|
.cm-staticnetAllow { color: #004f00; }
|
||||||
|
.cm-staticOpt { background-color: #ddd; font-weight: bold; }
|
||||||
|
|
||||||
|
.cm-search-widget {
|
||||||
|
align-items: center;
|
||||||
|
background-color: #eee;
|
||||||
|
cursor: default;
|
||||||
|
direction: ltr;
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 110%;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
/* position: absolute; */
|
||||||
|
right: 2em;
|
||||||
|
top: 0;
|
||||||
|
user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.cm-search-widget .fa-icon {
|
||||||
|
fill: #888;
|
||||||
|
font-size: 140%;
|
||||||
|
}
|
||||||
|
.cm-search-widget .fa-icon:not(.fa-icon-ro):hover {
|
||||||
|
fill: #000;
|
||||||
|
}
|
||||||
|
.cm-search-widget-input {
|
||||||
|
border: 1px solid gray;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: inline-flex;
|
||||||
|
min-width: 16em;
|
||||||
|
}
|
||||||
|
.cm-search-widget-input > input {
|
||||||
|
border: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.cm-search-widget-input > .cm-search-widget-count {
|
||||||
|
align-items: center;
|
||||||
|
color: #888;
|
||||||
|
display: none;
|
||||||
|
flex-grow: 0;
|
||||||
|
font-size: 80%;
|
||||||
|
padding: 0 0.4em;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.cm-search-widget[data-query] .cm-search-widget-count {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
.cm-search-widget .cm-search-widget-button:hover {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.cm-search-widget .sourceURL {
|
||||||
|
padding-left: 0.5em;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.cm-search-widget .sourceURL[href=""] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror-merge-l-deleted {
|
.CodeMirror-merge-l-deleted {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-size: 14px;
|
font: 14px sans-serif;
|
||||||
}
|
}
|
||||||
body[dir="ltr"] {
|
body[dir="ltr"] {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
|
@ -111,3 +111,35 @@ button.custom[disabled] {
|
||||||
code {
|
code {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.px-icon {
|
||||||
|
align-items: center;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.1em;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.px-icon > * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.px-icon.disabled,
|
||||||
|
.disabled > .px-icon,
|
||||||
|
.px-icon[disabled],
|
||||||
|
[disabled] > .px-icon {
|
||||||
|
color: #000;
|
||||||
|
fill: #000;
|
||||||
|
opacity: 0.25;
|
||||||
|
stroke: #888;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.px-icon > img {
|
||||||
|
height: 1em;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
.px-icon.active {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
|
@ -23,9 +23,13 @@ body {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
#dashboard-nav-widgets span {
|
#dashboard-nav-widgets > span {
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
font-size: larger;
|
font-size: larger;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
#dashboard-nav-widgets > span > img {
|
||||||
|
width: 1em;
|
||||||
}
|
}
|
||||||
.tabButton {
|
.tabButton {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -54,9 +54,7 @@ a {
|
||||||
border: 0;
|
border: 0;
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
font: 12px/1 sans-serif;
|
||||||
font-size: 12px;
|
|
||||||
line-height: 12px;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -65,17 +63,11 @@ a {
|
||||||
|
|
||||||
.paneHead {
|
.paneHead {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
left: 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: fixed;
|
position: sticky;
|
||||||
right: 0;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
.paneContent {
|
|
||||||
padding-top: 5.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.paneHead > a:first-child {
|
.paneHead > a:first-child {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
|
@ -152,6 +144,9 @@ body .toolbar button.disabled {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
stroke: none;
|
stroke: none;
|
||||||
}
|
}
|
||||||
|
#mtxSwitches > li > svg > * {
|
||||||
|
fill: #bbb;
|
||||||
|
}
|
||||||
#mtxSwitches > li.relevant > svg .dot {
|
#mtxSwitches > li.relevant > svg .dot {
|
||||||
fill: #aaa;
|
fill: #aaa;
|
||||||
}
|
}
|
||||||
|
@ -181,6 +176,13 @@ body .toolbar button.disabled {
|
||||||
#mtxSwitches > li > a:hover {
|
#mtxSwitches > li > a:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
#mtxSwitches > li.unsupported {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
#mtxSwitches > li.unsupported > svg .on,
|
||||||
|
#mtxSwitches > li.unsupported > svg .off {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-menu-capture {
|
.dropdown-menu-capture {
|
||||||
background-color: rgba(0,0,0,0.2);
|
background-color: rgba(0,0,0,0.2);
|
||||||
|
@ -336,19 +338,23 @@ body[data-scope="*"] .toolbar .scopeRel.disabled {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.embedded [data-extension-url],
|
||||||
|
body.tabless .needtab {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.matrix {
|
.matrix {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.matRow {
|
.matRow {
|
||||||
|
display: flex;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.matCell {
|
.matCell {
|
||||||
margin: 1px 1px 0 0;
|
margin: 1px 1px 0 0;
|
||||||
border: 1px dotted rgba(0,0,0,0.2);
|
padding: 6px 2px;
|
||||||
padding: 6px 1px 3px 1px;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
-moz-box-sizing: content-box;
|
|
||||||
width: 2.6em;
|
width: 2.6em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -357,9 +363,8 @@ body[data-scope="*"] .toolbar .scopeRel.disabled {
|
||||||
}
|
}
|
||||||
|
|
||||||
#matHead {
|
#matHead {
|
||||||
border-top: 1px dotted #ccc;
|
padding-bottom: 1px;
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
margin: 1px 0 0 0;
|
|
||||||
}
|
}
|
||||||
.paneHead .matCell:nth-child(2) {
|
.paneHead .matCell:nth-child(2) {
|
||||||
letter-spacing: -0.3px;
|
letter-spacing: -0.3px;
|
||||||
|
@ -383,17 +388,14 @@ body[data-scope="*"] .toolbar .scopeRel.disabled {
|
||||||
direction: inherit;
|
direction: inherit;
|
||||||
}
|
}
|
||||||
.matrix .matRow.l2 > .matCell:first-child {
|
.matrix .matRow.l2 > .matCell:first-child {
|
||||||
margin-left: 1px;
|
|
||||||
width: calc(16em - 1px);
|
|
||||||
}
|
}
|
||||||
.matrix .matRow > .matCell:hover {
|
.matrix .matRow > .matCell:hover {
|
||||||
border-style: solid;
|
|
||||||
}
|
}
|
||||||
.matrix .matGroup .matSection {
|
.matrix .matGroup .matSection {
|
||||||
margin: 2px 0 0 0;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
/* background-color: rgba(0,0,0,0.05); */
|
}
|
||||||
|
.matrix .matGroup .matSection:hover {
|
||||||
}
|
}
|
||||||
.matrix .matGroup.g0 .matSection:first-child {
|
.matrix .matGroup.g0 .matSection:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -406,7 +408,7 @@ body[data-scope="*"] .toolbar .scopeRel.disabled {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.matrix .matSection.collapsible.collapsed .matRow.meta {
|
.matrix .matSection.collapsible.collapsed .matRow.meta {
|
||||||
display: block;
|
display: flex;
|
||||||
}
|
}
|
||||||
.matrix .matSection.collapsible.collapsed .matRow.l1:not(.meta) {
|
.matrix .matSection.collapsible.collapsed .matRow.l1:not(.meta) {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -416,6 +418,9 @@ body[data-scope="*"] .toolbar .scopeRel.disabled {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Collapsing of blacklisted */
|
/* Collapsing of blacklisted */
|
||||||
|
.matrix .matGroup.g4 {
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
.matrix .g4Meta {
|
.matrix .g4Meta {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -443,7 +448,7 @@ body.powerOff .matrix .g4Meta.g4Collapsed ~ .matSection {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.matrix .g4Meta.g4Collapsed ~ .matRow.ro {
|
.matrix .g4Meta.g4Collapsed ~ .matRow.ro {
|
||||||
display: block;
|
display: flex;
|
||||||
}
|
}
|
||||||
body.powerOff .matrix .g4Meta.g4Collapsed ~ .matRow.ro {
|
body.powerOff .matrix .g4Meta.g4Collapsed ~ .matRow.ro {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -463,12 +468,10 @@ body.powerOff .matrix .g4Meta.g4Collapsed ~ .matRow.ro {
|
||||||
background-color: #080;
|
background-color: #080;
|
||||||
}
|
}
|
||||||
.t1 {
|
.t1 {
|
||||||
border-color: #debaba;
|
|
||||||
color: black;
|
color: black;
|
||||||
background-color: #f8d0d0;
|
background-color: #f8d0d0;
|
||||||
}
|
}
|
||||||
.t2 {
|
.t2 {
|
||||||
border-color: #bad6ba;
|
|
||||||
color: black;
|
color: black;
|
||||||
background-color: #d0f0d0;
|
background-color: #d0f0d0;
|
||||||
}
|
}
|
||||||
|
@ -489,17 +492,14 @@ body.colorblind .t81 {
|
||||||
background-color: rgb(0, 19, 110);
|
background-color: rgb(0, 19, 110);
|
||||||
}
|
}
|
||||||
body.colorblind .t82 {
|
body.colorblind .t82 {
|
||||||
border-color: rgb(255, 194, 57);
|
|
||||||
color: black;
|
color: black;
|
||||||
background-color: rgb(255, 194, 57);
|
background-color: rgb(255, 194, 57);
|
||||||
}
|
}
|
||||||
body.colorblind .t1 {
|
body.colorblind .t1 {
|
||||||
border-color: rgba(0, 19, 110, 0.3);
|
|
||||||
color: black;
|
color: black;
|
||||||
background-color: rgba(0, 19, 110, 0.2);
|
background-color: rgba(0, 19, 110, 0.2);
|
||||||
}
|
}
|
||||||
body.colorblind .t2 {
|
body.colorblind .t2 {
|
||||||
border-color: rgba(255, 194, 57, 0.3);
|
|
||||||
color: black;
|
color: black;
|
||||||
background-color: rgba(255, 194, 57, 0.2);
|
background-color: rgba(255, 194, 57, 0.2);
|
||||||
}
|
}
|
||||||
|
@ -612,7 +612,7 @@ body.colorblind .rw .matCell.t2 #blacklist:hover {
|
||||||
}
|
}
|
||||||
.matSection.collapsible .matRow.l1 .matCell:nth-of-type(1):hover #domainOnly,
|
.matSection.collapsible .matRow.l1 .matCell:nth-of-type(1):hover #domainOnly,
|
||||||
#matHead.collapsible .matRow .matCell:nth-of-type(1):hover #domainOnly {
|
#matHead.collapsible .matRow .matCell:nth-of-type(1):hover #domainOnly {
|
||||||
display: inline-flex;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
#domainOnly:hover {
|
#domainOnly:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
@ -638,12 +638,6 @@ body.noTabFound #noTabFound {
|
||||||
body.hConstrained {
|
body.hConstrained {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
body.hConstrained .paneHead {
|
|
||||||
left: auto;
|
|
||||||
position: absolute;
|
|
||||||
right: auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
body[data-touch="true"] .matCell {
|
body[data-touch="true"] .matCell {
|
||||||
line-height: 200%;
|
line-height: 200%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
div.body {
|
html {
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
justify-content: space-between;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
p {
|
body {
|
||||||
margin: 0.5em 0;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
textarea {
|
#rawSettings {
|
||||||
box-sizing: border-box;
|
border-top: 1px solid #ddd;
|
||||||
flex-grow: 1;
|
height: 75vh;
|
||||||
resize: none;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: pre;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
word-wrap: normal;
|
}
|
||||||
|
.CodeMirror-wrap pre {
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,15 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="shortcut icon" type="image/png" href="img/icon_16.png">
|
<link rel="shortcut icon" type="image/png" href="img/icon_16.png">
|
||||||
<title data-i18n="dashboardPageName"></title>
|
<title data-i18n="dashboardPageName"></title>
|
||||||
<link href='css/dashboard.css' rel='stylesheet' type='text/css'>
|
<link href="css/dashboard.css" rel="stylesheet" type="text/css">
|
||||||
<link href='css/common.css' rel='stylesheet' type='text/css'>
|
<link href="css/common.css" rel="stylesheet" type="text/css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="dashboard-nav">
|
<div id="dashboard-nav">
|
||||||
<div id="dashboard-nav-widgets">
|
<div id="dashboard-nav-widgets">
|
||||||
<span>uMatrix</span>
|
<span data-i18n-title="extName"><img src="img/icon_64.png"></span><!--
|
||||||
<a class="tabButton" id="settings" href="#settings" data-dashboard-panel-url="settings.html" data-i18n="settingsPageName"></a>
|
--><a class="tabButton" id="settings" href="#settings" data-dashboard-panel-url="settings.html" data-i18n="settingsPageName"></a>
|
||||||
<a class="tabButton" id="user-rules" href="#user-rules" data-dashboard-panel-url="user-rules.html" data-i18n="userRulesPageName"></a>
|
<a class="tabButton" id="user-rules" href="#user-rules" data-dashboard-panel-url="user-rules.html" data-i18n="userRulesPageName"></a>
|
||||||
<a class="tabButton" id="hosts-files" href="#hosts-files" data-dashboard-panel-url="hosts-files.html" data-i18n="ubiquitousRulesPageName"></a>
|
<a class="tabButton" id="hosts-files" href="#hosts-files" data-dashboard-panel-url="hosts-files.html" data-i18n="ubiquitousRulesPageName"></a>
|
||||||
<a class="tabButton" id="raw-settings" href="#raw-settings" data-dashboard-panel-url="raw-settings.html" data-i18n="rawSettingsPageName"></a>
|
<a class="tabButton" id="raw-settings" href="#raw-settings" data-dashboard-panel-url="raw-settings.html" data-i18n="rawSettingsPageName"></a>
|
||||||
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
<iframe src=""></iframe>
|
<iframe src=""></iframe>
|
||||||
|
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
|
|
|
@ -66,8 +66,10 @@
|
||||||
</div><!-- end of div.body -->
|
</div><!-- end of div.body -->
|
||||||
|
|
||||||
<script src="js/fa-icons.js"></script>
|
<script src="js/fa-icons.js"></script>
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
|
<script src="js/vapi-client-extra.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
<script src="js/i18n.js"></script>
|
<script src="js/i18n.js"></script>
|
||||||
<script src="js/dashboard-common.js"></script>
|
<script src="js/dashboard-common.js"></script>
|
||||||
|
|
|
@ -28,6 +28,7 @@ License - https://github.com/FortAwesome/Font-Awesome/tree/a8386aae19e200ddb0f68
|
||||||
<defs>
|
<defs>
|
||||||
<symbol id="angle-up" viewBox="0 0 998 582"><path d="m 998,499 q 0,13 -10,23 l -50,50 q -10,10 -23,10 -13,0 -23,-10 L 499,179 106,572 Q 96,582 83,582 70,582 60,572 L 10,522 Q 0,512 0,499 0,486 10,476 L 476,10 q 10,-10 23,-10 13,0 23,10 l 466,466 q 10,10 10,23 z"/></symbol>
|
<symbol id="angle-up" viewBox="0 0 998 582"><path d="m 998,499 q 0,13 -10,23 l -50,50 q -10,10 -23,10 -13,0 -23,-10 L 499,179 106,572 Q 96,582 83,582 70,582 60,572 L 10,522 Q 0,512 0,499 0,486 10,476 L 476,10 q 10,-10 23,-10 13,0 23,10 l 466,466 q 10,10 10,23 z"/></symbol>
|
||||||
<symbol id="arrow-left" viewBox="0 0 1472 1558"><path d="m 1472,715 0,128 q 0,53 -32.5,90.5 Q 1407,971 1355,971 l -704,0 293,294 q 38,36 38,90 0,54 -38,90 l -75,76 q -37,37 -90,37 -52,0 -91,-37 L 37,869 Q 0,832 0,779 0,727 37,688 L 688,38 q 38,-38 91,-38 52,0 90,38 l 75,74 q 38,38 38,91 0,53 -38,91 l -293,293 704,0 q 52,0 84.5,37.5 32.5,37.5 32.5,90.5 z"/></symbol>
|
<symbol id="arrow-left" viewBox="0 0 1472 1558"><path d="m 1472,715 0,128 q 0,53 -32.5,90.5 Q 1407,971 1355,971 l -704,0 293,294 q 38,36 38,90 0,54 -38,90 l -75,76 q -37,37 -90,37 -52,0 -91,-37 L 37,869 Q 0,832 0,779 0,727 37,688 L 688,38 q 38,-38 91,-38 52,0 90,38 l 75,74 q 38,38 38,91 0,53 -38,91 l -293,293 704,0 q 52,0 84.5,37.5 32.5,37.5 32.5,90.5 z"/></symbol>
|
||||||
|
<symbol id="clipboard" viewBox="0 0 1792 1792"><path d="m 768,1664 896,0 0,-640 -416,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -384,0 0,1152 z m 256,-1440 0,-64 q 0,-13 -9.5,-22.5 Q 1005,128 992,128 l -704,0 q -13,0 -22.5,9.5 Q 256,147 256,160 l 0,64 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 704,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 256,672 299,0 -299,-299 0,299 z m 512,128 0,672 q 0,40 -28,68 -28,28 -68,28 l -960,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-160 -544,0 Q 56,1536 28,1508 0,1480 0,1440 L 0,96 Q 0,56 28,28 56,0 96,0 l 1088,0 q 40,0 68,28 28,28 28,68 l 0,328 q 21,13 36,28 l 408,408 q 28,28 48,76 20,48 20,88 z"/></symbol>
|
||||||
<symbol id="clock" viewBox="0 0 1536 1536"><path d="m 896,416 0,448 q 0,14 -9,23 -9,9 -23,9 l -320,0 q -14,0 -23,-9 -9,-9 -9,-23 l 0,-64 q 0,-14 9,-23 9,-9 23,-9 l 224,0 0,-352 q 0,-14 9,-23 9,-9 23,-9 l 64,0 q 14,0 23,9 9,9 9,23 z m 416,352 q 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 z m 224,0 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z"/></symbol>
|
<symbol id="clock" viewBox="0 0 1536 1536"><path d="m 896,416 0,448 q 0,14 -9,23 -9,9 -23,9 l -320,0 q -14,0 -23,-9 -9,-9 -9,-23 l 0,-64 q 0,-14 9,-23 9,-9 23,-9 l 224,0 0,-352 q 0,-14 9,-23 9,-9 23,-9 l 64,0 q 14,0 23,9 9,9 9,23 z m 416,352 q 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 z m 224,0 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z"/></symbol>
|
||||||
<symbol id="cloud-download" viewBox="0 0 1920 1408"><path d="m 1280,800 q 0,-14 -9,-23 -9,-9 -23,-9 l -224,0 0,-352 q 0,-13 -9.5,-22.5 Q 1005,384 992,384 l -192,0 q -13,0 -22.5,9.5 Q 768,403 768,416 l 0,352 -224,0 q -13,0 -22.5,9.5 -9.5,9.5 -9.5,22.5 0,14 9,23 l 352,352 q 9,9 23,9 14,0 23,-9 l 351,-351 q 10,-12 10,-24 z m 640,224 q 0,159 -112.5,271.5 Q 1695,1408 1536,1408 l -1088,0 Q 263,1408 131.5,1276.5 0,1145 0,960 0,830 70,720 140,610 258,555 256,525 256,512 256,300 406,150 556,0 768,0 q 156,0 285.5,87 129.5,87 188.5,231 71,-62 166,-62 106,0 181,75 75,75 75,181 0,76 -41,138 130,31 213.5,135.5 Q 1920,890 1920,1024 Z"/></symbol>
|
<symbol id="cloud-download" viewBox="0 0 1920 1408"><path d="m 1280,800 q 0,-14 -9,-23 -9,-9 -23,-9 l -224,0 0,-352 q 0,-13 -9.5,-22.5 Q 1005,384 992,384 l -192,0 q -13,0 -22.5,9.5 Q 768,403 768,416 l 0,352 -224,0 q -13,0 -22.5,9.5 -9.5,9.5 -9.5,22.5 0,14 9,23 l 352,352 q 9,9 23,9 14,0 23,-9 l 351,-351 q 10,-12 10,-24 z m 640,224 q 0,159 -112.5,271.5 Q 1695,1408 1536,1408 l -1088,0 Q 263,1408 131.5,1276.5 0,1145 0,960 0,830 70,720 140,610 258,555 256,525 256,512 256,300 406,150 556,0 768,0 q 156,0 285.5,87 129.5,87 188.5,231 71,-62 166,-62 106,0 181,75 75,75 75,181 0,76 -41,138 130,31 213.5,135.5 Q 1920,890 1920,1024 Z"/></symbol>
|
||||||
<symbol id="cloud-upload" viewBox="0 0 1920 1408"><path d="m 1280,736 q 0,-14 -9,-23 L 919,361 q -9,-9 -23,-9 -14,0 -23,9 L 522,712 q -10,12 -10,24 0,14 9,23 9,9 23,9 l 224,0 0,352 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 192,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 l 0,-352 224,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 640,288 q 0,159 -112.5,271.5 Q 1695,1408 1536,1408 l -1088,0 Q 263,1408 131.5,1276.5 0,1145 0,960 0,830 70,720 140,610 258,555 256,525 256,512 256,300 406,150 556,0 768,0 q 156,0 285.5,87 129.5,87 188.5,231 71,-62 166,-62 106,0 181,75 75,75 75,181 0,76 -41,138 130,31 213.5,135.5 Q 1920,890 1920,1024 Z"/></symbol>
|
<symbol id="cloud-upload" viewBox="0 0 1920 1408"><path d="m 1280,736 q 0,-14 -9,-23 L 919,361 q -9,-9 -23,-9 -14,0 -23,9 L 522,712 q -10,12 -10,24 0,14 9,23 9,9 23,9 l 224,0 0,352 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 192,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 l 0,-352 224,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 640,288 q 0,159 -112.5,271.5 Q 1695,1408 1536,1408 l -1088,0 Q 263,1408 131.5,1276.5 0,1145 0,960 0,830 70,720 140,610 258,555 256,525 256,512 256,300 406,150 556,0 768,0 q 156,0 285.5,87 129.5,87 188.5,231 71,-62 166,-62 106,0 181,75 75,75 75,181 0,76 -41,138 130,31 213.5,135.5 Q 1920,890 1920,1024 Z"/></symbol>
|
||||||
|
@ -42,6 +43,8 @@ License - https://github.com/FortAwesome/Font-Awesome/tree/a8386aae19e200ddb0f68
|
||||||
<symbol id="info-circle" viewBox="0 0 1536 1536"><path d="m 1024,1248 0,-160 q 0,-14 -9,-23 -9,-9 -23,-9 l -96,0 0,-512 q 0,-14 -9,-23 -9,-9 -23,-9 l -320,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 96,0 0,320 -96,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 448,0 q 14,0 23,-9 9,-9 9,-23 z M 896,352 896,192 q 0,-14 -9,-23 -9,-9 -23,-9 l -192,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 192,0 q 14,0 23,-9 9,-9 9,-23 z m 640,416 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z"/></symbol>
|
<symbol id="info-circle" viewBox="0 0 1536 1536"><path d="m 1024,1248 0,-160 q 0,-14 -9,-23 -9,-9 -23,-9 l -96,0 0,-512 q 0,-14 -9,-23 -9,-9 -23,-9 l -320,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 96,0 0,320 -96,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 448,0 q 14,0 23,-9 9,-9 9,-23 z M 896,352 896,192 q 0,-14 -9,-23 -9,-9 -23,-9 l -192,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 192,0 q 14,0 23,-9 9,-9 9,-23 z m 640,416 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z"/></symbol>
|
||||||
<symbol id="list-alt" viewBox="0 0 1792 1408"><path d="m 384,1056 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -64,0 q -13,0 -22.5,-9.5 Q 256,1133 256,1120 l 0,-64 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 Q 365,896 352,896 l -64,0 q -13,0 -22.5,-9.5 Q 256,877 256,864 l 0,-64 q 0,-13 9.5,-22.5 Q 275,768 288,768 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 Q 365,640 352,640 l -64,0 q -13,0 -22.5,-9.5 Q 256,621 256,608 l 0,-64 q 0,-13 9.5,-22.5 Q 275,512 288,512 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 1152,512 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,1133 512,1120 l 0,-64 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,877 512,864 l 0,-64 q 0,-13 9.5,-22.5 Q 531,768 544,768 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,621 512,608 l 0,-64 q 0,-13 9.5,-22.5 Q 531,512 544,512 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,704 0,-832 q 0,-13 -9.5,-22.5 Q 1645,384 1632,384 l -1472,0 q -13,0 -22.5,9.5 Q 128,403 128,416 l 0,832 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 1472,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 128,-1088 0,1088 q 0,66 -47,113 -47,47 -113,47 l -1472,0 Q 94,1408 47,1361 0,1314 0,1248 L 0,160 Q 0,94 47,47 94,0 160,0 l 1472,0 q 66,0 113,47 47,47 47,113 z"/></symbol>
|
<symbol id="list-alt" viewBox="0 0 1792 1408"><path d="m 384,1056 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -64,0 q -13,0 -22.5,-9.5 Q 256,1133 256,1120 l 0,-64 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 Q 365,896 352,896 l -64,0 q -13,0 -22.5,-9.5 Q 256,877 256,864 l 0,-64 q 0,-13 9.5,-22.5 Q 275,768 288,768 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 Q 365,640 352,640 l -64,0 q -13,0 -22.5,-9.5 Q 256,621 256,608 l 0,-64 q 0,-13 9.5,-22.5 Q 275,512 288,512 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 1152,512 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,1133 512,1120 l 0,-64 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,877 512,864 l 0,-64 q 0,-13 9.5,-22.5 Q 531,768 544,768 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,621 512,608 l 0,-64 q 0,-13 9.5,-22.5 Q 531,512 544,512 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,704 0,-832 q 0,-13 -9.5,-22.5 Q 1645,384 1632,384 l -1472,0 q -13,0 -22.5,9.5 Q 128,403 128,416 l 0,832 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 1472,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 128,-1088 0,1088 q 0,66 -47,113 -47,47 -113,47 l -1472,0 Q 94,1408 47,1361 0,1314 0,1248 L 0,160 Q 0,94 47,47 94,0 160,0 l 1472,0 q 66,0 113,47 47,47 47,113 z"/></symbol>
|
||||||
<symbol id="lock" viewBox="0 0 1152 1408"><path d="m 320,640 512,0 0,-192 q 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 l 0,192 z m 832,96 0,576 q 0,40 -28,68 -28,28 -68,28 l -960,0 Q 56,1408 28,1380 0,1352 0,1312 L 0,736 q 0,-40 28,-68 28,-28 68,-28 l 32,0 0,-192 Q 128,264 260,132 392,0 576,0 q 184,0 316,132 132,132 132,316 l 0,192 32,0 q 40,0 68,28 28,28 28,68 z"/></symbol>
|
<symbol id="lock" viewBox="0 0 1152 1408"><path d="m 320,640 512,0 0,-192 q 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 l 0,192 z m 832,96 0,576 q 0,40 -28,68 -28,28 -68,28 l -960,0 Q 56,1408 28,1380 0,1352 0,1312 L 0,736 q 0,-40 28,-68 28,-28 68,-28 l 32,0 0,-192 Q 128,264 260,132 392,0 576,0 q 184,0 316,132 132,132 132,316 l 0,192 32,0 q 40,0 68,28 28,28 28,68 z"/></symbol>
|
||||||
|
<symbol id="pause-circle-o" viewBox="0 0 1536 1536"><path d="M 768,0 Q 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 1536,977 1433,1153.5 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 Z m 0,1312 q 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 z m 96,-224 q -14,0 -23,-9 -9,-9 -9,-23 l 0,-576 q 0,-14 9,-23 9,-9 23,-9 l 192,0 q 14,0 23,9 9,9 9,23 l 0,576 q 0,14 -9,23 -9,9 -23,9 l -192,0 z m -384,0 q -14,0 -23,-9 -9,-9 -9,-23 l 0,-576 q 0,-14 9,-23 9,-9 23,-9 l 192,0 q 14,0 23,9 9,9 9,23 l 0,576 q 0,14 -9,23 -9,9 -23,9 l -192,0 z"/></symbol>
|
||||||
|
<symbol id="play-circle-o" viewBox="0 0 1536 1536"><path d="m 1184,768 q 0,37 -32,55 l -544,320 q -15,9 -32,9 -16,0 -32,-8 -32,-19 -32,-56 l 0,-640 q 0,-37 32,-56 33,-18 64,1 l 544,320 q 32,18 32,55 z m 128,0 q 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 z m 224,0 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z"/></symbol>
|
||||||
<symbol id="plus" viewBox="0 0 1408 1408"><path d="m 1408,608 0,192 q 0,40 -28,68 -28,28 -68,28 l -416,0 0,416 q 0,40 -28,68 -28,28 -68,28 l -192,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -416,0 Q 56,896 28,868 0,840 0,800 L 0,608 q 0,-40 28,-68 28,-28 68,-28 l 416,0 0,-416 Q 512,56 540,28 568,0 608,0 l 192,0 q 40,0 68,28 28,28 28,68 l 0,416 416,0 q 40,0 68,28 28,28 28,68 z"/></symbol>
|
<symbol id="plus" viewBox="0 0 1408 1408"><path d="m 1408,608 0,192 q 0,40 -28,68 -28,28 -68,28 l -416,0 0,416 q 0,40 -28,68 -28,28 -68,28 l -192,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -416,0 Q 56,896 28,868 0,840 0,800 L 0,608 q 0,-40 28,-68 28,-28 68,-28 l 416,0 0,-416 Q 512,56 540,28 568,0 608,0 l 192,0 q 40,0 68,28 28,28 28,68 l 0,416 416,0 q 40,0 68,28 28,28 28,68 z"/></symbol>
|
||||||
<symbol id="power-off" viewBox="0 0 1536 1664"><path d="m 1536,896 q 0,156 -61,298 -61,142 -164,245 -103,103 -245,164 -142,61 -298,61 -156,0 -298,-61 Q 328,1542 225,1439 122,1336 61,1194 0,1052 0,896 0,714 80.5,553 161,392 307,283 q 43,-32 95.5,-25 52.5,7 83.5,50 32,42 24.5,94.5 Q 503,455 461,487 363,561 309.5,668 256,775 256,896 q 0,104 40.5,198.5 40.5,94.5 109.5,163.5 69,69 163.5,109.5 94.5,40.5 198.5,40.5 104,0 198.5,-40.5 Q 1061,1327 1130,1258 1199,1189 1239.5,1094.5 1280,1000 1280,896 1280,775 1226.5,668 1173,561 1075,487 1033,455 1025.5,402.5 1018,350 1050,308 q 31,-43 84,-50 53,-7 95,25 146,109 226.5,270 80.5,161 80.5,343 z m -640,-768 0,640 q 0,52 -38,90 -38,38 -90,38 -52,0 -90,-38 -38,-38 -38,-90 l 0,-640 q 0,-52 38,-90 38,-38 90,-38 52,0 90,38 38,38 38,90 z"/></symbol>
|
<symbol id="power-off" viewBox="0 0 1536 1664"><path d="m 1536,896 q 0,156 -61,298 -61,142 -164,245 -103,103 -245,164 -142,61 -298,61 -156,0 -298,-61 Q 328,1542 225,1439 122,1336 61,1194 0,1052 0,896 0,714 80.5,553 161,392 307,283 q 43,-32 95.5,-25 52.5,7 83.5,50 32,42 24.5,94.5 Q 503,455 461,487 363,561 309.5,668 256,775 256,896 q 0,104 40.5,198.5 40.5,94.5 109.5,163.5 69,69 163.5,109.5 94.5,40.5 198.5,40.5 104,0 198.5,-40.5 Q 1061,1327 1130,1258 1199,1189 1239.5,1094.5 1280,1000 1280,896 1280,775 1226.5,668 1173,561 1075,487 1033,455 1025.5,402.5 1018,350 1050,308 q 31,-43 84,-50 53,-7 95,25 146,109 226.5,270 80.5,161 80.5,343 z m -640,-768 0,640 q 0,52 -38,90 -38,38 -90,38 -52,0 -90,-38 -38,-38 -38,-90 l 0,-640 q 0,-52 38,-90 38,-38 90,-38 52,0 90,38 38,38 38,90 z"/></symbol>
|
||||||
<symbol id="puzzle-piece" viewBox="0 0 1664 1572"><path d="m 1664,1098 q 0,81 -44.5,135 -44.5,54 -123.5,54 -41,0 -77.5,-17.5 -36.5,-17.5 -59,-38 -22.5,-20.5 -56.5,-38 -34,-17.5 -71,-17.5 -110,0 -110,124 0,39 16,115 16,76 15,115 l 0,5 q -22,0 -33,1 -34,3 -97.5,11.5 -63.5,8.5 -115.5,13.5 -52,5 -98,5 -61,0 -103,-26.5 -42,-26.5 -42,-83.5 0,-37 17.5,-71 17.5,-34 38,-56.5 20.5,-22.5 38,-59 17.5,-36.5 17.5,-77.5 0,-79 -54,-123.5 -54,-44.5 -135,-44.5 -84,0 -143,45.5 -59,45.5 -59,127.5 0,43 15,83 15,40 33.5,64.5 18.5,24.5 33.5,53 15,28.5 15,50.5 0,45 -46,89 -37,35 -117,35 -95,0 -245,-24 -9,-2 -27.5,-4 -18.5,-2 -27.5,-4 l -13,-2 q -1,0 -3,-1 -2,0 -2,-1 L 0,512 q 2,1 17.5,3.5 15.5,2.5 34,5 18.5,2.5 21.5,3.5 150,24 245,24 80,0 117,-35 46,-44 46,-89 0,-22 -15,-50.5 Q 451,345 432.5,320.5 414,296 399,256 384,216 384,173 384,91 443,45.5 502,0 587,0 667,0 721,44.5 775,89 775,168 q 0,41 -17.5,77.5 -17.5,36.5 -38,59 -20.5,22.5 -38,56.5 -17.5,34 -17.5,71 0,57 42,83.5 42,26.5 103,26.5 64,0 180,-15 116,-15 163,-17 l 0,2 q -1,2 -3.5,17.5 -2.5,15.5 -5,34 -2.5,18.5 -3.5,21.5 -24,150 -24,245 0,80 35,117 44,46 89,46 22,0 50.5,-15 28.5,-15 53,-33.5 24.5,-18.5 64.5,-33.5 40,-15 83,-15 82,0 127.5,59 45.5,59 45.5,143 z"/></symbol>
|
<symbol id="puzzle-piece" viewBox="0 0 1664 1572"><path d="m 1664,1098 q 0,81 -44.5,135 -44.5,54 -123.5,54 -41,0 -77.5,-17.5 -36.5,-17.5 -59,-38 -22.5,-20.5 -56.5,-38 -34,-17.5 -71,-17.5 -110,0 -110,124 0,39 16,115 16,76 15,115 l 0,5 q -22,0 -33,1 -34,3 -97.5,11.5 -63.5,8.5 -115.5,13.5 -52,5 -98,5 -61,0 -103,-26.5 -42,-26.5 -42,-83.5 0,-37 17.5,-71 17.5,-34 38,-56.5 20.5,-22.5 38,-59 17.5,-36.5 17.5,-77.5 0,-79 -54,-123.5 -54,-44.5 -135,-44.5 -84,0 -143,45.5 -59,45.5 -59,127.5 0,43 15,83 15,40 33.5,64.5 18.5,24.5 33.5,53 15,28.5 15,50.5 0,45 -46,89 -37,35 -117,35 -95,0 -245,-24 -9,-2 -27.5,-4 -18.5,-2 -27.5,-4 l -13,-2 q -1,0 -3,-1 -2,0 -2,-1 L 0,512 q 2,1 17.5,3.5 15.5,2.5 34,5 18.5,2.5 21.5,3.5 150,24 245,24 80,0 117,-35 46,-44 46,-89 0,-22 -15,-50.5 Q 451,345 432.5,320.5 414,296 399,256 384,216 384,173 384,91 443,45.5 502,0 587,0 667,0 721,44.5 775,89 775,168 q 0,41 -17.5,77.5 -17.5,36.5 -38,59 -20.5,22.5 -38,56.5 -17.5,34 -17.5,71 0,57 42,83.5 42,26.5 103,26.5 64,0 180,-15 116,-15 163,-17 l 0,2 q -1,2 -3.5,17.5 -2.5,15.5 -5,34 -2.5,18.5 -3.5,21.5 -24,150 -24,245 0,80 35,117 44,46 89,46 22,0 50.5,-15 28.5,-15 53,-33.5 24.5,-18.5 64.5,-33.5 40,-15 83,-15 82,0 127.5,59 45.5,59 45.5,143 z"/></symbol>
|
||||||
|
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 26 KiB |
124
src/js/about.js
124
src/js/about.js
|
@ -1,7 +1,7 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uMatrix - a Chromium browser extension to black/white list requests.
|
uMatrix - a Chromium browser extension to black/white list requests.
|
||||||
Copyright (C) 2014-2018 Raymond Hill
|
Copyright (C) 2014-present Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -25,83 +25,82 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
uDom.onLoad(function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var backupUserDataToFile = function() {
|
const backupUserDataToFile = function() {
|
||||||
var userDataReady = function(userData) {
|
vAPI.messaging.send('dashboard', {
|
||||||
|
what: 'getAllUserData',
|
||||||
|
}).then(userData => {
|
||||||
vAPI.download({
|
vAPI.download({
|
||||||
'url': 'data:text/plain,' + encodeURIComponent(JSON.stringify(userData, null, 2)),
|
url: 'data:text/plain,' + encodeURIComponent(
|
||||||
'filename': uDom('[data-i18n="aboutBackupFilename"]').text()
|
JSON.stringify(userData, null, 2)
|
||||||
|
),
|
||||||
|
filename:
|
||||||
|
uDom.nodeFromSelector('[data-i18n="aboutBackupFilename"]')
|
||||||
|
.textContent
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
vAPI.messaging.send('about.js', { what: 'getAllUserData' }, userDataReady);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function restoreUserDataFromFile() {
|
const restoreUserDataFromFile = function() {
|
||||||
var validateBackup = function(s) {
|
const validateBackup = function(s) {
|
||||||
var userData = null;
|
let userData;
|
||||||
try {
|
try {
|
||||||
userData = JSON.parse(s);
|
userData = JSON.parse(s);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (ex) {
|
||||||
userData = null;
|
|
||||||
}
|
|
||||||
if ( userData === null ) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
if ( userData === undefined ) { return; }
|
||||||
if (
|
if (
|
||||||
typeof userData !== 'object' ||
|
typeof userData !== 'object' ||
|
||||||
typeof userData.app !== 'string' ||
|
typeof userData.app !== 'string' ||
|
||||||
typeof userData.version !== 'string' ||
|
typeof userData.version !== 'string' ||
|
||||||
typeof userData.when !== 'number' ||
|
typeof userData.when !== 'number' ||
|
||||||
typeof userData.settings !== 'object' ||
|
typeof userData.settings !== 'object' ||
|
||||||
(typeof userData.rules !== 'string' &&
|
typeof userData.rules !== 'string' &&
|
||||||
Array.isArray(userData.rules) === false)
|
Array.isArray(userData.rules) === false
|
||||||
) {
|
) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
return userData;
|
return userData;
|
||||||
};
|
};
|
||||||
|
|
||||||
var fileReaderOnLoadHandler = function() {
|
const fileReaderOnLoadHandler = function() {
|
||||||
var userData = validateBackup(this.result);
|
const userData = validateBackup(this.result);
|
||||||
if ( !userData ) {
|
if ( userData instanceof Object === false ) {
|
||||||
window.alert(uDom('[data-i18n="aboutRestoreError"]').text());
|
window.alert(uDom('[data-i18n="aboutRestoreError"]').text());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var time = new Date(userData.when);
|
const time = new Date(userData.when);
|
||||||
var msg = uDom('[data-i18n="aboutRestoreConfirm"]').text()
|
const msg = uDom.nodeFromSelector('[data-i18n="aboutRestoreConfirm"]')
|
||||||
.replace('{{time}}', time.toLocaleString());
|
.textContent
|
||||||
var proceed = window.confirm(msg);
|
.replace('{{time}}', time.toLocaleString());
|
||||||
|
const proceed = window.confirm(msg);
|
||||||
if ( proceed ) {
|
if ( proceed ) {
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('dashboard', {
|
||||||
'about.js',
|
what: 'restoreAllUserData',
|
||||||
{ what: 'restoreAllUserData', userData: userData }
|
userData
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var file = this.files[0];
|
const file = this.files[0];
|
||||||
if ( file === undefined || file.name === '' ) {
|
if ( file === undefined || file.name === '' ) { return; }
|
||||||
return;
|
if ( file.type.indexOf('text') !== 0 ) { return; }
|
||||||
}
|
const fr = new FileReader();
|
||||||
if ( file.type.indexOf('text') !== 0 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var fr = new FileReader();
|
|
||||||
fr.onload = fileReaderOnLoadHandler;
|
fr.onload = fileReaderOnLoadHandler;
|
||||||
fr.readAsText(file);
|
fr.readAsText(file);
|
||||||
}
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var startRestoreFilePicker = function() {
|
const startRestoreFilePicker = function() {
|
||||||
var input = document.getElementById('restoreFilePicker');
|
const input = document.getElementById('restoreFilePicker');
|
||||||
// Reset to empty string, this will ensure an change event is properly
|
// Reset to empty string, this will ensure an change event is properly
|
||||||
// triggered if the user pick a file, even if it is the same as the last
|
// triggered if the user pick a file, even if it is the same as the last
|
||||||
// one picked.
|
// one picked.
|
||||||
|
@ -111,28 +110,30 @@ var startRestoreFilePicker = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var resetUserData = function() {
|
const resetUserData = function() {
|
||||||
var proceed = window.confirm(uDom('[data-i18n="aboutResetConfirm"]').text());
|
const msg = uDom.nodeFromSelector('[data-i18n="aboutResetConfirm"]')
|
||||||
if ( proceed ) {
|
.textContent;
|
||||||
vAPI.messaging.send('about.js', { what: 'resetAllUserData' });
|
const proceed = window.confirm(msg);
|
||||||
}
|
if ( proceed !== true ) { return; }
|
||||||
|
vAPI.messaging.send('dashboard', {
|
||||||
|
what: 'resetAllUserData',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
vAPI.messaging.send('dashboard', {
|
||||||
var renderStats = function(details) {
|
what: 'getSomeStats',
|
||||||
document.getElementById('aboutVersion').textContent = details.version;
|
}).then(details => {
|
||||||
var template = uDom('[data-i18n="aboutStorageUsed"]').text();
|
document.getElementById('aboutVersion').textContent = details.version;
|
||||||
var storageUsed = '?';
|
const template = uDom('[data-i18n="aboutStorageUsed"]').text();
|
||||||
if ( typeof details.storageUsed === 'number' ) {
|
let storageUsed = '?';
|
||||||
storageUsed = details.storageUsed.toLocaleString();
|
if ( typeof details.storageUsed === 'number' ) {
|
||||||
}
|
storageUsed = details.storageUsed.toLocaleString();
|
||||||
document.getElementById('aboutStorageUsed').textContent =
|
}
|
||||||
template.replace('{{storageUsed}}', storageUsed);
|
document.getElementById('aboutStorageUsed').textContent =
|
||||||
};
|
template.replace('{{storageUsed}}', storageUsed);
|
||||||
vAPI.messaging.send('about.js', { what: 'getSomeStats' }, renderStats);
|
});
|
||||||
})();
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -143,4 +144,5 @@ uDom('#resetUserDataButton').on('click', resetUserData);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
});
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uMatrix - a Chromium browser extension to block requests.
|
uBlock Origin - a browser extension to block requests.
|
||||||
Copyright (C) 2014-2017 Raymond Hill
|
Copyright (C) 2014-present Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -16,30 +16,41 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
Home: https://github.com/gorhill/uMatrix
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* global CodeMirror, uBlockDashboard */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
(async ( ) => {
|
||||||
|
const params = new URL(document.location).searchParams;
|
||||||
|
const assetKey = params.get('url');
|
||||||
|
if ( assetKey === null ) { return; }
|
||||||
|
|
||||||
var onAssetContentReceived = function(details) {
|
const cmEditor = new CodeMirror(
|
||||||
document.getElementById('content').textContent =
|
document.getElementById('content'),
|
||||||
details && (details.content || '');
|
{
|
||||||
};
|
autofocus: true,
|
||||||
|
lineNumbers: true,
|
||||||
var q = window.location.search;
|
lineWrapping: true,
|
||||||
var matches = q.match(/^\?url=([^&]+)/);
|
readOnly: true,
|
||||||
if ( !matches || matches.length !== 2 ) {
|
styleActiveLine: true,
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
vAPI.messaging.send(
|
|
||||||
'asset-viewer.js',
|
|
||||||
{ what : 'getAssetContent', url: matches[1] },
|
|
||||||
onAssetContentReceived
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
uBlockDashboard.patchCodeMirrorEditor(cmEditor);
|
||||||
|
|
||||||
|
const details = await vAPI.messaging.send('default', {
|
||||||
|
what : 'getAssetContent',
|
||||||
|
url: assetKey,
|
||||||
|
});
|
||||||
|
cmEditor.setValue(details && details.content || '');
|
||||||
|
if ( details.sourceURL ) {
|
||||||
|
const a = document.querySelector('.cm-search-widget .sourceURL');
|
||||||
|
a.setAttribute('href', details.sourceURL);
|
||||||
|
a.setAttribute('title', details.sourceURL);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
1143
src/js/assets.js
1143
src/js/assets.js
File diff suppressed because it is too large
Load diff
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const µMatrix = (function() { // jshint ignore:line
|
const µMatrix = (( ) => { // jshint ignore:line
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -54,9 +54,22 @@ const oneDay = 24 * oneHour;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const rawSettingsDefault = {
|
const rawSettingsDefault = {
|
||||||
|
assetFetchBypassBrowserCache: false,
|
||||||
|
assetFetchTimeout: 30,
|
||||||
|
autoUpdateAssetFetchPeriod: 120,
|
||||||
|
cnameIgnoreList: 'unset',
|
||||||
|
cnameIgnore1stParty: true,
|
||||||
|
cnameIgnoreExceptions: true,
|
||||||
|
cnameIgnoreRootDocument: true,
|
||||||
|
cnameMaxTTL: 60,
|
||||||
|
cnameReplayFullURL: false,
|
||||||
|
consoleLogLevel: 'unset',
|
||||||
contributorMode: false,
|
contributorMode: false,
|
||||||
disableCSPReportInjection: false,
|
disableCSPReportInjection: false,
|
||||||
|
disableWebAssembly: false,
|
||||||
enforceEscapedFragment: true,
|
enforceEscapedFragment: true,
|
||||||
|
loggerPopupType: 'popup',
|
||||||
|
manualUpdateAssetFetchPeriod: 500,
|
||||||
placeholderBackground:
|
placeholderBackground:
|
||||||
[
|
[
|
||||||
'url("data:image/png;base64,',
|
'url("data:image/png;base64,',
|
||||||
|
@ -136,7 +149,6 @@ return {
|
||||||
externalHostsFiles: [],
|
externalHostsFiles: [],
|
||||||
externalRecipeFiles: [],
|
externalRecipeFiles: [],
|
||||||
iconBadgeEnabled: true,
|
iconBadgeEnabled: true,
|
||||||
maxLoggedRequests: 1000,
|
|
||||||
noTooltips: false,
|
noTooltips: false,
|
||||||
popupCollapseAllDomains: false,
|
popupCollapseAllDomains: false,
|
||||||
popupCollapseBlacklistedDomains: false,
|
popupCollapseBlacklistedDomains: false,
|
||||||
|
@ -155,22 +167,25 @@ return {
|
||||||
},
|
},
|
||||||
|
|
||||||
rawSettingsDefault: rawSettingsDefault,
|
rawSettingsDefault: rawSettingsDefault,
|
||||||
rawSettings: (function() {
|
rawSettings: (( ) => {
|
||||||
let out = Object.assign({}, rawSettingsDefault),
|
const out = Object.assign({}, rawSettingsDefault);
|
||||||
json = vAPI.localStorage.getItem('immediateRawSettings');
|
const json = vAPI.localStorage.getItem('immediateRawSettings');
|
||||||
if ( typeof json === 'string' ) {
|
if ( typeof json !== 'string' ) { return out; }
|
||||||
try {
|
try {
|
||||||
let o = JSON.parse(json);
|
const o = JSON.parse(json);
|
||||||
if ( o instanceof Object ) {
|
if ( o instanceof Object ) {
|
||||||
for ( const k in o ) {
|
for ( const k in o ) {
|
||||||
if ( out.hasOwnProperty(k) ) {
|
if ( out.hasOwnProperty(k) ) { out[k] = o[k]; }
|
||||||
out[k] = o[k];
|
}
|
||||||
}
|
self.log.verbosity = out.consoleLogLevel;
|
||||||
}
|
if ( typeof out.suspendTabsUntilReady === 'boolean' ) {
|
||||||
|
out.suspendTabsUntilReady = out.suspendTabsUntilReady
|
||||||
|
? 'yes'
|
||||||
|
: 'unset';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(ex) {
|
}
|
||||||
}
|
catch(ex) {
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
})(),
|
})(),
|
||||||
|
@ -201,6 +216,7 @@ return {
|
||||||
pMatrix: null,
|
pMatrix: null,
|
||||||
|
|
||||||
ubiquitousBlacklist: null,
|
ubiquitousBlacklist: null,
|
||||||
|
ubiquitousBlacklistRef: null,
|
||||||
|
|
||||||
// various stats
|
// various stats
|
||||||
cookieRemovedCounter: 0,
|
cookieRemovedCounter: 0,
|
||||||
|
@ -208,7 +224,6 @@ return {
|
||||||
cookieHeaderFoiledCounter: 0,
|
cookieHeaderFoiledCounter: 0,
|
||||||
hyperlinkAuditingFoiledCounter: 0,
|
hyperlinkAuditingFoiledCounter: 0,
|
||||||
browserCacheClearedCounter: 0,
|
browserCacheClearedCounter: 0,
|
||||||
storageUsed: 0,
|
|
||||||
|
|
||||||
// record what the browser is doing behind the scene
|
// record what the browser is doing behind the scene
|
||||||
behindTheSceneScope: 'behind-the-scene',
|
behindTheSceneScope: 'behind-the-scene',
|
||||||
|
|
|
@ -23,24 +23,21 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Browser data jobs
|
// Browser data jobs
|
||||||
|
|
||||||
var clearCache = function() {
|
const clearCache = function() {
|
||||||
vAPI.setTimeout(clearCache, 15 * 60 * 1000);
|
vAPI.setTimeout(clearCache, 15 * 60 * 1000);
|
||||||
|
|
||||||
var µm = µMatrix;
|
const µm = µMatrix;
|
||||||
if ( !µm.userSettings.clearBrowserCache ) {
|
if ( µm.userSettings.clearBrowserCache !== true ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
µm.clearBrowserCacheCycle -= 15;
|
µm.clearBrowserCacheCycle -= 15;
|
||||||
if ( µm.clearBrowserCacheCycle > 0 ) {
|
if ( µm.clearBrowserCacheCycle > 0 ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vAPI.browserData.clearCache();
|
vAPI.browserData.clearCache();
|
||||||
|
|
||||||
|
@ -48,15 +45,17 @@ var clearCache = function() {
|
||||||
µm.browserCacheClearedCounter++;
|
µm.browserCacheClearedCounter++;
|
||||||
|
|
||||||
// TODO: i18n
|
// TODO: i18n
|
||||||
µm.logger.writeOne({ info: vAPI.i18n('loggerEntryBrowserCacheCleared') });
|
µm.logger.writeOne({
|
||||||
|
realm: 'message',
|
||||||
//console.debug('clearBrowserCacheCallback()> vAPI.browserData.clearCache() called');
|
text: vAPI.i18n('loggerEntryBrowserCacheCleared'),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.setTimeout(clearCache, 15 * 60 * 1000);
|
vAPI.setTimeout(clearCache, 15 * 60 * 1000);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
465
src/js/cachestorage.js
Normal file
465
src/js/cachestorage.js
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2016-present The uBlock Origin authors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global IDBDatabase, indexedDB */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// The code below has been originally manually imported from:
|
||||||
|
// Commit: https://github.com/nikrolls/uBlock-Edge/commit/d1538ea9bea89d507219d3219592382eee306134
|
||||||
|
// Commit date: 29 October 2016
|
||||||
|
// Commit author: https://github.com/nikrolls
|
||||||
|
// Commit message: "Implement cacheStorage using IndexedDB"
|
||||||
|
|
||||||
|
// The original imported code has been subsequently modified as it was not
|
||||||
|
// compatible with Firefox.
|
||||||
|
// (a Promise thing, see https://github.com/dfahlander/Dexie.js/issues/317)
|
||||||
|
// Furthermore, code to migrate from browser.storage.local to vAPI.storage
|
||||||
|
// has been added, for seamless migration of cache-related entries into
|
||||||
|
// indexedDB.
|
||||||
|
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1371255
|
||||||
|
// Firefox-specific: we use indexedDB because browser.storage.local() has
|
||||||
|
// poor performance in Firefox.
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/328
|
||||||
|
// Use IndexedDB for Chromium as well, to take advantage of LZ4
|
||||||
|
// compression.
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/399
|
||||||
|
// Revert Chromium support of IndexedDB, use advanced setting to force
|
||||||
|
// IndexedDB.
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/409
|
||||||
|
// Allow forcing the use of webext storage on Firefox.
|
||||||
|
|
||||||
|
µMatrix.cacheStorage = (function() {
|
||||||
|
|
||||||
|
const STORAGE_NAME = 'uMatrixCacheStorage';
|
||||||
|
|
||||||
|
// Default to webext storage.
|
||||||
|
const localStorage = webext.storage.local;
|
||||||
|
const api = {
|
||||||
|
name: 'browser.storage.local',
|
||||||
|
get: localStorage.get,
|
||||||
|
set: localStorage.set,
|
||||||
|
remove: localStorage.remove,
|
||||||
|
clear: localStorage.clear,
|
||||||
|
getBytesInUse: localStorage.getBytesInUse,
|
||||||
|
select: function(selectedBackend) {
|
||||||
|
let actualBackend = selectedBackend;
|
||||||
|
if ( actualBackend === undefined || actualBackend === 'unset' ) {
|
||||||
|
actualBackend = vAPI.webextFlavor.soup.has('firefox')
|
||||||
|
? 'indexedDB'
|
||||||
|
: 'browser.storage.local';
|
||||||
|
}
|
||||||
|
if ( actualBackend === 'indexedDB' ) {
|
||||||
|
return selectIDB().then(success => {
|
||||||
|
if ( success || selectedBackend === 'indexedDB' ) {
|
||||||
|
clearWebext();
|
||||||
|
return 'indexedDB';
|
||||||
|
}
|
||||||
|
clearIDB();
|
||||||
|
return 'browser.storage.local';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ( actualBackend === 'browser.storage.local' ) {
|
||||||
|
clearIDB();
|
||||||
|
}
|
||||||
|
return Promise.resolve('browser.storage.local');
|
||||||
|
|
||||||
|
},
|
||||||
|
error: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reassign API entries to that of indexedDB-based ones
|
||||||
|
const selectIDB = async function() {
|
||||||
|
let db;
|
||||||
|
let dbPromise;
|
||||||
|
let dbTimer;
|
||||||
|
|
||||||
|
const noopfn = function () {
|
||||||
|
};
|
||||||
|
|
||||||
|
const disconnect = function() {
|
||||||
|
if ( dbTimer !== undefined ) {
|
||||||
|
clearTimeout(dbTimer);
|
||||||
|
dbTimer = undefined;
|
||||||
|
}
|
||||||
|
if ( db instanceof IDBDatabase ) {
|
||||||
|
db.close();
|
||||||
|
db = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const keepAlive = function() {
|
||||||
|
if ( dbTimer !== undefined ) {
|
||||||
|
clearTimeout(dbTimer);
|
||||||
|
}
|
||||||
|
dbTimer = vAPI.setTimeout(
|
||||||
|
( ) => {
|
||||||
|
dbTimer = undefined;
|
||||||
|
disconnect();
|
||||||
|
},
|
||||||
|
Math.max(
|
||||||
|
µMatrix.hiddenSettings.autoUpdateAssetFetchPeriod * 2 * 1000,
|
||||||
|
180000
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/3156
|
||||||
|
// I have observed that no event was fired in Tor Browser 7.0.7 +
|
||||||
|
// medium security level after the request to open the database was
|
||||||
|
// created. When this occurs, I have also observed that the `error`
|
||||||
|
// property was already set, so this means uBO can detect here whether
|
||||||
|
// the database can be opened successfully. A try-catch block is
|
||||||
|
// necessary when reading the `error` property because we are not
|
||||||
|
// allowed to read this propery outside of event handlers in newer
|
||||||
|
// implementation of IDBRequest (my understanding).
|
||||||
|
|
||||||
|
const getDb = function() {
|
||||||
|
keepAlive();
|
||||||
|
if ( db !== undefined ) {
|
||||||
|
return Promise.resolve(db);
|
||||||
|
}
|
||||||
|
if ( dbPromise !== undefined ) {
|
||||||
|
return dbPromise;
|
||||||
|
}
|
||||||
|
dbPromise = new Promise(resolve => {
|
||||||
|
let req;
|
||||||
|
try {
|
||||||
|
req = indexedDB.open(STORAGE_NAME, 1);
|
||||||
|
if ( req.error ) {
|
||||||
|
console.log(req.error);
|
||||||
|
req = undefined;
|
||||||
|
}
|
||||||
|
} catch(ex) {
|
||||||
|
}
|
||||||
|
if ( req === undefined ) {
|
||||||
|
db = null;
|
||||||
|
dbPromise = undefined;
|
||||||
|
return resolve(null);
|
||||||
|
}
|
||||||
|
req.onupgradeneeded = function(ev) {
|
||||||
|
if ( ev.oldVersion === 1 ) { return; }
|
||||||
|
try {
|
||||||
|
const db = ev.target.result;
|
||||||
|
db.createObjectStore(STORAGE_NAME, { keyPath: 'key' });
|
||||||
|
} catch(ex) {
|
||||||
|
req.onerror();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
req.onsuccess = function(ev) {
|
||||||
|
if ( resolve === undefined ) { return; }
|
||||||
|
req = undefined;
|
||||||
|
db = ev.target.result;
|
||||||
|
dbPromise = undefined;
|
||||||
|
resolve(db);
|
||||||
|
resolve = undefined;
|
||||||
|
};
|
||||||
|
req.onerror = req.onblocked = function() {
|
||||||
|
if ( resolve === undefined ) { return; }
|
||||||
|
req = undefined;
|
||||||
|
console.log(this.error);
|
||||||
|
db = null;
|
||||||
|
dbPromise = undefined;
|
||||||
|
resolve(null);
|
||||||
|
resolve = undefined;
|
||||||
|
};
|
||||||
|
setTimeout(( ) => {
|
||||||
|
if ( resolve === undefined ) { return; }
|
||||||
|
db = null;
|
||||||
|
dbPromise = undefined;
|
||||||
|
resolve(null);
|
||||||
|
resolve = undefined;
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
return dbPromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFromDb = async function(keys, keyvalStore, callback) {
|
||||||
|
if ( typeof callback !== 'function' ) { return; }
|
||||||
|
if ( keys.length === 0 ) { return callback(keyvalStore); }
|
||||||
|
const promises = [];
|
||||||
|
const gotOne = function() {
|
||||||
|
if ( typeof this.result !== 'object' ) { return; }
|
||||||
|
keyvalStore[this.result.key] = this.result.value;
|
||||||
|
if ( this.result.value instanceof Blob === false ) { return; }
|
||||||
|
promises.push(
|
||||||
|
µMatrix.lz4Codec.decode(
|
||||||
|
this.result.key,
|
||||||
|
this.result.value
|
||||||
|
).then(result => {
|
||||||
|
keyvalStore[result.key] = result.data;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const db = await getDb();
|
||||||
|
if ( !db ) { return callback(); }
|
||||||
|
const transaction = db.transaction(STORAGE_NAME, 'readonly');
|
||||||
|
transaction.oncomplete =
|
||||||
|
transaction.onerror =
|
||||||
|
transaction.onabort = ( ) => {
|
||||||
|
Promise.all(promises).then(( ) => {
|
||||||
|
callback(keyvalStore);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const table = transaction.objectStore(STORAGE_NAME);
|
||||||
|
for ( const key of keys ) {
|
||||||
|
const req = table.get(key);
|
||||||
|
req.onsuccess = gotOne;
|
||||||
|
req.onerror = noopfn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(reason) {
|
||||||
|
console.info(`cacheStorage.getFromDb() failed: ${reason}`);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const visitAllFromDb = async function(visitFn) {
|
||||||
|
const db = await getDb();
|
||||||
|
if ( !db ) { return visitFn(); }
|
||||||
|
const transaction = db.transaction(STORAGE_NAME, 'readonly');
|
||||||
|
transaction.oncomplete =
|
||||||
|
transaction.onerror =
|
||||||
|
transaction.onabort = ( ) => visitFn();
|
||||||
|
const table = transaction.objectStore(STORAGE_NAME);
|
||||||
|
const req = table.openCursor();
|
||||||
|
req.onsuccess = function(ev) {
|
||||||
|
let cursor = ev.target && ev.target.result;
|
||||||
|
if ( !cursor ) { return; }
|
||||||
|
let entry = cursor.value;
|
||||||
|
visitFn(entry);
|
||||||
|
cursor.continue();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllFromDb = function(callback) {
|
||||||
|
if ( typeof callback !== 'function' ) { return; }
|
||||||
|
const promises = [];
|
||||||
|
const keyvalStore = {};
|
||||||
|
visitAllFromDb(entry => {
|
||||||
|
if ( entry === undefined ) {
|
||||||
|
Promise.all(promises).then(( ) => {
|
||||||
|
callback(keyvalStore);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
keyvalStore[entry.key] = entry.value;
|
||||||
|
if ( entry.value instanceof Blob === false ) { return; }
|
||||||
|
promises.push(
|
||||||
|
µMatrix.lz4Codec.decode(
|
||||||
|
entry.key,
|
||||||
|
entry.value
|
||||||
|
).then(result => {
|
||||||
|
keyvalStore[result.key] = result.value;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}).catch(reason => {
|
||||||
|
console.info(`cacheStorage.getAllFromDb() failed: ${reason}`);
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/141
|
||||||
|
// Mind that IDBDatabase.transaction() and IDBObjectStore.put()
|
||||||
|
// can throw:
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put
|
||||||
|
|
||||||
|
const putToDb = async function(keyvalStore, callback) {
|
||||||
|
if ( typeof callback !== 'function' ) {
|
||||||
|
callback = noopfn;
|
||||||
|
}
|
||||||
|
const keys = Object.keys(keyvalStore);
|
||||||
|
if ( keys.length === 0 ) { return callback(); }
|
||||||
|
const promises = [ getDb() ];
|
||||||
|
const entries = [];
|
||||||
|
const dontCompress =
|
||||||
|
µMatrix.hiddenSettings.cacheStorageCompression !== true;
|
||||||
|
const handleEncodingResult = result => {
|
||||||
|
entries.push({ key: result.key, value: result.data });
|
||||||
|
};
|
||||||
|
for ( const key of keys ) {
|
||||||
|
const data = keyvalStore[key];
|
||||||
|
const isString = typeof data === 'string';
|
||||||
|
if ( isString === false || dontCompress ) {
|
||||||
|
entries.push({ key, value: data });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
promises.push(
|
||||||
|
µMatrix.lz4Codec.encode(key, data).then(handleEncodingResult)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const finish = ( ) => {
|
||||||
|
if ( callback === undefined ) { return; }
|
||||||
|
let cb = callback;
|
||||||
|
callback = undefined;
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
const db = results[0];
|
||||||
|
if ( !db ) { return callback(); }
|
||||||
|
const transaction = db.transaction(
|
||||||
|
STORAGE_NAME,
|
||||||
|
'readwrite'
|
||||||
|
);
|
||||||
|
transaction.oncomplete =
|
||||||
|
transaction.onerror =
|
||||||
|
transaction.onabort = finish;
|
||||||
|
const table = transaction.objectStore(STORAGE_NAME);
|
||||||
|
for ( const entry of entries ) {
|
||||||
|
table.put(entry);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFromDb = async function(input, callback) {
|
||||||
|
if ( typeof callback !== 'function' ) {
|
||||||
|
callback = noopfn;
|
||||||
|
}
|
||||||
|
const keys = Array.isArray(input) ? input.slice() : [ input ];
|
||||||
|
if ( keys.length === 0 ) { return callback(); }
|
||||||
|
const finish = ( ) => {
|
||||||
|
if ( callback === undefined ) { return; }
|
||||||
|
let cb = callback;
|
||||||
|
callback = undefined;
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const db = await getDb();
|
||||||
|
if ( !db ) { return callback(); }
|
||||||
|
const transaction = db.transaction(STORAGE_NAME, 'readwrite');
|
||||||
|
transaction.oncomplete =
|
||||||
|
transaction.onerror =
|
||||||
|
transaction.onabort = finish;
|
||||||
|
const table = transaction.objectStore(STORAGE_NAME);
|
||||||
|
for ( const key of keys ) {
|
||||||
|
table.delete(key);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearDb = async function(callback) {
|
||||||
|
if ( typeof callback !== 'function' ) {
|
||||||
|
callback = noopfn;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db = await getDb();
|
||||||
|
if ( !db ) { return callback(); }
|
||||||
|
const transaction = db.transaction(STORAGE_NAME, 'readwrite');
|
||||||
|
transaction.oncomplete =
|
||||||
|
transaction.onerror =
|
||||||
|
transaction.onabort = ( ) => {
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
transaction.objectStore(STORAGE_NAME).clear();
|
||||||
|
}
|
||||||
|
catch(reason) {
|
||||||
|
console.info(`cacheStorage.clearDb() failed: ${reason}`);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await getDb();
|
||||||
|
if ( !db ) { return false; }
|
||||||
|
|
||||||
|
api.name = 'indexedDB';
|
||||||
|
api.get = function get(keys) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if ( keys === null ) {
|
||||||
|
return getAllFromDb(bin => resolve(bin));
|
||||||
|
}
|
||||||
|
let toRead, output = {};
|
||||||
|
if ( typeof keys === 'string' ) {
|
||||||
|
toRead = [ keys ];
|
||||||
|
} else if ( Array.isArray(keys) ) {
|
||||||
|
toRead = keys;
|
||||||
|
} else /* if ( typeof keys === 'object' ) */ {
|
||||||
|
toRead = Object.keys(keys);
|
||||||
|
output = keys;
|
||||||
|
}
|
||||||
|
getFromDb(toRead, output, bin => resolve(bin));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
api.set = function set(keys) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
putToDb(keys, details => resolve(details));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
api.remove = function remove(keys) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
deleteFromDb(keys, ( ) => resolve());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
api.clear = function clear() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
clearDb(( ) => resolve());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
api.getBytesInUse = function getBytesInUse() {
|
||||||
|
return Promise.resolve(0);
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/328
|
||||||
|
// Delete cache-related entries from webext storage.
|
||||||
|
const clearWebext = async function() {
|
||||||
|
const bin = await webext.storage.local.get('assetCacheRegistry');
|
||||||
|
if (
|
||||||
|
bin instanceof Object === false ||
|
||||||
|
bin.assetCacheRegistry instanceof Object === false
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toRemove = [
|
||||||
|
'assetCacheRegistry',
|
||||||
|
'assetSourceRegistry',
|
||||||
|
'resourcesSelfie',
|
||||||
|
'selfie'
|
||||||
|
];
|
||||||
|
for ( const key in bin.assetCacheRegistry ) {
|
||||||
|
if ( bin.assetCacheRegistry.hasOwnProperty(key) ) {
|
||||||
|
toRemove.push('cache/' + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
webext.storage.local.remove(toRemove);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearIDB = function() {
|
||||||
|
try {
|
||||||
|
indexedDB.deleteDatabase(STORAGE_NAME);
|
||||||
|
} catch(ex) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return api;
|
||||||
|
}());
|
||||||
|
|
||||||
|
/******************************************************************************/
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -39,19 +39,15 @@ self.cloud = {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var widget = uDom.nodeFromId('cloudWidget');
|
const widget = uDom.nodeFromId('cloudWidget');
|
||||||
if ( widget === null ) {
|
if ( widget === null ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cloud.datakey = widget.getAttribute('data-cloud-entry') || '';
|
self.cloud.datakey = widget.getAttribute('data-cloud-entry') || '';
|
||||||
if ( self.cloud.datakey === '' ) {
|
if ( self.cloud.datakey === '' ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onCloudDataReceived = function(entry) {
|
const onCloudDataReceived = function(entry) {
|
||||||
if ( entry instanceof Object === false ) { return; }
|
if ( entry instanceof Object === false ) { return; }
|
||||||
|
|
||||||
self.cloud.data = entry.data;
|
self.cloud.data = entry.data;
|
||||||
|
@ -59,7 +55,7 @@ var onCloudDataReceived = function(entry) {
|
||||||
uDom.nodeFromId('cloudPull').removeAttribute('disabled');
|
uDom.nodeFromId('cloudPull').removeAttribute('disabled');
|
||||||
uDom.nodeFromId('cloudPullAndMerge').removeAttribute('disabled');
|
uDom.nodeFromId('cloudPullAndMerge').removeAttribute('disabled');
|
||||||
|
|
||||||
let timeOptions = {
|
const timeOptions = {
|
||||||
weekday: 'short',
|
weekday: 'short',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
|
@ -70,7 +66,7 @@ var onCloudDataReceived = function(entry) {
|
||||||
timeZoneName: 'short'
|
timeZoneName: 'short'
|
||||||
};
|
};
|
||||||
|
|
||||||
let time = new Date(entry.tstamp);
|
const time = new Date(entry.tstamp);
|
||||||
widget.querySelector('[data-i18n="cloudNoData"]').textContent =
|
widget.querySelector('[data-i18n="cloudNoData"]').textContent =
|
||||||
entry.source + '\n' +
|
entry.source + '\n' +
|
||||||
time.toLocaleString('fullwide', timeOptions);
|
time.toLocaleString('fullwide', timeOptions);
|
||||||
|
@ -78,37 +74,31 @@ var onCloudDataReceived = function(entry) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var fetchCloudData = function() {
|
const fetchCloudData = function() {
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('cloud-ui.js', {
|
||||||
'cloud-ui.js',
|
what: 'cloudPull',
|
||||||
{
|
datakey: self.cloud.datakey
|
||||||
what: 'cloudPull',
|
}).then(response => {
|
||||||
datakey: self.cloud.datakey
|
onCloudDataReceived(response);
|
||||||
},
|
});
|
||||||
onCloudDataReceived
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var pushData = function() {
|
const pushData = function() {
|
||||||
if ( typeof self.cloud.onPush !== 'function' ) {
|
if ( typeof self.cloud.onPush !== 'function' ) { return; }
|
||||||
return;
|
vAPI.messaging.send('cloud-ui.js', {
|
||||||
}
|
what: 'cloudPush',
|
||||||
vAPI.messaging.send(
|
datakey: self.cloud.datakey,
|
||||||
'cloud-ui.js',
|
data: self.cloud.onPush()
|
||||||
{
|
}).then(( ) => {
|
||||||
what: 'cloudPush',
|
fetchCloudData();
|
||||||
datakey: self.cloud.datakey,
|
});
|
||||||
data: self.cloud.onPush()
|
|
||||||
},
|
|
||||||
fetchCloudData
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var pullData = function(ev) {
|
const pullData = function(ev) {
|
||||||
if ( typeof self.cloud.onPull === 'function' ) {
|
if ( typeof self.cloud.onPull === 'function' ) {
|
||||||
self.cloud.onPull(self.cloud.data, ev.shiftKey);
|
self.cloud.onPull(self.cloud.data, ev.shiftKey);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +106,7 @@ var pullData = function(ev) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var pullAndMergeData = function() {
|
const pullAndMergeData = function() {
|
||||||
if ( typeof self.cloud.onPull === 'function' ) {
|
if ( typeof self.cloud.onPull === 'function' ) {
|
||||||
self.cloud.onPull(self.cloud.data, true);
|
self.cloud.onPull(self.cloud.data, true);
|
||||||
}
|
}
|
||||||
|
@ -124,8 +114,8 @@ var pullAndMergeData = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var openOptions = function() {
|
const openOptions = function() {
|
||||||
let input = uDom.nodeFromId('cloudDeviceName');
|
const input = uDom.nodeFromId('cloudDeviceName');
|
||||||
input.value = self.cloud.options.deviceName;
|
input.value = self.cloud.options.deviceName;
|
||||||
input.setAttribute('placeholder', self.cloud.options.defaultDeviceName);
|
input.setAttribute('placeholder', self.cloud.options.defaultDeviceName);
|
||||||
uDom.nodeFromId('cloudOptions').classList.add('show');
|
uDom.nodeFromId('cloudOptions').classList.add('show');
|
||||||
|
@ -133,50 +123,46 @@ var openOptions = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var closeOptions = function(ev) {
|
const closeOptions = function(ev) {
|
||||||
let root = uDom.nodeFromId('cloudOptions');
|
const root = uDom.nodeFromId('cloudOptions');
|
||||||
if ( ev.target !== root ) {
|
if ( ev.target !== root ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
root.classList.remove('show');
|
root.classList.remove('show');
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var submitOptions = function() {
|
const submitOptions = function() {
|
||||||
let onOptions = function(options) {
|
|
||||||
if ( typeof options !== 'object' || options === null ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.cloud.options = options;
|
|
||||||
};
|
|
||||||
|
|
||||||
vAPI.messaging.send('cloud-ui.js', {
|
vAPI.messaging.send('cloud-ui.js', {
|
||||||
what: 'cloudSetOptions',
|
what: 'cloudSetOptions',
|
||||||
options: {
|
options: {
|
||||||
deviceName: uDom.nodeFromId('cloudDeviceName').value
|
deviceName: uDom.nodeFromId('cloudDeviceName').value
|
||||||
}
|
}
|
||||||
}, onOptions);
|
}).then(options => {
|
||||||
|
if ( typeof options !== 'object' || options === null ) { return; }
|
||||||
|
self.cloud.options = options;
|
||||||
|
});
|
||||||
uDom.nodeFromId('cloudOptions').classList.remove('show');
|
uDom.nodeFromId('cloudOptions').classList.remove('show');
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onInitialize = function(options) {
|
vAPI.messaging.send('cloud-ui.js', {
|
||||||
|
what: 'cloudGetOptions'
|
||||||
|
}).then(options => {
|
||||||
if ( typeof options !== 'object' || options === null ) { return; }
|
if ( typeof options !== 'object' || options === null ) { return; }
|
||||||
|
|
||||||
if ( !options.enabled ) { return; }
|
if ( !options.enabled ) { return; }
|
||||||
self.cloud.options = options;
|
self.cloud.options = options;
|
||||||
|
|
||||||
let xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', 'cloud-ui.html', true);
|
xhr.open('GET', 'cloud-ui.html', true);
|
||||||
xhr.overrideMimeType('text/html;charset=utf-8');
|
xhr.overrideMimeType('text/html;charset=utf-8');
|
||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.onload = function() {
|
xhr.onload = function() {
|
||||||
this.onload = null;
|
this.onload = null;
|
||||||
let parser = new DOMParser(),
|
const parser = new DOMParser();
|
||||||
parsed = parser.parseFromString(this.responseText, 'text/html'),
|
const parsed = parser.parseFromString(this.responseText, 'text/html');
|
||||||
fromParent = parsed.body;
|
const fromParent = parsed.body;
|
||||||
while ( fromParent.firstElementChild !== null ) {
|
while ( fromParent.firstElementChild !== null ) {
|
||||||
widget.appendChild(
|
widget.appendChild(
|
||||||
document.adoptNode(fromParent.firstElementChild)
|
document.adoptNode(fromParent.firstElementChild)
|
||||||
|
@ -198,12 +184,8 @@ var onInitialize = function(options) {
|
||||||
fetchCloudData();
|
fetchCloudData();
|
||||||
};
|
};
|
||||||
xhr.send();
|
xhr.send();
|
||||||
};
|
});
|
||||||
|
|
||||||
vAPI.messaging.send('cloud-ui.js', { what: 'cloudGetOptions' }, onInitialize);
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://www.youtube.com/watch?v=aQFp67VoiDA
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
37
src/js/codemirror/mode/raw-settings.js
vendored
Normal file
37
src/js/codemirror/mode/raw-settings.js
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2019-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global CodeMirror */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
CodeMirror.defineMode("raw-settings", function() {
|
||||||
|
return {
|
||||||
|
token: function(stream) {
|
||||||
|
if ( stream.sol() ) {
|
||||||
|
stream.match(/\s*\S+/);
|
||||||
|
return 'keyword';
|
||||||
|
}
|
||||||
|
stream.skipToEnd();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
336
src/js/codemirror/search.js
Normal file
336
src/js/codemirror/search.js
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
// The following code is heavily based on the standard CodeMirror
|
||||||
|
// search addon found at: https://codemirror.net/addon/search/search.js
|
||||||
|
// I added/removed and modified code in order to get a closer match to a
|
||||||
|
// browser's built-in find-in-page feature which are just enough for
|
||||||
|
// uBlock Origin.
|
||||||
|
|
||||||
|
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
// Define search commands. Depends on dialog.js or another
|
||||||
|
// implementation of the openDialog method.
|
||||||
|
|
||||||
|
// Replace works a little oddly -- it will do the replace on the next
|
||||||
|
// Ctrl-G (or whatever is bound to findNext) press. You prevent a
|
||||||
|
// replace by making sure the match is no longer selected when hitting
|
||||||
|
// Ctrl-G.
|
||||||
|
|
||||||
|
/* globals define, require, CodeMirror */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports === "object" && typeof module === "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
|
||||||
|
else if (typeof define === "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
|
||||||
|
function searchOverlay(query, caseInsensitive) {
|
||||||
|
if (typeof query === "string")
|
||||||
|
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
|
||||||
|
else if (!query.global)
|
||||||
|
query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: function(stream) {
|
||||||
|
query.lastIndex = stream.pos;
|
||||||
|
var match = query.exec(stream.string);
|
||||||
|
if (match && match.index === stream.pos) {
|
||||||
|
stream.pos += match[0].length || 1;
|
||||||
|
return "searching";
|
||||||
|
} else if (match) {
|
||||||
|
stream.pos = match.index;
|
||||||
|
} else {
|
||||||
|
stream.skipToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchWidgetKeydownHandler(cm, ev) {
|
||||||
|
var keyName = CodeMirror.keyName(ev);
|
||||||
|
if ( !keyName ) { return; }
|
||||||
|
CodeMirror.lookupKey(
|
||||||
|
keyName,
|
||||||
|
cm.getOption('keyMap'),
|
||||||
|
function(command) {
|
||||||
|
if ( widgetCommandHandler(cm, command) ) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchWidgetInputHandler(cm) {
|
||||||
|
let state = getSearchState(cm);
|
||||||
|
if ( queryTextFromSearchWidget(cm) === state.queryText ) { return; }
|
||||||
|
if ( state.queryTimer !== null ) {
|
||||||
|
clearTimeout(state.queryTimer);
|
||||||
|
}
|
||||||
|
state.queryTimer = setTimeout(
|
||||||
|
() => {
|
||||||
|
state.queryTimer = null;
|
||||||
|
findCommit(cm, 0);
|
||||||
|
},
|
||||||
|
350
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchWidgetClickHandler(cm, ev) {
|
||||||
|
var tcl = ev.target.classList;
|
||||||
|
if ( tcl.contains('cm-search-widget-up') ) {
|
||||||
|
findNext(cm, -1);
|
||||||
|
} else if ( tcl.contains('cm-search-widget-down') ) {
|
||||||
|
findNext(cm, 1);
|
||||||
|
}
|
||||||
|
if ( ev.target.localName !== 'input' ) {
|
||||||
|
ev.preventDefault();
|
||||||
|
} else {
|
||||||
|
ev.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryTextFromSearchWidget(cm) {
|
||||||
|
return getSearchState(cm).widget.querySelector('input[type="search"]').value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryTextToSearchWidget(cm, q) {
|
||||||
|
var input = getSearchState(cm).widget.querySelector('input[type="search"]');
|
||||||
|
if ( typeof q === 'string' && q !== input.value ) {
|
||||||
|
input.value = q;
|
||||||
|
}
|
||||||
|
input.setSelectionRange(0, input.value.length);
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchState(cm) {
|
||||||
|
this.query = null;
|
||||||
|
this.overlay = null;
|
||||||
|
this.panel = null;
|
||||||
|
const widgetParent =
|
||||||
|
document.querySelector('.cm-search-widget-template').cloneNode(true);
|
||||||
|
this.widget = widgetParent.children[0];
|
||||||
|
this.widget.addEventListener('keydown', searchWidgetKeydownHandler.bind(null, cm));
|
||||||
|
this.widget.addEventListener('input', searchWidgetInputHandler.bind(null, cm));
|
||||||
|
this.widget.addEventListener('mousedown', searchWidgetClickHandler.bind(null, cm));
|
||||||
|
if ( typeof cm.addPanel === 'function' ) {
|
||||||
|
this.panel = cm.addPanel(this.widget);
|
||||||
|
}
|
||||||
|
this.queryText = '';
|
||||||
|
this.queryTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the search widget to behave as if the focus was on the
|
||||||
|
// CodeMirror editor.
|
||||||
|
|
||||||
|
const reSearchCommands = /^(?:find|findNext|findPrev|newlineAndIndent)$/;
|
||||||
|
|
||||||
|
function widgetCommandHandler(cm, command) {
|
||||||
|
if ( reSearchCommands.test(command) === false ) { return false; }
|
||||||
|
var queryText = queryTextFromSearchWidget(cm);
|
||||||
|
if ( command === 'find' ) {
|
||||||
|
queryTextToSearchWidget(cm);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( queryText.length !== 0 ) {
|
||||||
|
findNext(cm, command === 'findPrev' ? -1 : 1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchState(cm) {
|
||||||
|
return cm.state.search || (cm.state.search = new SearchState(cm));
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryCaseInsensitive(query) {
|
||||||
|
return typeof query === "string" && query === query.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchCursor(cm, query, pos) {
|
||||||
|
// Heuristic: if the query string is all lowercase, do a case insensitive search.
|
||||||
|
return cm.getSearchCursor(
|
||||||
|
query,
|
||||||
|
pos,
|
||||||
|
{ caseFold: queryCaseInsensitive(query), multiline: false }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/658
|
||||||
|
// Modified to backslash-escape ONLY widely-used control characters.
|
||||||
|
function parseString(string) {
|
||||||
|
return string.replace(/\\[nrt\\]/g, function(match) {
|
||||||
|
if (match === "\\n") return "\n";
|
||||||
|
if (match === "\\r") return "\r";
|
||||||
|
if (match === '\\t') return '\t';
|
||||||
|
if (match === '\\\\') return '\\';
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseQuery(query) {
|
||||||
|
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
|
||||||
|
if (isRE) {
|
||||||
|
try { query = new RegExp(isRE[1], isRE[2].indexOf("i") === -1 ? "" : "i"); }
|
||||||
|
catch(e) {} // Not a regular expression after all, do a string search
|
||||||
|
} else {
|
||||||
|
query = parseString(query);
|
||||||
|
}
|
||||||
|
if (typeof query === "string" ? query === "" : query.test(""))
|
||||||
|
query = /x^/;
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSearch(cm, state) {
|
||||||
|
state.query = parseQuery(state.queryText);
|
||||||
|
if ( state.overlay ) {
|
||||||
|
cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
|
||||||
|
}
|
||||||
|
state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
|
||||||
|
cm.addOverlay(state.overlay);
|
||||||
|
if ( cm.showMatchesOnScrollbar ) {
|
||||||
|
if ( state.annotate ) {
|
||||||
|
state.annotate.clear();
|
||||||
|
state.annotate = null;
|
||||||
|
}
|
||||||
|
state.annotate = cm.showMatchesOnScrollbar(
|
||||||
|
state.query,
|
||||||
|
queryCaseInsensitive(state.query),
|
||||||
|
{ multiline: false }
|
||||||
|
);
|
||||||
|
let count = state.annotate.matches.length;
|
||||||
|
state.widget
|
||||||
|
.querySelector('.cm-search-widget-count > span:nth-of-type(2)')
|
||||||
|
.textContent = count > 1000 ? '1000+' : count;
|
||||||
|
state.widget.setAttribute('data-query', state.queryText);
|
||||||
|
// Ensure the caret is visible
|
||||||
|
let input = state.widget.querySelector('.cm-search-widget-input > input');
|
||||||
|
input.selectionStart = input.selectionStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNext(cm, dir, callback) {
|
||||||
|
cm.operation(function() {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( !state.query ) { return; }
|
||||||
|
var cursor = getSearchCursor(
|
||||||
|
cm,
|
||||||
|
state.query,
|
||||||
|
dir <= 0 ? cm.getCursor('from') : cm.getCursor('to')
|
||||||
|
);
|
||||||
|
let previous = dir < 0;
|
||||||
|
if (!cursor.find(previous)) {
|
||||||
|
cursor = getSearchCursor(
|
||||||
|
cm,
|
||||||
|
state.query,
|
||||||
|
previous ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0)
|
||||||
|
);
|
||||||
|
if (!cursor.find(previous)) return;
|
||||||
|
}
|
||||||
|
cm.setSelection(cursor.from(), cursor.to());
|
||||||
|
cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20);
|
||||||
|
if (callback) callback(cursor.from(), cursor.to());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch(cm, hard) {
|
||||||
|
cm.operation(function() {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( state.query ) {
|
||||||
|
state.query = state.queryText = null;
|
||||||
|
}
|
||||||
|
if ( state.overlay ) {
|
||||||
|
cm.removeOverlay(state.overlay);
|
||||||
|
state.overlay = null;
|
||||||
|
}
|
||||||
|
if ( state.annotate ) {
|
||||||
|
state.annotate.clear();
|
||||||
|
state.annotate = null;
|
||||||
|
}
|
||||||
|
state.widget.removeAttribute('data-query');
|
||||||
|
if ( hard ) {
|
||||||
|
state.panel.clear();
|
||||||
|
state.panel = null;
|
||||||
|
state.widget = null;
|
||||||
|
cm.state.search = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCommit(cm, dir) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( state.queryTimer !== null ) {
|
||||||
|
clearTimeout(state.queryTimer);
|
||||||
|
state.queryTimer = null;
|
||||||
|
}
|
||||||
|
var queryText = queryTextFromSearchWidget(cm);
|
||||||
|
if ( queryText === state.queryText ) { return; }
|
||||||
|
state.queryText = queryText;
|
||||||
|
if ( state.queryText === '' ) {
|
||||||
|
clearSearch(cm);
|
||||||
|
} else {
|
||||||
|
cm.operation(function() {
|
||||||
|
startSearch(cm, state);
|
||||||
|
findNext(cm, dir);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findCommand(cm) {
|
||||||
|
var queryText = cm.getSelection() || undefined;
|
||||||
|
if ( !queryText ) {
|
||||||
|
var word = cm.findWordAt(cm.getCursor());
|
||||||
|
queryText = cm.getRange(word.anchor, word.head);
|
||||||
|
if ( /^\W|\W$/.test(queryText) ) {
|
||||||
|
queryText = undefined;
|
||||||
|
}
|
||||||
|
cm.setCursor(word.anchor);
|
||||||
|
}
|
||||||
|
queryTextToSearchWidget(cm, queryText);
|
||||||
|
findCommit(cm, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNextCommand(cm) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( state.query ) { return findNext(cm, 1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPrevCommand(cm) {
|
||||||
|
var state = getSearchState(cm);
|
||||||
|
if ( state.query ) { return findNext(cm, -1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const searchWidgetTemplate =
|
||||||
|
'<div class="cm-search-widget-template" style="display:none;">' +
|
||||||
|
'<div class="cm-search-widget">' +
|
||||||
|
'<span class="fa-icon fa-icon-ro">search</span> ' +
|
||||||
|
'<span class="cm-search-widget-input">' +
|
||||||
|
'<input type="search">' +
|
||||||
|
'<span class="cm-search-widget-count">' +
|
||||||
|
'<span><!-- future use --></span><span>0</span>' +
|
||||||
|
'</span>' +
|
||||||
|
'</span> ' +
|
||||||
|
'<span class="cm-search-widget-up cm-search-widget-button fa-icon">angle-up</span> ' +
|
||||||
|
'<span class="cm-search-widget-down cm-search-widget-button fa-icon fa-icon-vflipped">angle-up</span> ' +
|
||||||
|
'<a class="fa-icon sourceURL" href>external-link</a>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
const domParser = new DOMParser();
|
||||||
|
const doc = domParser.parseFromString(searchWidgetTemplate, 'text/html');
|
||||||
|
const widgetTemplate = document.adoptNode(doc.body.firstElementChild);
|
||||||
|
document.body.appendChild(widgetTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.commands.find = findCommand;
|
||||||
|
CodeMirror.commands.findNext = findNextCommand;
|
||||||
|
CodeMirror.commands.findPrev = findPrevCommand;
|
||||||
|
|
||||||
|
CodeMirror.defineInitHook(function(cm) {
|
||||||
|
getSearchState(cm);
|
||||||
|
});
|
||||||
|
});
|
34
src/js/console.js
Normal file
34
src/js/console.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uMatrix - a browser extension to block requests.
|
||||||
|
Copyright (C) 2019-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
self.log = (function() {
|
||||||
|
const noopFunc = function() {};
|
||||||
|
const info = function(s) { console.log(`[uMatrix] ${s}`); };
|
||||||
|
return {
|
||||||
|
get verbosity( ) { return; },
|
||||||
|
set verbosity(level) {
|
||||||
|
this.info = console.info = level === 'info' ? info : noopFunc;
|
||||||
|
},
|
||||||
|
info: noopFunc,
|
||||||
|
};
|
||||||
|
})();
|
|
@ -1,7 +1,7 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uMatrix - a browser extension to black/white list requests.
|
uMatrix - a browser extension to black/white list requests.
|
||||||
Copyright (C) 2017-2018 Raymond Hill
|
Copyright (C) 2017-present Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -26,15 +26,15 @@
|
||||||
|
|
||||||
// Injected into content pages
|
// Injected into content pages
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
|
||||||
if ( typeof vAPI !== 'object' ) { return; }
|
if ( typeof vAPI !== 'object' ) { return; }
|
||||||
|
|
||||||
vAPI.selfWorkerSrcReported = vAPI.selfWorkerSrcReported || false;
|
vAPI.selfWorkerSrcReported = vAPI.selfWorkerSrcReported || false;
|
||||||
|
|
||||||
var reGoodWorkerSrc = /(?:child|worker)-src[^;,]+?'none'/;
|
const reGoodWorkerSrc = /(?:child|worker)-src[^;,]+?'none'/;
|
||||||
|
|
||||||
var handler = function(ev) {
|
const handler = function(ev) {
|
||||||
if (
|
if (
|
||||||
ev.isTrusted !== true ||
|
ev.isTrusted !== true ||
|
||||||
ev.originalPolicy.includes('report-uri about:blank') === false
|
ev.originalPolicy.includes('report-uri about:blank') === false
|
||||||
|
@ -69,28 +69,21 @@
|
||||||
vAPI.selfWorkerSrcReported = true;
|
vAPI.selfWorkerSrcReported = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('contentscript.js', {
|
||||||
'contentscript.js',
|
what: 'securityPolicyViolation',
|
||||||
{
|
directive: 'worker-src',
|
||||||
what: 'securityPolicyViolation',
|
blockedURI: ev.blockedURI,
|
||||||
directive: 'worker-src',
|
documentURI: ev.documentURI,
|
||||||
blockedURI: ev.blockedURI,
|
blocked: ev.disposition === 'enforce',
|
||||||
documentURI: ev.documentURI,
|
});
|
||||||
blocked: ev.disposition === 'enforce'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener('securitypolicyviolation', ev => {
|
||||||
'securitypolicyviolation',
|
if ( !handler(ev) ) { return; }
|
||||||
function(ev) {
|
ev.stopPropagation();
|
||||||
if ( !handler(ev) ) { return; }
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
}, true);
|
||||||
ev.preventDefault();
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uMatrix - a browser extension to black/white list requests.
|
uMatrix - a browser extension to black/white list requests.
|
||||||
Copyright (C) 2014-2018 Raymond Hill
|
Copyright (C) 2014-present Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
// Injected into content pages
|
// Injected into content pages
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -42,9 +42,7 @@ if (
|
||||||
}
|
}
|
||||||
|
|
||||||
// This can also happen (for example if script injected into a `data:` URI doc)
|
// This can also happen (for example if script injected into a `data:` URI doc)
|
||||||
if ( !window.location ) {
|
if ( !window.location ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This can happen
|
// This can happen
|
||||||
if ( typeof vAPI !== 'object' ) {
|
if ( typeof vAPI !== 'object' ) {
|
||||||
|
@ -65,8 +63,8 @@ vAPI.contentscriptEndInjected = true;
|
||||||
|
|
||||||
// Executed only once.
|
// Executed only once.
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
var localStorageHandler = function(mustRemove) {
|
const localStorageHandler = function(mustRemove) {
|
||||||
if ( mustRemove ) {
|
if ( mustRemove ) {
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
window.sessionStorage.clear();
|
window.sessionStorage.clear();
|
||||||
|
@ -78,15 +76,17 @@ vAPI.contentscriptEndInjected = true;
|
||||||
// to site data is disabled.
|
// to site data is disabled.
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/215
|
// https://github.com/gorhill/httpswitchboard/issues/215
|
||||||
try {
|
try {
|
||||||
var hasLocalStorage =
|
const hasLocalStorage =
|
||||||
window.localStorage && window.localStorage.length !== 0;
|
window.localStorage && window.localStorage.length !== 0;
|
||||||
var hasSessionStorage =
|
const hasSessionStorage =
|
||||||
window.sessionStorage && window.sessionStorage.length !== 0;
|
window.sessionStorage && window.sessionStorage.length !== 0;
|
||||||
if ( hasLocalStorage || hasSessionStorage ) {
|
if ( hasLocalStorage || hasSessionStorage ) {
|
||||||
vAPI.messaging.send('contentscript.js', {
|
vAPI.messaging.send('contentscript.js', {
|
||||||
what: 'contentScriptHasLocalStorage',
|
what: 'contentScriptHasLocalStorage',
|
||||||
originURL: window.location.origin
|
originURL: window.location.origin,
|
||||||
}, localStorageHandler);
|
}).then(response => {
|
||||||
|
localStorageHandler(response);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: indexedDB
|
// TODO: indexedDB
|
||||||
|
@ -105,41 +105,41 @@ vAPI.contentscriptEndInjected = true;
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
}
|
}
|
||||||
})();
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://github.com/gorhill/uMatrix/issues/45
|
// https://github.com/gorhill/uMatrix/issues/45
|
||||||
|
|
||||||
var collapser = (function() {
|
const collapser = (( ) => {
|
||||||
var resquestIdGenerator = 1,
|
let resquestIdGenerator = 1,
|
||||||
processTimer,
|
processTimer,
|
||||||
toProcess = [],
|
toProcess = [],
|
||||||
toFilter = [],
|
toFilter = [],
|
||||||
toCollapse = new Map(),
|
|
||||||
cachedBlockedMap,
|
cachedBlockedMap,
|
||||||
cachedBlockedMapHash,
|
cachedBlockedMapHash,
|
||||||
cachedBlockedMapTimer,
|
cachedBlockedMapTimer;
|
||||||
reURLPlaceholder = /\{\{url\}\}/g;
|
const toCollapse = new Map();
|
||||||
var src1stProps = {
|
const reURLPlaceholder = /\{\{url\}\}/g;
|
||||||
|
const src1stProps = {
|
||||||
'embed': 'src',
|
'embed': 'src',
|
||||||
'frame': 'src',
|
'frame': 'src',
|
||||||
'iframe': 'src',
|
'iframe': 'src',
|
||||||
'img': 'src',
|
'img': 'src',
|
||||||
'object': 'data'
|
'object': 'data'
|
||||||
};
|
};
|
||||||
var src2ndProps = {
|
const src2ndProps = {
|
||||||
'img': 'srcset'
|
'img': 'srcset'
|
||||||
};
|
};
|
||||||
var tagToTypeMap = {
|
const tagToTypeMap = {
|
||||||
embed: 'media',
|
embed: 'media',
|
||||||
frame: 'frame',
|
frame: 'frame',
|
||||||
iframe: 'frame',
|
iframe: 'frame',
|
||||||
img: 'image',
|
img: 'image',
|
||||||
object: 'media'
|
object: 'media'
|
||||||
};
|
};
|
||||||
var cachedBlockedSetClear = function() {
|
const cachedBlockedSetClear = function() {
|
||||||
cachedBlockedMap =
|
cachedBlockedMap =
|
||||||
cachedBlockedMapHash =
|
cachedBlockedMapHash =
|
||||||
cachedBlockedMapTimer = undefined;
|
cachedBlockedMapTimer = undefined;
|
||||||
|
@ -147,13 +147,13 @@ var collapser = (function() {
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/174
|
// https://github.com/chrisaljoudi/uBlock/issues/174
|
||||||
// Do not remove fragment from src URL
|
// Do not remove fragment from src URL
|
||||||
var onProcessed = function(response) {
|
const onProcessed = function(response) {
|
||||||
if ( !response ) { // This happens if uBO is disabled or restarted.
|
if ( !response ) { // This happens if uBO is disabled or restarted.
|
||||||
toCollapse.clear();
|
toCollapse.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targets = toCollapse.get(response.id);
|
const targets = toCollapse.get(response.id);
|
||||||
if ( targets === undefined ) { return; }
|
if ( targets === undefined ) { return; }
|
||||||
toCollapse.delete(response.id);
|
toCollapse.delete(response.id);
|
||||||
if ( cachedBlockedMapHash !== response.hash ) {
|
if ( cachedBlockedMapHash !== response.hash ) {
|
||||||
|
@ -168,10 +168,10 @@ var collapser = (function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let placeholders = response.placeholders;
|
const placeholders = response.placeholders;
|
||||||
|
|
||||||
for ( let target of targets ) {
|
for ( const target of targets ) {
|
||||||
let tag = target.localName;
|
const tag = target.localName;
|
||||||
let prop = src1stProps[tag];
|
let prop = src1stProps[tag];
|
||||||
if ( prop === undefined ) { continue; }
|
if ( prop === undefined ) { continue; }
|
||||||
let src = target[prop];
|
let src = target[prop];
|
||||||
|
@ -181,7 +181,7 @@ var collapser = (function() {
|
||||||
src = target[prop];
|
src = target[prop];
|
||||||
if ( typeof src !== 'string' || src.length === 0 ) { continue; }
|
if ( typeof src !== 'string' || src.length === 0 ) { continue; }
|
||||||
}
|
}
|
||||||
let collapsed = cachedBlockedMap.get(tagToTypeMap[tag] + ' ' + src);
|
const collapsed = cachedBlockedMap.get(tagToTypeMap[tag] + ' ' + src);
|
||||||
if ( collapsed === undefined ) { continue; }
|
if ( collapsed === undefined ) { continue; }
|
||||||
if ( collapsed ) {
|
if ( collapsed ) {
|
||||||
target.style.setProperty('display', 'none', 'important');
|
target.style.setProperty('display', 'none', 'important');
|
||||||
|
@ -192,7 +192,7 @@ var collapser = (function() {
|
||||||
case 'frame':
|
case 'frame':
|
||||||
case 'iframe':
|
case 'iframe':
|
||||||
if ( placeholders.frame !== true ) { break; }
|
if ( placeholders.frame !== true ) { break; }
|
||||||
let docurl =
|
const docurl =
|
||||||
'data:text/html,' +
|
'data:text/html,' +
|
||||||
encodeURIComponent(
|
encodeURIComponent(
|
||||||
placeholders.frameDocument.replace(
|
placeholders.frameDocument.replace(
|
||||||
|
@ -244,22 +244,23 @@ var collapser = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var send = function() {
|
const send = function() {
|
||||||
processTimer = undefined;
|
processTimer = undefined;
|
||||||
toCollapse.set(resquestIdGenerator, toProcess);
|
toCollapse.set(resquestIdGenerator, toProcess);
|
||||||
var msg = {
|
vAPI.messaging.send('contentscript.js', {
|
||||||
what: 'lookupBlockedCollapsibles',
|
what: 'lookupBlockedCollapsibles',
|
||||||
id: resquestIdGenerator,
|
id: resquestIdGenerator,
|
||||||
toFilter: toFilter,
|
toFilter: toFilter,
|
||||||
hash: cachedBlockedMapHash
|
hash: cachedBlockedMapHash,
|
||||||
};
|
}).then(response => {
|
||||||
vAPI.messaging.send('contentscript.js', msg, onProcessed);
|
onProcessed(response);
|
||||||
|
});
|
||||||
toProcess = [];
|
toProcess = [];
|
||||||
toFilter = [];
|
toFilter = [];
|
||||||
resquestIdGenerator += 1;
|
resquestIdGenerator += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
var process = function(delay) {
|
const process = function(delay) {
|
||||||
if ( toProcess.length === 0 ) { return; }
|
if ( toProcess.length === 0 ) { return; }
|
||||||
if ( delay === 0 ) {
|
if ( delay === 0 ) {
|
||||||
if ( processTimer !== undefined ) {
|
if ( processTimer !== undefined ) {
|
||||||
|
@ -271,7 +272,7 @@ var collapser = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var add = function(target) {
|
const add = function(target) {
|
||||||
toProcess.push(target);
|
toProcess.push(target);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -282,20 +283,20 @@ var collapser = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var iframeSourceModified = function(mutations) {
|
const iframeSourceModified = function(mutations) {
|
||||||
var i = mutations.length;
|
let i = mutations.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
addIFrame(mutations[i].target, true);
|
addIFrame(mutations[i].target, true);
|
||||||
}
|
}
|
||||||
process();
|
process();
|
||||||
};
|
};
|
||||||
var iframeSourceObserver;
|
let iframeSourceObserver;
|
||||||
var iframeSourceObserverOptions = {
|
const iframeSourceObserverOptions = {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeFilter: [ 'src' ]
|
attributeFilter: [ 'src' ],
|
||||||
};
|
};
|
||||||
|
|
||||||
var addIFrame = function(iframe, dontObserve) {
|
const addIFrame = function(iframe, dontObserve) {
|
||||||
// https://github.com/gorhill/uBlock/issues/162
|
// https://github.com/gorhill/uBlock/issues/162
|
||||||
// Be prepared to deal with possible change of src attribute.
|
// Be prepared to deal with possible change of src attribute.
|
||||||
if ( dontObserve !== true ) {
|
if ( dontObserve !== true ) {
|
||||||
|
@ -304,25 +305,24 @@ var collapser = (function() {
|
||||||
}
|
}
|
||||||
iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
|
iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
|
||||||
}
|
}
|
||||||
var src = iframe.src;
|
const src = iframe.src;
|
||||||
if ( src === '' || typeof src !== 'string' ) { return; }
|
if ( src === '' || typeof src !== 'string' ) { return; }
|
||||||
if ( src.startsWith('http') === false ) { return; }
|
if ( src.startsWith('http') === false ) { return; }
|
||||||
toFilter.push({ type: 'frame', url: iframe.src });
|
toFilter.push({ type: 'frame', url: iframe.src });
|
||||||
add(iframe);
|
add(iframe);
|
||||||
};
|
};
|
||||||
|
|
||||||
var addIFrames = function(iframes) {
|
const addIFrames = function(iframes) {
|
||||||
var i = iframes.length;
|
let i = iframes.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
addIFrame(iframes[i]);
|
addIFrame(iframes[i]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var addNodeList = function(nodeList) {
|
const addNodeList = function(nodeList) {
|
||||||
var node,
|
let i = nodeList.length;
|
||||||
i = nodeList.length;
|
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
node = nodeList[i];
|
const node = nodeList[i];
|
||||||
if ( node.nodeType !== 1 ) { continue; }
|
if ( node.nodeType !== 1 ) { continue; }
|
||||||
if ( node.localName === 'iframe' || node.localName === 'frame' ) {
|
if ( node.localName === 'iframe' || node.localName === 'frame' ) {
|
||||||
addIFrame(node);
|
addIFrame(node);
|
||||||
|
@ -333,7 +333,7 @@ var collapser = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onResourceFailed = function(ev) {
|
const onResourceFailed = function(ev) {
|
||||||
if ( tagToTypeMap[ev.target.localName] !== undefined ) {
|
if ( tagToTypeMap[ev.target.localName] !== undefined ) {
|
||||||
add(ev.target);
|
add(ev.target);
|
||||||
process();
|
process();
|
||||||
|
@ -354,10 +354,10 @@ var collapser = (function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
addMany: addMany,
|
addMany,
|
||||||
addIFrames: addIFrames,
|
addIFrames,
|
||||||
addNodeList: addNodeList,
|
addNodeList,
|
||||||
process: process
|
process,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -368,16 +368,16 @@ var collapser = (function() {
|
||||||
|
|
||||||
// Added node lists will be cumulated here before being processed
|
// Added node lists will be cumulated here before being processed
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
// This fixes http://acid3.acidtests.org/
|
// This fixes http://acid3.acidtests.org/
|
||||||
if ( !document.body ) { return; }
|
if ( !document.body ) { return; }
|
||||||
|
|
||||||
var addedNodeLists = [];
|
let addedNodeLists = [];
|
||||||
var addedNodeListsTimer;
|
let addedNodeListsTimer;
|
||||||
|
|
||||||
var treeMutationObservedHandler = function() {
|
const treeMutationObservedHandler = function() {
|
||||||
addedNodeListsTimer = undefined;
|
addedNodeListsTimer = undefined;
|
||||||
var i = addedNodeLists.length;
|
let i = addedNodeLists.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
collapser.addNodeList(addedNodeLists[i]);
|
collapser.addNodeList(addedNodeLists[i]);
|
||||||
}
|
}
|
||||||
|
@ -387,11 +387,10 @@ var collapser = (function() {
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/205
|
// https://github.com/gorhill/uBlock/issues/205
|
||||||
// Do not handle added node directly from within mutation observer.
|
// Do not handle added node directly from within mutation observer.
|
||||||
var treeMutationObservedHandlerAsync = function(mutations) {
|
const treeMutationObservedHandlerAsync = function(mutations) {
|
||||||
var iMutation = mutations.length,
|
let iMutation = mutations.length;
|
||||||
nodeList;
|
|
||||||
while ( iMutation-- ) {
|
while ( iMutation-- ) {
|
||||||
nodeList = mutations[iMutation].addedNodes;
|
const nodeList = mutations[iMutation].addedNodes;
|
||||||
if ( nodeList.length !== 0 ) {
|
if ( nodeList.length !== 0 ) {
|
||||||
addedNodeLists.push(nodeList);
|
addedNodeLists.push(nodeList);
|
||||||
}
|
}
|
||||||
|
@ -402,7 +401,7 @@ var collapser = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/176
|
// https://github.com/gorhill/httpswitchboard/issues/176
|
||||||
var treeObserver = new MutationObserver(treeMutationObservedHandlerAsync);
|
let treeObserver = new MutationObserver(treeMutationObservedHandlerAsync);
|
||||||
treeObserver.observe(document.body, {
|
treeObserver.observe(document.body, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true
|
subtree: true
|
||||||
|
@ -437,7 +436,7 @@ var collapser = (function() {
|
||||||
// https://github.com/gorhill/uMatrix/issues/924
|
// https://github.com/gorhill/uMatrix/issues/924
|
||||||
// Report inline styles.
|
// Report inline styles.
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
if (
|
if (
|
||||||
document.querySelector('script:not([src])') !== null ||
|
document.querySelector('script:not([src])') !== null ||
|
||||||
document.querySelector('a[href^="javascript:"]') !== null ||
|
document.querySelector('a[href^="javascript:"]') !== null ||
|
||||||
|
@ -446,7 +445,7 @@ var collapser = (function() {
|
||||||
vAPI.messaging.send('contentscript.js', {
|
vAPI.messaging.send('contentscript.js', {
|
||||||
what: 'securityPolicyViolation',
|
what: 'securityPolicyViolation',
|
||||||
directive: 'script-src',
|
directive: 'script-src',
|
||||||
documentURI: window.location.href
|
documentURI: window.location.href,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,14 +453,14 @@ var collapser = (function() {
|
||||||
vAPI.messaging.send('contentscript.js', {
|
vAPI.messaging.send('contentscript.js', {
|
||||||
what: 'securityPolicyViolation',
|
what: 'securityPolicyViolation',
|
||||||
directive: 'style-src',
|
directive: 'style-src',
|
||||||
documentURI: window.location.href
|
documentURI: window.location.href,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
collapser.addMany(document.querySelectorAll('img'));
|
collapser.addMany(document.querySelectorAll('img'));
|
||||||
collapser.addIFrames(document.querySelectorAll('iframe, frame'));
|
collapser.addIFrames(document.querySelectorAll('iframe, frame'));
|
||||||
collapser.process();
|
collapser.process();
|
||||||
})();
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -471,23 +470,23 @@ var collapser = (function() {
|
||||||
// https://github.com/gorhill/uMatrix/issues/232
|
// https://github.com/gorhill/uMatrix/issues/232
|
||||||
// Force `display` property, Firefox is still affected by the issue.
|
// Force `display` property, Firefox is still affected by the issue.
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
var noscripts = document.querySelectorAll('noscript');
|
const noscripts = document.querySelectorAll('noscript');
|
||||||
if ( noscripts.length === 0 ) { return; }
|
if ( noscripts.length === 0 ) { return; }
|
||||||
|
|
||||||
var redirectTimer,
|
const reMetaContent = /^\s*(\d+)\s*;\s*url=(['"]?)([^'"]+)\2/i;
|
||||||
reMetaContent = /^\s*(\d+)\s*;\s*url=(['"]?)([^'"]+)\2/i,
|
const reSafeURL = /^https?:\/\//;
|
||||||
reSafeURL = /^https?:\/\//;
|
let redirectTimer;
|
||||||
|
|
||||||
var autoRefresh = function(root) {
|
const autoRefresh = function(root) {
|
||||||
var meta = root.querySelector('meta[http-equiv="refresh"][content]');
|
const meta = root.querySelector('meta[http-equiv="refresh"][content]');
|
||||||
if ( meta === null ) { return; }
|
if ( meta === null ) { return; }
|
||||||
var match = reMetaContent.exec(meta.getAttribute('content'));
|
const match = reMetaContent.exec(meta.getAttribute('content'));
|
||||||
if ( match === null || match[3].trim() === '' ) { return; }
|
if ( match === null || match[3].trim() === '' ) { return; }
|
||||||
var url = new URL(match[3], document.baseURI);
|
const url = new URL(match[3], document.baseURI);
|
||||||
if ( reSafeURL.test(url.href) === false ) { return; }
|
if ( reSafeURL.test(url.href) === false ) { return; }
|
||||||
redirectTimer = setTimeout(
|
redirectTimer = setTimeout(
|
||||||
function() {
|
( ) => {
|
||||||
location.assign(url.href);
|
location.assign(url.href);
|
||||||
},
|
},
|
||||||
parseInt(match[1], 10) * 1000 + 1
|
parseInt(match[1], 10) * 1000 + 1
|
||||||
|
@ -495,29 +494,28 @@ var collapser = (function() {
|
||||||
meta.parentNode.removeChild(meta);
|
meta.parentNode.removeChild(meta);
|
||||||
};
|
};
|
||||||
|
|
||||||
var morphNoscript = function(from) {
|
const morphNoscript = function(from) {
|
||||||
if ( /^application\/(?:xhtml\+)?xml/.test(document.contentType) ) {
|
if ( /^application\/(?:xhtml\+)?xml/.test(document.contentType) ) {
|
||||||
var to = document.createElement('span');
|
const to = document.createElement('span');
|
||||||
while ( from.firstChild !== null ) {
|
while ( from.firstChild !== null ) {
|
||||||
to.appendChild(from.firstChild);
|
to.appendChild(from.firstChild);
|
||||||
}
|
}
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
var parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
var doc = parser.parseFromString(
|
const doc = parser.parseFromString(
|
||||||
'<span>' + from.textContent + '</span>',
|
'<span>' + from.textContent + '</span>',
|
||||||
'text/html'
|
'text/html'
|
||||||
);
|
);
|
||||||
return document.adoptNode(doc.querySelector('span'));
|
return document.adoptNode(doc.querySelector('span'));
|
||||||
};
|
};
|
||||||
|
|
||||||
var renderNoscriptTags = function(response) {
|
const renderNoscriptTags = function(response) {
|
||||||
if ( response !== true ) { return; }
|
if ( response !== true ) { return; }
|
||||||
var parent, span;
|
|
||||||
for ( var noscript of noscripts ) {
|
for ( var noscript of noscripts ) {
|
||||||
parent = noscript.parentNode;
|
const parent = noscript.parentNode;
|
||||||
if ( parent === null ) { continue; }
|
if ( parent === null ) { continue; }
|
||||||
span = morphNoscript(noscript);
|
const span = morphNoscript(noscript);
|
||||||
span.style.setProperty('display', 'inline', 'important');
|
span.style.setProperty('display', 'inline', 'important');
|
||||||
if ( redirectTimer === undefined ) {
|
if ( redirectTimer === undefined ) {
|
||||||
autoRefresh(span);
|
autoRefresh(span);
|
||||||
|
@ -526,25 +524,23 @@ var collapser = (function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('contentscript.js', {
|
||||||
'contentscript.js',
|
what: 'mustRenderNoscriptTags?',
|
||||||
{ what: 'mustRenderNoscriptTags?' },
|
}).then(response => {
|
||||||
renderNoscriptTags
|
renderNoscriptTags(response);
|
||||||
);
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('contentscript.js', {
|
||||||
'contentscript.js',
|
what: 'shutdown?',
|
||||||
{ what: 'shutdown?' },
|
}).then(response => {
|
||||||
function(response) {
|
if ( response === true ) {
|
||||||
if ( response === true ) {
|
vAPI.shutdown.exec();
|
||||||
vAPI.shutdown.exec();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
|
@ -33,58 +33,59 @@
|
||||||
// Use cached-context approach rather than object-based approach, as details
|
// Use cached-context approach rather than object-based approach, as details
|
||||||
// of the implementation do not need to be visible
|
// of the implementation do not need to be visible
|
||||||
|
|
||||||
µMatrix.cookieHunter = (function() {
|
µMatrix.cookieHunter = (( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var µm = µMatrix;
|
const µm = µMatrix;
|
||||||
|
|
||||||
var recordPageCookiesQueue = new Map();
|
const recordPageCookiesQueue = new Map();
|
||||||
var removeCookieQueue = new Set();
|
const removeCookieQueue = new Set();
|
||||||
var cookieDict = new Map();
|
const cookieDict = new Map();
|
||||||
var cookieEntryJunkyard = [];
|
const cookieEntryJunkyard = [];
|
||||||
var processRemoveQueuePeriod = 2 * 60 * 1000;
|
const processRemoveQueuePeriod = 2 * 60 * 1000;
|
||||||
var processCleanPeriod = 10 * 60 * 1000;
|
const processCleanPeriod = 10 * 60 * 1000;
|
||||||
var processPageRecordQueueTimer = null;
|
let processPageRecordQueueTimer = null;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var CookieEntry = function(cookie) {
|
const CookieEntry = class {
|
||||||
this.usedOn = new Set();
|
constructor(cookie) {
|
||||||
this.init(cookie);
|
this.usedOn = new Set();
|
||||||
};
|
this.init(cookie);
|
||||||
|
}
|
||||||
|
|
||||||
CookieEntry.prototype.init = function(cookie) {
|
init(cookie) {
|
||||||
this.secure = cookie.secure;
|
this.secure = cookie.secure;
|
||||||
this.session = cookie.session;
|
this.session = cookie.session;
|
||||||
this.anySubdomain = cookie.domain.charAt(0) === '.';
|
this.anySubdomain = cookie.domain.charAt(0) === '.';
|
||||||
this.hostname = this.anySubdomain ? cookie.domain.slice(1) : cookie.domain;
|
this.hostname = this.anySubdomain ? cookie.domain.slice(1) : cookie.domain;
|
||||||
this.domain = µm.URI.domainFromHostname(this.hostname) || this.hostname;
|
this.domain = µm.URI.domainFromHostname(this.hostname) || this.hostname;
|
||||||
this.path = cookie.path;
|
this.path = cookie.path;
|
||||||
this.name = cookie.name;
|
this.name = cookie.name;
|
||||||
this.value = cookie.value;
|
this.value = cookie.value;
|
||||||
this.tstamp = Date.now();
|
this.tstamp = Date.now();
|
||||||
this.usedOn.clear();
|
this.usedOn.clear();
|
||||||
return this;
|
return this;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Release anything which may consume too much memory
|
// Reset any property which indirectly consumes memory
|
||||||
|
dispose() {
|
||||||
CookieEntry.prototype.dispose = function() {
|
this.hostname = '';
|
||||||
this.hostname = '';
|
this.domain = '';
|
||||||
this.domain = '';
|
this.path = '';
|
||||||
this.path = '';
|
this.name = '';
|
||||||
this.name = '';
|
this.value = '';
|
||||||
this.value = '';
|
this.usedOn.clear();
|
||||||
this.usedOn.clear();
|
return this;
|
||||||
return this;
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var addCookieToDict = function(cookie) {
|
const addCookieToDict = function(cookie) {
|
||||||
var cookieKey = cookieKeyFromCookie(cookie),
|
const cookieKey = cookieKeyFromCookie(cookie);
|
||||||
cookieEntry = cookieDict.get(cookieKey);
|
let cookieEntry = cookieDict.get(cookieKey);
|
||||||
if ( cookieEntry === undefined ) {
|
if ( cookieEntry === undefined ) {
|
||||||
cookieEntry = cookieEntryJunkyard.pop();
|
cookieEntry = cookieEntryJunkyard.pop();
|
||||||
if ( cookieEntry ) {
|
if ( cookieEntry ) {
|
||||||
|
@ -99,17 +100,8 @@ var addCookieToDict = function(cookie) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var addCookiesToDict = function(cookies) {
|
const removeCookieFromDict = function(cookieKey) {
|
||||||
var i = cookies.length;
|
const cookieEntry = cookieDict.get(cookieKey);
|
||||||
while ( i-- ) {
|
|
||||||
addCookieToDict(cookies[i]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var removeCookieFromDict = function(cookieKey) {
|
|
||||||
var cookieEntry = cookieDict.get(cookieKey);
|
|
||||||
if ( cookieEntry === undefined ) { return false; }
|
if ( cookieEntry === undefined ) { return false; }
|
||||||
cookieDict.delete(cookieKey);
|
cookieDict.delete(cookieKey);
|
||||||
if ( cookieEntryJunkyard.length < 25 ) {
|
if ( cookieEntryJunkyard.length < 25 ) {
|
||||||
|
@ -120,7 +112,7 @@ var removeCookieFromDict = function(cookieKey) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var cookieKeyBuilder = [
|
const cookieKeyBuilder = [
|
||||||
'', // 0 = scheme
|
'', // 0 = scheme
|
||||||
'://',
|
'://',
|
||||||
'', // 2 = domain
|
'', // 2 = domain
|
||||||
|
@ -132,8 +124,8 @@ var cookieKeyBuilder = [
|
||||||
'}'
|
'}'
|
||||||
];
|
];
|
||||||
|
|
||||||
var cookieKeyFromCookie = function(cookie) {
|
const cookieKeyFromCookie = function(cookie) {
|
||||||
var cb = cookieKeyBuilder;
|
const cb = cookieKeyBuilder;
|
||||||
cb[0] = cookie.secure ? 'https' : 'http';
|
cb[0] = cookie.secure ? 'https' : 'http';
|
||||||
cb[2] = cookie.domain.charAt(0) === '.' ? cookie.domain.slice(1) : cookie.domain;
|
cb[2] = cookie.domain.charAt(0) === '.' ? cookie.domain.slice(1) : cookie.domain;
|
||||||
cb[3] = cookie.path;
|
cb[3] = cookie.path;
|
||||||
|
@ -142,9 +134,9 @@ var cookieKeyFromCookie = function(cookie) {
|
||||||
return cb.join('');
|
return cb.join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
var cookieKeyFromCookieURL = function(url, type, name) {
|
const cookieKeyFromCookieURL = function(url, type, name) {
|
||||||
var µmuri = µm.URI.set(url);
|
const µmuri = µm.URI.set(url);
|
||||||
var cb = cookieKeyBuilder;
|
const cb = cookieKeyBuilder;
|
||||||
cb[0] = µmuri.scheme;
|
cb[0] = µmuri.scheme;
|
||||||
cb[2] = µmuri.hostname;
|
cb[2] = µmuri.hostname;
|
||||||
cb[3] = µmuri.path;
|
cb[3] = µmuri.path;
|
||||||
|
@ -155,7 +147,7 @@ var cookieKeyFromCookieURL = function(url, type, name) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var cookieURLFromCookieEntry = function(entry) {
|
const cookieURLFromCookieEntry = function(entry) {
|
||||||
if ( !entry ) {
|
if ( !entry ) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -164,13 +156,11 @@ var cookieURLFromCookieEntry = function(entry) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var cookieMatchDomains = function(cookieKey, allHostnamesString) {
|
const cookieMatchDomains = function(cookieKey, allHostnamesString) {
|
||||||
var cookieEntry = cookieDict.get(cookieKey);
|
const cookieEntry = cookieDict.get(cookieKey);
|
||||||
if ( cookieEntry === undefined ) { return false; }
|
if ( cookieEntry === undefined ) { return false; }
|
||||||
if ( allHostnamesString.indexOf(' ' + cookieEntry.hostname + ' ') < 0 ) {
|
if ( allHostnamesString.indexOf(' ' + cookieEntry.hostname + ' ') < 0 ) {
|
||||||
if ( !cookieEntry.anySubdomain ) {
|
if ( !cookieEntry.anySubdomain ) { return false; }
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ( allHostnamesString.indexOf('.' + cookieEntry.hostname + ' ') < 0 ) {
|
if ( allHostnamesString.indexOf('.' + cookieEntry.hostname + ' ') < 0 ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -182,7 +172,7 @@ var cookieMatchDomains = function(cookieKey, allHostnamesString) {
|
||||||
|
|
||||||
// Look for cookies to record for a specific web page
|
// Look for cookies to record for a specific web page
|
||||||
|
|
||||||
var recordPageCookiesAsync = function(pageStore) {
|
const recordPageCookiesAsync = function(pageStore) {
|
||||||
// Store the page stats objects so that it doesn't go away
|
// Store the page stats objects so that it doesn't go away
|
||||||
// before we handle the job.
|
// before we handle the job.
|
||||||
// rhill 2013-10-19: pageStore could be nil, for example, this can
|
// rhill 2013-10-19: pageStore could be nil, for example, this can
|
||||||
|
@ -195,17 +185,17 @@ var recordPageCookiesAsync = function(pageStore) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var recordPageCookie = (function() {
|
const recordPageCookie = (( ) => {
|
||||||
let queue = new Map();
|
const queue = new Map();
|
||||||
|
const cookieLogEntryBuilder = [ '', '{', '', '-cookie:', '', '}' ];
|
||||||
let queueTimer;
|
let queueTimer;
|
||||||
let cookieLogEntryBuilder = [ '', '{', '', '-cookie:', '', '}' ];
|
|
||||||
|
|
||||||
let process = function() {
|
const process = function() {
|
||||||
queueTimer = undefined;
|
queueTimer = undefined;
|
||||||
for ( let qentry of queue ) {
|
for ( const qentry of queue ) {
|
||||||
let pageStore = qentry[0];
|
const pageStore = qentry[0];
|
||||||
if ( pageStore.tabId === '' ) { continue; }
|
if ( pageStore.tabId === '' ) { continue; }
|
||||||
for ( let cookieKey of qentry[1] ) {
|
for ( const cookieKey of qentry[1] ) {
|
||||||
let cookieEntry = cookieDict.get(cookieKey);
|
let cookieEntry = cookieDict.get(cookieKey);
|
||||||
if ( cookieEntry === undefined ) { continue; }
|
if ( cookieEntry === undefined ) { continue; }
|
||||||
let blocked = µm.mustBlock(
|
let blocked = µm.mustBlock(
|
||||||
|
@ -221,22 +211,24 @@ var recordPageCookie = (function() {
|
||||||
cookieEntry.session ? 'session' : 'persistent';
|
cookieEntry.session ? 'session' : 'persistent';
|
||||||
cookieLogEntryBuilder[4] =
|
cookieLogEntryBuilder[4] =
|
||||||
encodeURIComponent(cookieEntry.name);
|
encodeURIComponent(cookieEntry.name);
|
||||||
let cookieURL = cookieLogEntryBuilder.join('');
|
const cookieURL = cookieLogEntryBuilder.join('');
|
||||||
pageStore.recordRequest('cookie', cookieURL, blocked);
|
pageStore.recordRequest('cookie', cookieURL, blocked);
|
||||||
µm.logger.writeOne({
|
if ( µm.logger.enabled ) {
|
||||||
tabId: pageStore.tabId,
|
µm.filteringContext
|
||||||
srcHn: pageStore.pageHostname,
|
.duplicate()
|
||||||
desHn: cookieEntry.hostname,
|
.fromTabId(pageStore.tabId)
|
||||||
desURL: cookieURL,
|
.setType('cookie')
|
||||||
type: 'cookie',
|
.setURL(cookieURL)
|
||||||
blocked
|
.setFilter(blocked)
|
||||||
});
|
.setRealm('network')
|
||||||
|
.toLogger();
|
||||||
|
}
|
||||||
cookieEntry.usedOn.add(pageStore.pageHostname);
|
cookieEntry.usedOn.add(pageStore.pageHostname);
|
||||||
if ( !blocked ) { continue; }
|
if ( !blocked ) { continue; }
|
||||||
if ( µm.userSettings.deleteCookies ) {
|
if ( µm.userSettings.deleteCookies ) {
|
||||||
removeCookieAsync(cookieKey);
|
removeCookieAsync(cookieKey);
|
||||||
}
|
}
|
||||||
µm.updateBadgeAsync(pageStore.tabId);
|
µm.updateToolbarIcon(pageStore.tabId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
queue.clear();
|
queue.clear();
|
||||||
|
@ -260,27 +252,29 @@ var recordPageCookie = (function() {
|
||||||
|
|
||||||
// Candidate for removal
|
// Candidate for removal
|
||||||
|
|
||||||
var removeCookieAsync = function(cookieKey) {
|
const removeCookieAsync = function(cookieKey) {
|
||||||
removeCookieQueue.add(cookieKey);
|
removeCookieQueue.add(cookieKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var chromeCookieRemove = function(cookieEntry, name) {
|
const browserCookieRemove = function(cookieEntry, name) {
|
||||||
var url = cookieURLFromCookieEntry(cookieEntry);
|
const url = cookieURLFromCookieEntry(cookieEntry);
|
||||||
if ( url === '' ) { return; }
|
if ( url === '' ) { return; }
|
||||||
|
|
||||||
var sessionCookieKey = cookieKeyFromCookieURL(url, 'session', name);
|
const sessionCookieKey = cookieKeyFromCookieURL(url, 'session', name);
|
||||||
var persistCookieKey = cookieKeyFromCookieURL(url, 'persistent', name);
|
const persistCookieKey = cookieKeyFromCookieURL(url, 'persistent', name);
|
||||||
var callback = function(details) {
|
|
||||||
var success = !!details;
|
vAPI.cookies.remove({ url, name }).then(details => {
|
||||||
var template = success ? i18nCookieDeleteSuccess : i18nCookieDeleteFailure;
|
const success = !!details;
|
||||||
|
const template = success ? i18nCookieDeleteSuccess : i18nCookieDeleteFailure;
|
||||||
if ( removeCookieFromDict(sessionCookieKey) ) {
|
if ( removeCookieFromDict(sessionCookieKey) ) {
|
||||||
if ( success ) {
|
if ( success ) {
|
||||||
µm.cookieRemovedCounter += 1;
|
µm.cookieRemovedCounter += 1;
|
||||||
}
|
}
|
||||||
µm.logger.writeOne({
|
µm.logger.writeOne({
|
||||||
info: template.replace('{{value}}', sessionCookieKey)
|
realm: 'message',
|
||||||
|
text: template.replace('{{value}}', sessionCookieKey)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if ( removeCookieFromDict(persistCookieKey) ) {
|
if ( removeCookieFromDict(persistCookieKey) ) {
|
||||||
|
@ -288,23 +282,22 @@ var chromeCookieRemove = function(cookieEntry, name) {
|
||||||
µm.cookieRemovedCounter += 1;
|
µm.cookieRemovedCounter += 1;
|
||||||
}
|
}
|
||||||
µm.logger.writeOne({
|
µm.logger.writeOne({
|
||||||
info: template.replace('{{value}}', persistCookieKey)
|
realm: 'message',
|
||||||
|
text: template.replace('{{value}}', persistCookieKey)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
vAPI.cookies.remove({ url: url, name: name }, callback);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var i18nCookieDeleteSuccess = vAPI.i18n('loggerEntryCookieDeleted');
|
const i18nCookieDeleteSuccess = vAPI.i18n('loggerEntryCookieDeleted');
|
||||||
var i18nCookieDeleteFailure = vAPI.i18n('loggerEntryDeleteCookieError');
|
const i18nCookieDeleteFailure = vAPI.i18n('loggerEntryDeleteCookieError');
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var processPageRecordQueue = function() {
|
const processPageRecordQueue = function() {
|
||||||
processPageRecordQueueTimer = null;
|
processPageRecordQueueTimer = null;
|
||||||
|
|
||||||
for ( var pageStore of recordPageCookiesQueue.values() ) {
|
for ( const pageStore of recordPageCookiesQueue.values() ) {
|
||||||
findAndRecordPageCookies(pageStore);
|
findAndRecordPageCookies(pageStore);
|
||||||
}
|
}
|
||||||
recordPageCookiesQueue.clear();
|
recordPageCookiesQueue.clear();
|
||||||
|
@ -314,39 +307,36 @@ var processPageRecordQueue = function() {
|
||||||
|
|
||||||
// Effectively remove cookies.
|
// Effectively remove cookies.
|
||||||
|
|
||||||
var processRemoveQueue = function() {
|
const processRemoveQueue = function() {
|
||||||
var userSettings = µm.userSettings;
|
const userSettings = µm.userSettings;
|
||||||
var deleteCookies = userSettings.deleteCookies;
|
const deleteCookies = userSettings.deleteCookies;
|
||||||
|
|
||||||
// Session cookies which timestamp is *after* tstampObsolete will
|
// Session cookies which timestamp is *after* tstampObsolete will
|
||||||
// be left untouched
|
// be left untouched
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/257
|
// https://github.com/gorhill/httpswitchboard/issues/257
|
||||||
var tstampObsolete = userSettings.deleteUnusedSessionCookies ?
|
const tstampObsolete = userSettings.deleteUnusedSessionCookies ?
|
||||||
Date.now() - userSettings.deleteUnusedSessionCookiesAfter * 60 * 1000 :
|
Date.now() - userSettings.deleteUnusedSessionCookiesAfter * 60 * 1000 :
|
||||||
0;
|
0;
|
||||||
|
|
||||||
var srcHostnames;
|
let srcHostnames;
|
||||||
var cookieEntry;
|
|
||||||
|
|
||||||
for ( var cookieKey of removeCookieQueue ) {
|
for ( const cookieKey of removeCookieQueue ) {
|
||||||
// rhill 2014-05-12: Apparently this can happen. I have to
|
// rhill 2014-05-12: Apparently this can happen. I have to
|
||||||
// investigate how (A session cookie has same name as a
|
// investigate how (A session cookie has same name as a
|
||||||
// persistent cookie?)
|
// persistent cookie?)
|
||||||
cookieEntry = cookieDict.get(cookieKey);
|
const cookieEntry = cookieDict.get(cookieKey);
|
||||||
if ( cookieEntry === undefined ) { continue; }
|
if ( cookieEntry === undefined ) { continue; }
|
||||||
|
|
||||||
// Delete obsolete session cookies: enabled.
|
// Delete obsolete session cookies: enabled.
|
||||||
if ( tstampObsolete !== 0 && cookieEntry.session ) {
|
if ( tstampObsolete !== 0 && cookieEntry.session ) {
|
||||||
if ( cookieEntry.tstamp < tstampObsolete ) {
|
if ( cookieEntry.tstamp < tstampObsolete ) {
|
||||||
chromeCookieRemove(cookieEntry, cookieEntry.name);
|
browserCookieRemove(cookieEntry, cookieEntry.name);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all blocked cookies: disabled.
|
// Delete all blocked cookies: disabled.
|
||||||
if ( deleteCookies === false ) {
|
if ( deleteCookies === false ) { continue; }
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query scopes only if we are going to use them
|
// Query scopes only if we are going to use them
|
||||||
if ( srcHostnames === undefined ) {
|
if ( srcHostnames === undefined ) {
|
||||||
|
@ -357,7 +347,7 @@ var processRemoveQueue = function() {
|
||||||
// happen that a cookie is blacklisted on one web page while
|
// happen that a cookie is blacklisted on one web page while
|
||||||
// being whitelisted on another (because of per-page permissions).
|
// being whitelisted on another (because of per-page permissions).
|
||||||
if ( canRemoveCookie(cookieKey, srcHostnames) ) {
|
if ( canRemoveCookie(cookieKey, srcHostnames) ) {
|
||||||
chromeCookieRemove(cookieEntry, cookieEntry.name);
|
browserCookieRemove(cookieEntry, cookieEntry.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,12 +364,12 @@ var processRemoveQueue = function() {
|
||||||
// Remove only some of the cookies which are candidate for removal: who knows,
|
// Remove only some of the cookies which are candidate for removal: who knows,
|
||||||
// maybe a user has 1000s of cookies sitting in his browser...
|
// maybe a user has 1000s of cookies sitting in his browser...
|
||||||
|
|
||||||
var processClean = function() {
|
const processClean = function() {
|
||||||
var us = µm.userSettings;
|
const us = µm.userSettings;
|
||||||
if ( us.deleteCookies || us.deleteUnusedSessionCookies ) {
|
if ( us.deleteCookies || us.deleteUnusedSessionCookies ) {
|
||||||
var cookieKeys = Array.from(cookieDict.keys()),
|
const cookieKeys = Array.from(cookieDict.keys());
|
||||||
len = cookieKeys.length,
|
const len = cookieKeys.length;
|
||||||
step, offset, n;
|
let step, offset, n;
|
||||||
if ( len > 25 ) {
|
if ( len > 25 ) {
|
||||||
step = len / 25;
|
step = len / 25;
|
||||||
offset = Math.floor(Math.random() * len);
|
offset = Math.floor(Math.random() * len);
|
||||||
|
@ -389,7 +379,7 @@ var processClean = function() {
|
||||||
offset = 0;
|
offset = 0;
|
||||||
n = len;
|
n = len;
|
||||||
}
|
}
|
||||||
var i = offset;
|
let i = offset;
|
||||||
while ( n-- ) {
|
while ( n-- ) {
|
||||||
removeCookieAsync(cookieKeys[Math.floor(i % len)]);
|
removeCookieAsync(cookieKeys[Math.floor(i % len)]);
|
||||||
i += step;
|
i += step;
|
||||||
|
@ -401,8 +391,8 @@ var processClean = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var findAndRecordPageCookies = function(pageStore) {
|
const findAndRecordPageCookies = function(pageStore) {
|
||||||
for ( var cookieKey of cookieDict.keys() ) {
|
for ( const cookieKey of cookieDict.keys() ) {
|
||||||
if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) {
|
if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) {
|
||||||
recordPageCookie(pageStore, cookieKey);
|
recordPageCookie(pageStore, cookieKey);
|
||||||
}
|
}
|
||||||
|
@ -411,14 +401,13 @@ var findAndRecordPageCookies = function(pageStore) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var canRemoveCookie = function(cookieKey, srcHostnames) {
|
const canRemoveCookie = function(cookieKey, srcHostnames) {
|
||||||
var cookieEntry = cookieDict.get(cookieKey);
|
const cookieEntry = cookieDict.get(cookieKey);
|
||||||
if ( cookieEntry === undefined ) { return false; }
|
if ( cookieEntry === undefined ) { return false; }
|
||||||
|
|
||||||
var cookieHostname = cookieEntry.hostname;
|
const cookieHostname = cookieEntry.hostname;
|
||||||
var srcHostname;
|
|
||||||
|
|
||||||
for ( srcHostname of cookieEntry.usedOn ) {
|
for ( const srcHostname of cookieEntry.usedOn ) {
|
||||||
if ( µm.mustAllow(srcHostname, cookieHostname, 'cookie') ) {
|
if ( µm.mustAllow(srcHostname, cookieHostname, 'cookie') ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -427,21 +416,17 @@ var canRemoveCookie = function(cookieKey, srcHostnames) {
|
||||||
// For example, if I am logged in into `github.com`, I do not want to be
|
// For example, if I am logged in into `github.com`, I do not want to be
|
||||||
// logged out just because I did not yet open a `github.com` page after
|
// logged out just because I did not yet open a `github.com` page after
|
||||||
// re-starting the browser.
|
// re-starting the browser.
|
||||||
srcHostname = cookieHostname;
|
let srcHostname = cookieHostname;
|
||||||
var pos;
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if ( srcHostnames.has(srcHostname) ) {
|
if (
|
||||||
if ( µm.mustAllow(srcHostname, cookieHostname, 'cookie') ) {
|
srcHostnames.has(srcHostname) &&
|
||||||
return false;
|
µm.mustAllow(srcHostname, cookieHostname, 'cookie')
|
||||||
}
|
) {
|
||||||
}
|
return false;
|
||||||
if ( srcHostname === cookieEntry.domain ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos = srcHostname.indexOf('.');
|
|
||||||
if ( pos === -1 ) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
if ( srcHostname === cookieEntry.domain ) { break; }
|
||||||
|
const pos = srcHostname.indexOf('.');
|
||||||
|
if ( pos === -1 ) { break; }
|
||||||
srcHostname = srcHostname.slice(pos + 1);
|
srcHostname = srcHostname.slice(pos + 1);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -454,27 +439,27 @@ var canRemoveCookie = function(cookieKey, srcHostnames) {
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/79
|
// https://github.com/gorhill/httpswitchboard/issues/79
|
||||||
// If cookie value didn't change, no need to record.
|
// If cookie value didn't change, no need to record.
|
||||||
|
|
||||||
vAPI.cookies.onChanged = (function() {
|
vAPI.cookies.onChanged = (( ) => {
|
||||||
let queue = new Map();
|
const queue = new Map();
|
||||||
let queueTimer;
|
let queueTimer;
|
||||||
|
|
||||||
// Go through all pages and update if needed, as one cookie can be used
|
// Go through all pages and update if needed, as one cookie can be used
|
||||||
// by many web pages, so they need to be recorded for all these pages.
|
// by many web pages, so they need to be recorded for all these pages.
|
||||||
|
|
||||||
let process = function() {
|
const process = function() {
|
||||||
queueTimer = undefined;
|
queueTimer = undefined;
|
||||||
let now = Date.now();
|
const now = Date.now();
|
||||||
let cookieKeys = [];
|
const cookieKeys = [];
|
||||||
for ( let qentry of queue ) {
|
for ( const qentry of queue ) {
|
||||||
if ( qentry[1] > now ) { continue; }
|
if ( qentry[1] > now ) { continue; }
|
||||||
if ( cookieDict.has(qentry[0]) === false ) { continue; }
|
if ( cookieDict.has(qentry[0]) === false ) { continue; }
|
||||||
cookieKeys.push(qentry[0]);
|
cookieKeys.push(qentry[0]);
|
||||||
queue.delete(qentry[0]);
|
queue.delete(qentry[0]);
|
||||||
}
|
}
|
||||||
if ( cookieKeys.length !== 0 ) {
|
if ( cookieKeys.length !== 0 ) {
|
||||||
for ( let pageStore of µm.pageStores.values() ) {
|
for ( const pageStore of µm.pageStores.values() ) {
|
||||||
let allHostnamesString = pageStore.allHostnamesString;
|
const allHostnamesString = pageStore.allHostnamesString;
|
||||||
for ( let cookieKey of cookieKeys ) {
|
for ( const cookieKey of cookieKeys ) {
|
||||||
if ( cookieMatchDomains(cookieKey, allHostnamesString) ) {
|
if ( cookieMatchDomains(cookieKey, allHostnamesString) ) {
|
||||||
recordPageCookie(pageStore, cookieKey);
|
recordPageCookie(pageStore, cookieKey);
|
||||||
}
|
}
|
||||||
|
@ -487,7 +472,7 @@ vAPI.cookies.onChanged = (function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return function(cookie) {
|
return function(cookie) {
|
||||||
let cookieKey = cookieKeyFromCookie(cookie);
|
const cookieKey = cookieKeyFromCookie(cookie);
|
||||||
let cookieEntry = cookieDict.get(cookieKey);
|
let cookieEntry = cookieDict.get(cookieKey);
|
||||||
if ( cookieEntry === undefined ) {
|
if ( cookieEntry === undefined ) {
|
||||||
cookieEntry = addCookieToDict(cookie);
|
cookieEntry = addCookieToDict(cookie);
|
||||||
|
@ -509,10 +494,11 @@ vAPI.cookies.onChanged = (function() {
|
||||||
// Listen to any change in cookieland, we will update page stats accordingly.
|
// Listen to any change in cookieland, we will update page stats accordingly.
|
||||||
|
|
||||||
vAPI.cookies.onRemoved = function(cookie) {
|
vAPI.cookies.onRemoved = function(cookie) {
|
||||||
var cookieKey = cookieKeyFromCookie(cookie);
|
const cookieKey = cookieKeyFromCookie(cookie);
|
||||||
if ( removeCookieFromDict(cookieKey) ) {
|
if ( removeCookieFromDict(cookieKey) ) {
|
||||||
µm.logger.writeOne({
|
µm.logger.writeOne({
|
||||||
info: i18nCookieDeleteSuccess.replace('{{value}}', cookieKey),
|
realm: 'message',
|
||||||
|
text: i18nCookieDeleteSuccess.replace('{{value}}', cookieKey),
|
||||||
prettify: 'cookie'
|
prettify: 'cookie'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -523,10 +509,11 @@ vAPI.cookies.onRemoved = function(cookie) {
|
||||||
// Listen to any change in cookieland, we will update page stats accordingly.
|
// Listen to any change in cookieland, we will update page stats accordingly.
|
||||||
|
|
||||||
vAPI.cookies.onAllRemoved = function() {
|
vAPI.cookies.onAllRemoved = function() {
|
||||||
for ( var cookieKey of cookieDict.keys() ) {
|
for ( const cookieKey of cookieDict.keys() ) {
|
||||||
if ( removeCookieFromDict(cookieKey) ) {
|
if ( removeCookieFromDict(cookieKey) ) {
|
||||||
µm.logger.writeOne({
|
µm.logger.writeOne({
|
||||||
info: i18nCookieDeleteSuccess.replace('{{value}}', cookieKey),
|
realm: 'message',
|
||||||
|
text: i18nCookieDeleteSuccess.replace('{{value}}', cookieKey),
|
||||||
prettify: 'cookie'
|
prettify: 'cookie'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -535,7 +522,11 @@ vAPI.cookies.onAllRemoved = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.cookies.getAll(addCookiesToDict);
|
vAPI.cookies.getAll().then(cookies => {
|
||||||
|
for ( const cookie of cookies ) {
|
||||||
|
addCookieToDict(cookie);
|
||||||
|
}
|
||||||
|
});
|
||||||
vAPI.cookies.start();
|
vAPI.cookies.start();
|
||||||
|
|
||||||
vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod);
|
vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
µMatrix - a Chromium browser extension to black/white list requests.
|
µMatrix - a Chromium browser extension to black/white list requests.
|
||||||
Copyright (C) 2014 Raymond Hill
|
Copyright (C) 2014 Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
@ -19,22 +19,133 @@
|
||||||
Home: https://github.com/gorhill/uMatrix
|
Home: https://github.com/gorhill/uMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/******************************************************************************/
|
/* global CodeMirror, uDom */
|
||||||
|
|
||||||
uDom.onLoad(function() {
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Open links in the proper window
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
self.uBlockDashboard = self.uBlockDashboard || {};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
{
|
||||||
|
let grabFocusTimer;
|
||||||
|
let grabFocusTarget;
|
||||||
|
|
||||||
|
const grabFocus = function() {
|
||||||
|
grabFocusTarget.focus();
|
||||||
|
grabFocusTimer = grabFocusTarget = undefined;
|
||||||
|
};
|
||||||
|
const grabFocusAsync = function(cm) {
|
||||||
|
grabFocusTarget = cm;
|
||||||
|
if ( grabFocusTimer === undefined ) {
|
||||||
|
grabFocusTimer = vAPI.setTimeout(grabFocus, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/gorhill/uBlock/issues/3646
|
||||||
|
const patchSelectAll = function(cm, details) {
|
||||||
|
var vp = cm.getViewport();
|
||||||
|
if ( details.ranges.length !== 1 ) { return; }
|
||||||
|
var range = details.ranges[0],
|
||||||
|
lineFrom = range.anchor.line,
|
||||||
|
lineTo = range.head.line;
|
||||||
|
if ( lineTo === lineFrom ) { return; }
|
||||||
|
if ( range.head.ch !== 0 ) { lineTo += 1; }
|
||||||
|
if ( lineFrom !== vp.from || lineTo !== vp.to ) { return; }
|
||||||
|
details.update([
|
||||||
|
{
|
||||||
|
anchor: { line: 0, ch: 0 },
|
||||||
|
head: { line: cm.lineCount(), ch: 0 }
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
grabFocusAsync(cm);
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastGutterClick = 0;
|
||||||
|
let lastGutterLine = 0;
|
||||||
|
|
||||||
|
const onGutterClicked = function(cm, line) {
|
||||||
|
const delta = Date.now() - lastGutterClick;
|
||||||
|
if ( delta >= 500 || line !== lastGutterLine ) {
|
||||||
|
cm.setSelection(
|
||||||
|
{ line: line, ch: 0 },
|
||||||
|
{ line: line + 1, ch: 0 }
|
||||||
|
);
|
||||||
|
lastGutterClick = Date.now();
|
||||||
|
lastGutterLine = line;
|
||||||
|
} else {
|
||||||
|
cm.setSelection(
|
||||||
|
{ line: 0, ch: 0 },
|
||||||
|
{ line: cm.lineCount(), ch: 0 },
|
||||||
|
{ scroll: false }
|
||||||
|
);
|
||||||
|
lastGutterClick = 0;
|
||||||
|
}
|
||||||
|
grabFocusAsync(cm);
|
||||||
|
};
|
||||||
|
|
||||||
|
let resizeTimer,
|
||||||
|
resizeObserver;
|
||||||
|
const resize = function(cm) {
|
||||||
|
resizeTimer = undefined;
|
||||||
|
const child = document.querySelector('.codeMirrorFillVertical');
|
||||||
|
if ( child === null ) { return; }
|
||||||
|
const prect = document.documentElement.getBoundingClientRect();
|
||||||
|
const crect = child.getBoundingClientRect();
|
||||||
|
const cssHeight = Math.floor(Math.max(prect.bottom - crect.top, 80)) + 'px';
|
||||||
|
if ( child.style.height === cssHeight ) { return; }
|
||||||
|
child.style.height = cssHeight;
|
||||||
|
// https://github.com/gorhill/uBlock/issues/3694
|
||||||
|
// Need to call cm.refresh() when resizing occurs. However the
|
||||||
|
// cursor position may end up outside the viewport, hence we also
|
||||||
|
// call cm.scrollIntoView() to address this.
|
||||||
|
// Reference: https://codemirror.net/doc/manual.html#api_sizing
|
||||||
|
if ( cm instanceof CodeMirror ) {
|
||||||
|
cm.refresh();
|
||||||
|
cm.scrollIntoView(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const resizeAsync = function(cm, delay) {
|
||||||
|
if ( resizeTimer !== undefined ) { return; }
|
||||||
|
resizeTimer = vAPI.setTimeout(
|
||||||
|
resize.bind(null, cm),
|
||||||
|
typeof delay === 'number' ? delay : 66
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.uBlockDashboard.patchCodeMirrorEditor = function(cm) {
|
||||||
|
if ( document.querySelector('.codeMirrorFillVertical') !== null ) {
|
||||||
|
const boundResizeAsync = resizeAsync.bind(null, cm);
|
||||||
|
window.addEventListener('resize', boundResizeAsync);
|
||||||
|
resizeObserver = new MutationObserver(boundResizeAsync);
|
||||||
|
resizeObserver.observe(document.querySelector('.body'), {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
resizeAsync(cm, 1);
|
||||||
|
}
|
||||||
|
if ( cm.options.inputStyle === 'contenteditable' ) {
|
||||||
|
cm.on('beforeSelectionChange', patchSelectAll);
|
||||||
|
}
|
||||||
|
cm.on('gutterClick', onGutterClicked);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
uDom('a').attr('target', '_blank');
|
uDom('a').attr('target', '_blank');
|
||||||
uDom('a[href*="dashboard.html"]').attr('target', '_parent');
|
uDom('a[href*="dashboard.html"]').attr('target', '_parent');
|
||||||
uDom('.whatisthis').on('click', function() {
|
uDom('.whatisthis').on('click', ev => {
|
||||||
uDom(this).parent()
|
ev.target
|
||||||
.descendants('.whatisthis-expandable')
|
.parentElement
|
||||||
.toggleClass('whatisthis-expanded');
|
.querySelector('.whatisthis-expandable')
|
||||||
|
.classList.toggle('whatisthis-expanded');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// <<<<< end of local scope
|
||||||
/******************************************************************************/
|
}
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -23,33 +23,37 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
const loadDashboardPanel = function(hash) {
|
||||||
|
const button = uDom(hash);
|
||||||
var loadDashboardPanel = function(hash) {
|
const url = button.attr('data-dashboard-panel-url');
|
||||||
var button = uDom(hash);
|
uDom('iframe').attr('src', url);
|
||||||
var url = button.attr('data-dashboard-panel-url');
|
uDom('.tabButton').forEach(function(button){
|
||||||
uDom('iframe').attr('src', url);
|
button.toggleClass(
|
||||||
uDom('.tabButton').forEach(function(button){
|
'selected',
|
||||||
button.toggleClass(
|
button.attr('data-dashboard-panel-url') === url
|
||||||
'selected',
|
);
|
||||||
button.attr('data-dashboard-panel-url') === url
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var onTabClickHandler = function() {
|
|
||||||
loadDashboardPanel(window.location.hash);
|
|
||||||
};
|
|
||||||
|
|
||||||
uDom.onLoad(function() {
|
|
||||||
window.addEventListener('hashchange', onTabClickHandler);
|
|
||||||
var hash = window.location.hash;
|
|
||||||
if ( hash.length < 2 ) {
|
|
||||||
hash = '#settings';
|
|
||||||
}
|
|
||||||
loadDashboardPanel(hash);
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
})();
|
const onTabClickHandler = function() {
|
||||||
|
loadDashboardPanel(window.location.hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
uDom.onLoad(function() {
|
||||||
|
window.addEventListener('hashchange', onTabClickHandler);
|
||||||
|
let hash = window.location.hash;
|
||||||
|
if ( hash.length < 2 ) {
|
||||||
|
hash = '#settings';
|
||||||
|
}
|
||||||
|
loadDashboardPanel(hash);
|
||||||
|
});
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
|
|
310
src/js/filtering-context.js
Normal file
310
src/js/filtering-context.js
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2018-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
µMatrix.FilteringContext = function(other) {
|
||||||
|
if ( other instanceof µMatrix.FilteringContext ) {
|
||||||
|
return this.fromFilteringContext(other);
|
||||||
|
}
|
||||||
|
this.tstamp = 0;
|
||||||
|
this.realm = '';
|
||||||
|
this.id = undefined;
|
||||||
|
this.type = undefined;
|
||||||
|
this.url = undefined;
|
||||||
|
this.aliasURL = undefined;
|
||||||
|
this.hostname = undefined;
|
||||||
|
this.domain = undefined;
|
||||||
|
this.docId = undefined;
|
||||||
|
this.docOrigin = undefined;
|
||||||
|
this.docHostname = undefined;
|
||||||
|
this.docDomain = undefined;
|
||||||
|
this.tabId = undefined;
|
||||||
|
this.tabOrigin = undefined;
|
||||||
|
this.tabHostname = undefined;
|
||||||
|
this.tabDomain = undefined;
|
||||||
|
this.filter = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
µMatrix.FilteringContext.prototype = {
|
||||||
|
requestTypeNormalizer: {
|
||||||
|
font : 'css',
|
||||||
|
image : 'image',
|
||||||
|
imageset : 'image',
|
||||||
|
main_frame : 'doc',
|
||||||
|
media : 'media',
|
||||||
|
object : 'media',
|
||||||
|
other : 'other',
|
||||||
|
script : 'script',
|
||||||
|
stylesheet : 'css',
|
||||||
|
sub_frame : 'frame',
|
||||||
|
websocket : 'fetch',
|
||||||
|
xmlhttprequest: 'fetch'
|
||||||
|
},
|
||||||
|
fromTabId: function(tabId) {
|
||||||
|
const tabContext = µMatrix.tabContextManager.mustLookup(tabId);
|
||||||
|
this.tabOrigin = tabContext.origin;
|
||||||
|
this.tabHostname = tabContext.rootHostname;
|
||||||
|
this.tabDomain = tabContext.rootDomain;
|
||||||
|
this.tabId = tabContext.tabId;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/459
|
||||||
|
// In case of a request for frame and if ever no context is specified,
|
||||||
|
// assume the origin of the context is the same as the request itself.
|
||||||
|
fromWebrequestDetails: function(details) {
|
||||||
|
this.type = this.requestTypeNormalizer[details.type] || 'other';
|
||||||
|
const tabId = details.tabId;
|
||||||
|
if ( tabId > 0 && this.type === 'doc' ) {
|
||||||
|
µMatrix.tabContextManager.push(tabId, details.url);
|
||||||
|
}
|
||||||
|
this.fromTabId(tabId);
|
||||||
|
this.realm = '';
|
||||||
|
this.id = details.requestId;
|
||||||
|
this.setURL(details.url);
|
||||||
|
this.aliasURL = details.aliasURL || undefined;
|
||||||
|
this.docId = this.type !== 'frame'
|
||||||
|
? details.frameId
|
||||||
|
: details.parentFrameId;
|
||||||
|
if ( this.tabId > 0 ) {
|
||||||
|
if ( this.docId === 0 ) {
|
||||||
|
this.docOrigin = this.tabOrigin;
|
||||||
|
this.docHostname = this.tabHostname;
|
||||||
|
this.docDomain = this.tabDomain;
|
||||||
|
} else if ( details.documentUrl !== undefined ) {
|
||||||
|
this.setDocOriginFromURL(details.documentUrl);
|
||||||
|
} else {
|
||||||
|
this.setDocOrigin(this.tabOrigin);
|
||||||
|
}
|
||||||
|
} else if ( details.documentUrl !== undefined ) {
|
||||||
|
const origin = this.originFromURI(
|
||||||
|
µMatrix.normalizePageURL(0, details.documentUrl)
|
||||||
|
);
|
||||||
|
this.setDocOrigin(origin).setTabOrigin(origin);
|
||||||
|
} else if (
|
||||||
|
this.docId === -1 ||
|
||||||
|
this.type === 'doc' ||
|
||||||
|
this.type === 'frame'
|
||||||
|
) {
|
||||||
|
const origin = this.originFromURI(this.url);
|
||||||
|
this.setDocOrigin(origin).setTabOrigin(origin);
|
||||||
|
} else {
|
||||||
|
this.setDocOrigin(this.tabOrigin);
|
||||||
|
}
|
||||||
|
this.filter = undefined;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
fromFilteringContext: function(other) {
|
||||||
|
this.realm = other.realm;
|
||||||
|
this.type = other.type;
|
||||||
|
this.url = other.url;
|
||||||
|
this.hostname = other.hostname;
|
||||||
|
this.domain = other.domain;
|
||||||
|
this.docId = other.docId;
|
||||||
|
this.docOrigin = other.docOrigin;
|
||||||
|
this.docHostname = other.docHostname;
|
||||||
|
this.docDomain = other.docDomain;
|
||||||
|
this.tabId = other.tabId;
|
||||||
|
this.tabOrigin = other.tabOrigin;
|
||||||
|
this.tabHostname = other.tabHostname;
|
||||||
|
this.tabDomain = other.tabDomain;
|
||||||
|
this.filter = undefined;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
duplicate: function() {
|
||||||
|
return (new µMatrix.FilteringContext(this));
|
||||||
|
},
|
||||||
|
setRealm: function(a) {
|
||||||
|
this.realm = a;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
setType: function(a) {
|
||||||
|
this.type = a;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
setURL: function(a) {
|
||||||
|
if ( a !== this.url ) {
|
||||||
|
this.hostname = this.domain = undefined;
|
||||||
|
this.url = a;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
getHostname: function() {
|
||||||
|
if ( this.hostname === undefined ) {
|
||||||
|
this.hostname = this.hostnameFromURI(this.url);
|
||||||
|
}
|
||||||
|
return this.hostname;
|
||||||
|
},
|
||||||
|
setHostname: function(a) {
|
||||||
|
if ( a !== this.hostname ) {
|
||||||
|
this.domain = undefined;
|
||||||
|
this.hostname = a;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
getDomain: function() {
|
||||||
|
if ( this.domain === undefined ) {
|
||||||
|
this.domain = this.domainFromHostname(this.getHostname());
|
||||||
|
}
|
||||||
|
return this.domain;
|
||||||
|
},
|
||||||
|
setDomain: function(a) {
|
||||||
|
this.domain = a;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
getDocOrigin: function() {
|
||||||
|
if ( this.docOrigin === undefined ) {
|
||||||
|
this.docOrigin = this.tabOrigin;
|
||||||
|
}
|
||||||
|
return this.docOrigin;
|
||||||
|
},
|
||||||
|
setDocOrigin: function(a) {
|
||||||
|
if ( a !== this.docOrigin ) {
|
||||||
|
this.docHostname = this.docDomain = undefined;
|
||||||
|
this.docOrigin = a;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
setDocOriginFromURL: function(a) {
|
||||||
|
return this.setDocOrigin(this.originFromURI(a));
|
||||||
|
},
|
||||||
|
getDocHostname: function() {
|
||||||
|
if ( this.docHostname === undefined ) {
|
||||||
|
this.docHostname = this.hostnameFromURI(this.getDocOrigin());
|
||||||
|
}
|
||||||
|
return this.docHostname;
|
||||||
|
},
|
||||||
|
setDocHostname: function(a) {
|
||||||
|
if ( a !== this.docHostname ) {
|
||||||
|
this.docDomain = undefined;
|
||||||
|
this.docHostname = a;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
getDocDomain: function() {
|
||||||
|
if ( this.docDomain === undefined ) {
|
||||||
|
this.docDomain = this.domainFromHostname(this.getDocHostname());
|
||||||
|
}
|
||||||
|
return this.docDomain;
|
||||||
|
},
|
||||||
|
setDocDomain: function(a) {
|
||||||
|
this.docDomain = a;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
// The idea is to minimize the amout of work done to figure out whether
|
||||||
|
// the resource is 3rd-party to the document.
|
||||||
|
is3rdPartyToDoc: function() {
|
||||||
|
let docDomain = this.getDocDomain();
|
||||||
|
if ( docDomain === '' ) { docDomain = this.docHostname; }
|
||||||
|
if ( this.domain !== undefined && this.domain !== '' ) {
|
||||||
|
return this.domain !== docDomain;
|
||||||
|
}
|
||||||
|
const hostname = this.getHostname();
|
||||||
|
if ( hostname.endsWith(docDomain) === false ) { return true; }
|
||||||
|
const i = hostname.length - docDomain.length;
|
||||||
|
if ( i === 0 ) { return false; }
|
||||||
|
return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */;
|
||||||
|
},
|
||||||
|
setTabId: function(a) {
|
||||||
|
this.tabId = a;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
getTabOrigin: function() {
|
||||||
|
if ( this.tabOrigin === undefined ) {
|
||||||
|
const tabContext = µMatrix.tabContextManager.mustLookup(this.tabId);
|
||||||
|
this.tabOrigin = tabContext.origin;
|
||||||
|
this.tabHostname = tabContext.rootHostname;
|
||||||
|
this.tabDomain = tabContext.rootDomain;
|
||||||
|
}
|
||||||
|
return this.tabOrigin;
|
||||||
|
},
|
||||||
|
setTabOrigin: function(a) {
|
||||||
|
if ( a !== this.tabOrigin ) {
|
||||||
|
this.tabHostname = this.tabDomain = undefined;
|
||||||
|
this.tabOrigin = a;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
setTabOriginFromURL: function(a) {
|
||||||
|
return this.setTabOrigin(this.originFromURI(a));
|
||||||
|
},
|
||||||
|
getTabHostname: function() {
|
||||||
|
if ( this.tabHostname === undefined ) {
|
||||||
|
this.tabHostname = this.hostnameFromURI(this.getTabOrigin());
|
||||||
|
}
|
||||||
|
return this.tabHostname;
|
||||||
|
},
|
||||||
|
setTabHostname: function(a) {
|
||||||
|
if ( a !== this.tabHostname ) {
|
||||||
|
this.tabDomain = undefined;
|
||||||
|
this.tabHostname = a;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
getTabDomain: function() {
|
||||||
|
if ( this.tabDomain === undefined ) {
|
||||||
|
this.tabDomain = this.domainFromHostname(this.getTabHostname());
|
||||||
|
}
|
||||||
|
return this.tabDomain;
|
||||||
|
},
|
||||||
|
setTabDomain: function(a) {
|
||||||
|
this.docDomain = a;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
// The idea is to minimize the amout of work done to figure out whether
|
||||||
|
// the resource is 3rd-party to the top document.
|
||||||
|
is3rdPartyToTab: function() {
|
||||||
|
let tabDomain = this.getTabDomain();
|
||||||
|
if ( tabDomain === '' ) { tabDomain = this.tabHostname; }
|
||||||
|
if ( this.domain !== undefined && this.domain !== '' ) {
|
||||||
|
return this.domain !== tabDomain;
|
||||||
|
}
|
||||||
|
const hostname = this.getHostname();
|
||||||
|
if ( hostname.endsWith(tabDomain) === false ) { return true; }
|
||||||
|
const i = hostname.length - tabDomain.length;
|
||||||
|
if ( i === 0 ) { return false; }
|
||||||
|
return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */;
|
||||||
|
},
|
||||||
|
setFilter: function(a) {
|
||||||
|
this.filter = a;
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
toLogger: function() {
|
||||||
|
this.tstamp = Date.now();
|
||||||
|
if ( this.domain === undefined ) {
|
||||||
|
void this.getDomain();
|
||||||
|
}
|
||||||
|
if ( this.docDomain === undefined ) {
|
||||||
|
void this.getDocDomain();
|
||||||
|
}
|
||||||
|
if ( this.tabDomain === undefined ) {
|
||||||
|
void this.getTabDomain();
|
||||||
|
}
|
||||||
|
µMatrix.logger.writeOne(this);
|
||||||
|
},
|
||||||
|
originFromURI: µMatrix.URI.originFromURI,
|
||||||
|
hostnameFromURI: vAPI.hostnameFromURI,
|
||||||
|
domainFromHostname: vAPI.domainFromHostname,
|
||||||
|
};
|
||||||
|
|
||||||
|
µMatrix.filteringContext = new µMatrix.FilteringContext();
|
760
src/js/hntrie.js
Normal file
760
src/js/hntrie.js
Normal file
|
@ -0,0 +1,760 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2017-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* globals WebAssembly */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// *****************************************************************************
|
||||||
|
// start of local namespace
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// The directory from which the current script was fetched should
|
||||||
|
// also contain the related WASM file. The script is fetched from
|
||||||
|
// a trusted location, and consequently so will be the related
|
||||||
|
// WASM file.
|
||||||
|
let workingDir = '';
|
||||||
|
{
|
||||||
|
const url = new URL(document.currentScript.src);
|
||||||
|
const match = /[^\/]+$/.exec(url.pathname);
|
||||||
|
if ( match !== null ) {
|
||||||
|
url.pathname = url.pathname.slice(0, match.index);
|
||||||
|
}
|
||||||
|
workingDir = url.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
The original prototype was to develop an idea I had about using jump indices
|
||||||
|
in a TypedArray for quickly matching hostnames (or more generally strings)[1].
|
||||||
|
Once I had a working, un-optimized prototype, I realized I had ended up
|
||||||
|
with something formally named a "trie": <https://en.wikipedia.org/wiki/Trie>,
|
||||||
|
hence the name. I have no idea whether the implementation here or one
|
||||||
|
resembling it has been done elsewhere.
|
||||||
|
|
||||||
|
"HN" in HNTrieContainer stands for "HostName", because the trie is
|
||||||
|
specialized to deal with matching hostnames -- which is a bit more
|
||||||
|
complicated than matching plain strings.
|
||||||
|
|
||||||
|
For example, `www.abc.com` is deemed matching `abc.com`, because the former
|
||||||
|
is a subdomain of the latter. The opposite is of course not true.
|
||||||
|
|
||||||
|
The resulting read-only tries created as a result of using HNTrieContainer
|
||||||
|
are simply just typed arrays filled with integers. The matching algorithm is
|
||||||
|
just a matter of reading/comparing these integers, and further using them as
|
||||||
|
indices in the array as a way to move around in the trie.
|
||||||
|
|
||||||
|
[1] To solve <https://github.com/gorhill/uBlock/issues/3193>
|
||||||
|
|
||||||
|
Since this trie is specialized for matching hostnames, the stored
|
||||||
|
strings are reversed internally, because of hostname comparison logic:
|
||||||
|
|
||||||
|
Correct matching:
|
||||||
|
index 0123456
|
||||||
|
abc.com
|
||||||
|
|
|
||||||
|
www.abc.com
|
||||||
|
index 01234567890
|
||||||
|
|
||||||
|
Incorrect matching (typically used for plain strings):
|
||||||
|
index 0123456
|
||||||
|
abc.com
|
||||||
|
|
|
||||||
|
www.abc.com
|
||||||
|
index 01234567890
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
1st iteration:
|
||||||
|
- https://github.com/gorhill/uBlock/blob/ff58107dac3a32607f8113e39ed5015584506813/src/js/hntrie.js
|
||||||
|
- Suitable for small to medium set of hostnames
|
||||||
|
- One buffer per trie
|
||||||
|
|
||||||
|
2nd iteration: goal was to make matches() method wasm-able
|
||||||
|
- https://github.com/gorhill/uBlock/blob/c3b0fd31f64bd7ffecdd282fb1208fe07aac3eb0/src/js/hntrie.js
|
||||||
|
- Suitable for small to medium set of hostnames
|
||||||
|
- Distinct tries all share same buffer:
|
||||||
|
- Reduced memory footprint
|
||||||
|
- https://stackoverflow.com/questions/45803829/memory-overhead-of-typed-arrays-vs-strings/45808835#45808835
|
||||||
|
- Reusing needle character lookups for all tries
|
||||||
|
- This significantly reduce the number of String.charCodeAt() calls
|
||||||
|
- Slightly improved creation time
|
||||||
|
|
||||||
|
This is the 3rd iteration: goal was to make add() method wasm-able and
|
||||||
|
further improve memory/CPU efficiency.
|
||||||
|
|
||||||
|
This 3rd iteration has the following new traits:
|
||||||
|
- Suitable for small to large set of hostnames
|
||||||
|
- Support multiple trie containers (instanciable)
|
||||||
|
- Designed to hold large number of hostnames
|
||||||
|
- Hostnames can be added at any time (instead of all at once)
|
||||||
|
- This means pre-sorting is no longer a requirement
|
||||||
|
- The trie is always compact
|
||||||
|
- There is no longer a need for a `vacuum` method
|
||||||
|
- This makes the add() method wasm-able
|
||||||
|
- It can return the exact hostname which caused the match
|
||||||
|
- serializable/unserializable available for fast loading
|
||||||
|
- Distinct trie reference support the iteration protocol, thus allowing
|
||||||
|
to extract all the hostnames in the trie
|
||||||
|
|
||||||
|
Its primary purpose is to replace the use of Set() as a mean to hold
|
||||||
|
large number of hostnames (ex. FilterHostnameDict in static filtering
|
||||||
|
engine).
|
||||||
|
|
||||||
|
A HNTrieContainer is mostly a large buffer in which distinct but related
|
||||||
|
tries are stored. The memory layout of the buffer is as follow:
|
||||||
|
|
||||||
|
0-254: needle being processed
|
||||||
|
255: length of needle
|
||||||
|
256-259: offset to start of trie data section (=> trie0)
|
||||||
|
260-263: offset to end of trie data section (=> trie1)
|
||||||
|
264-267: offset to start of character data section (=> char0)
|
||||||
|
268-271: offset to end of character data section (=> char1)
|
||||||
|
272: start of trie data section
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const PAGE_SIZE = 65536;
|
||||||
|
// i32 / i8
|
||||||
|
const TRIE0_SLOT = 256 >>> 2; // 64 / 256
|
||||||
|
const TRIE1_SLOT = TRIE0_SLOT + 1; // 65 / 260
|
||||||
|
const CHAR0_SLOT = TRIE0_SLOT + 2; // 66 / 264
|
||||||
|
const CHAR1_SLOT = TRIE0_SLOT + 3; // 67 / 268
|
||||||
|
const TRIE0_START = TRIE0_SLOT + 4 << 2; // 272
|
||||||
|
|
||||||
|
const HNTrieContainer = class {
|
||||||
|
|
||||||
|
constructor(details) {
|
||||||
|
if ( details instanceof Object === false ) { details = {}; }
|
||||||
|
let len = (details.byteLength || 0) + PAGE_SIZE-1 & ~(PAGE_SIZE-1);
|
||||||
|
this.buf = new Uint8Array(Math.max(len, 131072));
|
||||||
|
this.buf32 = new Uint32Array(this.buf.buffer);
|
||||||
|
this.needle = '';
|
||||||
|
this.buf32[TRIE0_SLOT] = TRIE0_START;
|
||||||
|
this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT];
|
||||||
|
this.buf32[CHAR0_SLOT] = details.char0 || 65536;
|
||||||
|
this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT];
|
||||||
|
this.wasmInstancePromise = null;
|
||||||
|
this.wasmMemory = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Public methods
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT];
|
||||||
|
this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT];
|
||||||
|
}
|
||||||
|
|
||||||
|
setNeedle(needle) {
|
||||||
|
if ( needle !== this.needle ) {
|
||||||
|
const buf = this.buf;
|
||||||
|
let i = needle.length;
|
||||||
|
if ( i > 255 ) { i = 255; }
|
||||||
|
buf[255] = i;
|
||||||
|
while ( i-- ) {
|
||||||
|
buf[i] = needle.charCodeAt(i);
|
||||||
|
}
|
||||||
|
this.needle = needle;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesJS(iroot) {
|
||||||
|
const buf32 = this.buf32;
|
||||||
|
const buf8 = this.buf;
|
||||||
|
const char0 = buf32[CHAR0_SLOT];
|
||||||
|
let ineedle = buf8[255];
|
||||||
|
let icell = buf32[iroot+0];
|
||||||
|
if ( icell === 0 ) { return -1; }
|
||||||
|
for (;;) {
|
||||||
|
if ( ineedle === 0 ) { return -1; }
|
||||||
|
ineedle -= 1;
|
||||||
|
let c = buf8[ineedle];
|
||||||
|
let v, i0;
|
||||||
|
// find first segment with a first-character match
|
||||||
|
for (;;) {
|
||||||
|
v = buf32[icell+2];
|
||||||
|
i0 = char0 + (v & 0x00FFFFFF);
|
||||||
|
if ( buf8[i0] === c ) { break; }
|
||||||
|
icell = buf32[icell+0];
|
||||||
|
if ( icell === 0 ) { return -1; }
|
||||||
|
}
|
||||||
|
// all characters in segment must match
|
||||||
|
let n = v >>> 24;
|
||||||
|
if ( n > 1 ) {
|
||||||
|
n -= 1;
|
||||||
|
if ( n > ineedle ) { return -1; }
|
||||||
|
i0 += 1;
|
||||||
|
const i1 = i0 + n;
|
||||||
|
do {
|
||||||
|
ineedle -= 1;
|
||||||
|
if ( buf8[i0] !== buf8[ineedle] ) { return -1; }
|
||||||
|
i0 += 1;
|
||||||
|
} while ( i0 < i1 );
|
||||||
|
}
|
||||||
|
// next segment
|
||||||
|
icell = buf32[icell+1];
|
||||||
|
if ( icell === 0 ) { break; }
|
||||||
|
if ( buf32[icell+2] === 0 ) {
|
||||||
|
if ( ineedle === 0 || buf8[ineedle-1] === 0x2E ) {
|
||||||
|
return ineedle;
|
||||||
|
}
|
||||||
|
icell = buf32[icell+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ineedle === 0 || buf8[ineedle-1] === 0x2E ? ineedle : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
createOne(args) {
|
||||||
|
if ( Array.isArray(args) ) {
|
||||||
|
return new this.HNTrieRef(this, ...args);
|
||||||
|
}
|
||||||
|
// grow buffer if needed
|
||||||
|
if ( (this.buf32[CHAR0_SLOT] - this.buf32[TRIE1_SLOT]) < 12 ) {
|
||||||
|
this.growBuf(12, 0);
|
||||||
|
}
|
||||||
|
const iroot = this.buf32[TRIE1_SLOT] >>> 2;
|
||||||
|
this.buf32[TRIE1_SLOT] += 12;
|
||||||
|
this.buf32[iroot+0] = 0;
|
||||||
|
this.buf32[iroot+1] = 0;
|
||||||
|
this.buf32[iroot+2] = 0;
|
||||||
|
return new this.HNTrieRef(this, iroot, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOne(trieRef) {
|
||||||
|
return [
|
||||||
|
trieRef.iroot,
|
||||||
|
trieRef.addCount,
|
||||||
|
trieRef.addedCount,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
addJS(iroot) {
|
||||||
|
let lhnchar = this.buf[255];
|
||||||
|
if ( lhnchar === 0 ) { return 0; }
|
||||||
|
// grow buffer if needed
|
||||||
|
if (
|
||||||
|
(this.buf32[CHAR0_SLOT] - this.buf32[TRIE1_SLOT]) < 24 ||
|
||||||
|
(this.buf.length - this.buf32[CHAR1_SLOT]) < 256
|
||||||
|
) {
|
||||||
|
this.growBuf(24, 256);
|
||||||
|
}
|
||||||
|
let icell = this.buf32[iroot+0];
|
||||||
|
// special case: first node in trie
|
||||||
|
if ( icell === 0 ) {
|
||||||
|
this.buf32[iroot+0] = this.addCell(0, 0, this.addSegment(lhnchar));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
const char0 = this.buf32[CHAR0_SLOT];
|
||||||
|
let inext;
|
||||||
|
// find a matching cell: move down
|
||||||
|
for (;;) {
|
||||||
|
const vseg = this.buf32[icell+2];
|
||||||
|
// skip boundary cells
|
||||||
|
if ( vseg === 0 ) {
|
||||||
|
// remainder is at label boundary? if yes, no need to add
|
||||||
|
// the rest since the shortest match is always reported
|
||||||
|
if ( this.buf[lhnchar-1] === 0x2E /* '.' */ ) { return -1; }
|
||||||
|
icell = this.buf32[icell+1];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let isegchar0 = char0 + (vseg & 0x00FFFFFF);
|
||||||
|
// if first character is no match, move to next descendant
|
||||||
|
if ( this.buf[isegchar0] !== this.buf[lhnchar-1] ) {
|
||||||
|
inext = this.buf32[icell+0];
|
||||||
|
if ( inext === 0 ) {
|
||||||
|
this.buf32[icell+0] = this.addCell(0, 0, this.addSegment(lhnchar));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
icell = inext;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 1st character was tested
|
||||||
|
let isegchar = 1;
|
||||||
|
lhnchar -= 1;
|
||||||
|
// find 1st mismatch in rest of segment
|
||||||
|
const lsegchar = vseg >>> 24;
|
||||||
|
if ( lsegchar !== 1 ) {
|
||||||
|
for (;;) {
|
||||||
|
if ( isegchar === lsegchar ) { break; }
|
||||||
|
if ( lhnchar === 0 ) { break; }
|
||||||
|
if ( this.buf[isegchar0+isegchar] !== this.buf[lhnchar-1] ) { break; }
|
||||||
|
isegchar += 1;
|
||||||
|
lhnchar -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// all segment characters matched
|
||||||
|
if ( isegchar === lsegchar ) {
|
||||||
|
inext = this.buf32[icell+1];
|
||||||
|
// needle remainder: no
|
||||||
|
if ( lhnchar === 0 ) {
|
||||||
|
// boundary cell already present
|
||||||
|
if ( inext === 0 || this.buf32[inext+2] === 0 ) { return 0; }
|
||||||
|
// need boundary cell
|
||||||
|
this.buf32[icell+1] = this.addCell(0, inext, 0);
|
||||||
|
}
|
||||||
|
// needle remainder: yes
|
||||||
|
else {
|
||||||
|
if ( inext !== 0 ) {
|
||||||
|
icell = inext;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// remainder is at label boundary? if yes, no need to add
|
||||||
|
// the rest since the shortest match is always reported
|
||||||
|
if ( this.buf[lhnchar-1] === 0x2E /* '.' */ ) { return -1; }
|
||||||
|
// boundary cell + needle remainder
|
||||||
|
inext = this.addCell(0, 0, 0);
|
||||||
|
this.buf32[icell+1] = inext;
|
||||||
|
this.buf32[inext+1] = this.addCell(0, 0, this.addSegment(lhnchar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// some segment characters matched
|
||||||
|
else {
|
||||||
|
// split current cell
|
||||||
|
isegchar0 -= char0;
|
||||||
|
this.buf32[icell+2] = isegchar << 24 | isegchar0;
|
||||||
|
inext = this.addCell(
|
||||||
|
0,
|
||||||
|
this.buf32[icell+1],
|
||||||
|
lsegchar - isegchar << 24 | isegchar0 + isegchar
|
||||||
|
);
|
||||||
|
this.buf32[icell+1] = inext;
|
||||||
|
// needle remainder: no = need boundary cell
|
||||||
|
if ( lhnchar === 0 ) {
|
||||||
|
this.buf32[icell+1] = this.addCell(0, inext, 0);
|
||||||
|
}
|
||||||
|
// needle remainder: yes = need new cell for remaining characters
|
||||||
|
else {
|
||||||
|
this.buf32[inext+0] = this.addCell(0, 0, this.addSegment(lhnchar));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optimize() {
|
||||||
|
this.shrinkBuf();
|
||||||
|
return {
|
||||||
|
byteLength: this.buf.byteLength,
|
||||||
|
char0: this.buf32[CHAR0_SLOT],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fromIterable(hostnames, add) {
|
||||||
|
if ( add === undefined ) { add = 'add'; }
|
||||||
|
const trieRef = this.createOne();
|
||||||
|
for ( const hn of hostnames ) {
|
||||||
|
trieRef[add](hn);
|
||||||
|
}
|
||||||
|
return trieRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(encoder) {
|
||||||
|
if ( encoder instanceof Object ) {
|
||||||
|
return encoder.encode(
|
||||||
|
this.buf32.buffer,
|
||||||
|
this.buf32[CHAR1_SLOT]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Array.from(
|
||||||
|
new Uint32Array(
|
||||||
|
this.buf32.buffer,
|
||||||
|
0,
|
||||||
|
this.buf32[CHAR1_SLOT] + 3 >>> 2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unserialize(selfie, decoder) {
|
||||||
|
this.needle = '';
|
||||||
|
const shouldDecode = typeof selfie === 'string';
|
||||||
|
let byteLength = shouldDecode
|
||||||
|
? decoder.decodeSize(selfie)
|
||||||
|
: selfie.length << 2;
|
||||||
|
if ( byteLength === 0 ) { return false; }
|
||||||
|
byteLength = byteLength + PAGE_SIZE-1 & ~(PAGE_SIZE-1);
|
||||||
|
if ( this.wasmMemory !== null ) {
|
||||||
|
const pageCountBefore = this.buf.length >>> 16;
|
||||||
|
const pageCountAfter = byteLength >>> 16;
|
||||||
|
if ( pageCountAfter > pageCountBefore ) {
|
||||||
|
this.wasmMemory.grow(pageCountAfter - pageCountBefore);
|
||||||
|
this.buf = new Uint8Array(this.wasmMemory.buffer);
|
||||||
|
this.buf32 = new Uint32Array(this.buf.buffer);
|
||||||
|
}
|
||||||
|
} else if ( byteLength > this.buf.length ) {
|
||||||
|
this.buf = new Uint8Array(byteLength);
|
||||||
|
this.buf32 = new Uint32Array(this.buf.buffer);
|
||||||
|
}
|
||||||
|
if ( shouldDecode ) {
|
||||||
|
decoder.decode(selfie, this.buf.buffer);
|
||||||
|
} else {
|
||||||
|
this.buf32.set(selfie);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private methods
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
addCell(idown, iright, v) {
|
||||||
|
let icell = this.buf32[TRIE1_SLOT];
|
||||||
|
this.buf32[TRIE1_SLOT] = icell + 12;
|
||||||
|
icell >>>= 2;
|
||||||
|
this.buf32[icell+0] = idown;
|
||||||
|
this.buf32[icell+1] = iright;
|
||||||
|
this.buf32[icell+2] = v;
|
||||||
|
return icell;
|
||||||
|
}
|
||||||
|
|
||||||
|
addSegment(lsegchar) {
|
||||||
|
if ( lsegchar === 0 ) { return 0; }
|
||||||
|
let char1 = this.buf32[CHAR1_SLOT];
|
||||||
|
const isegchar = char1 - this.buf32[CHAR0_SLOT];
|
||||||
|
let i = lsegchar;
|
||||||
|
do {
|
||||||
|
this.buf[char1++] = this.buf[--i];
|
||||||
|
} while ( i !== 0 );
|
||||||
|
this.buf32[CHAR1_SLOT] = char1;
|
||||||
|
return (lsegchar << 24) | isegchar;
|
||||||
|
}
|
||||||
|
|
||||||
|
growBuf(trieGrow, charGrow) {
|
||||||
|
const char0 = Math.max(
|
||||||
|
(this.buf32[TRIE1_SLOT] + trieGrow + PAGE_SIZE-1) & ~(PAGE_SIZE-1),
|
||||||
|
this.buf32[CHAR0_SLOT]
|
||||||
|
);
|
||||||
|
const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT];
|
||||||
|
const bufLen = Math.max(
|
||||||
|
(char1 + charGrow + PAGE_SIZE-1) & ~(PAGE_SIZE-1),
|
||||||
|
this.buf.length
|
||||||
|
);
|
||||||
|
this.resizeBuf(bufLen, char0);
|
||||||
|
}
|
||||||
|
|
||||||
|
shrinkBuf() {
|
||||||
|
// Can't shrink WebAssembly.Memory
|
||||||
|
if ( this.wasmMemory !== null ) { return; }
|
||||||
|
const char0 = this.buf32[TRIE1_SLOT] + 24;
|
||||||
|
const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT];
|
||||||
|
const bufLen = char1 + 256;
|
||||||
|
this.resizeBuf(bufLen, char0);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeBuf(bufLen, char0) {
|
||||||
|
bufLen = bufLen + PAGE_SIZE-1 & ~(PAGE_SIZE-1);
|
||||||
|
if (
|
||||||
|
bufLen === this.buf.length &&
|
||||||
|
char0 === this.buf32[CHAR0_SLOT]
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const charDataLen = this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT];
|
||||||
|
if ( this.wasmMemory !== null ) {
|
||||||
|
const pageCount = (bufLen >>> 16) - (this.buf.byteLength >>> 16);
|
||||||
|
if ( pageCount > 0 ) {
|
||||||
|
this.wasmMemory.grow(pageCount);
|
||||||
|
this.buf = new Uint8Array(this.wasmMemory.buffer);
|
||||||
|
this.buf32 = new Uint32Array(this.wasmMemory.buffer);
|
||||||
|
}
|
||||||
|
} else if ( bufLen !== this.buf.length ) {
|
||||||
|
const newBuf = new Uint8Array(bufLen);
|
||||||
|
newBuf.set(
|
||||||
|
new Uint8Array(
|
||||||
|
this.buf.buffer,
|
||||||
|
0,
|
||||||
|
this.buf32[TRIE1_SLOT]
|
||||||
|
),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
newBuf.set(
|
||||||
|
new Uint8Array(
|
||||||
|
this.buf.buffer,
|
||||||
|
this.buf32[CHAR0_SLOT],
|
||||||
|
charDataLen
|
||||||
|
),
|
||||||
|
char0
|
||||||
|
);
|
||||||
|
this.buf = newBuf;
|
||||||
|
this.buf32 = new Uint32Array(this.buf.buffer);
|
||||||
|
this.buf32[CHAR0_SLOT] = char0;
|
||||||
|
this.buf32[CHAR1_SLOT] = char0 + charDataLen;
|
||||||
|
}
|
||||||
|
if ( char0 !== this.buf32[CHAR0_SLOT] ) {
|
||||||
|
this.buf.set(
|
||||||
|
new Uint8Array(
|
||||||
|
this.buf.buffer,
|
||||||
|
this.buf32[CHAR0_SLOT],
|
||||||
|
charDataLen
|
||||||
|
),
|
||||||
|
char0
|
||||||
|
);
|
||||||
|
this.buf32[CHAR0_SLOT] = char0;
|
||||||
|
this.buf32[CHAR1_SLOT] = char0 + charDataLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initWASM() {
|
||||||
|
const module = await HNTrieContainer.enableWASM();
|
||||||
|
if ( module instanceof WebAssembly.Module === false ) { return false; }
|
||||||
|
|
||||||
|
if ( this.wasmInstancePromise !== null ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const memory = new WebAssembly.Memory({ initial: 2 });
|
||||||
|
this.wasmInstancePromise = WebAssembly.instantiate(
|
||||||
|
module,
|
||||||
|
{
|
||||||
|
imports: {
|
||||||
|
memory,
|
||||||
|
growBuf: this.growBuf.bind(this, 24, 256)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const instance = await this.wasmInstancePromise;
|
||||||
|
this.wasmMemory = memory;
|
||||||
|
const curPageCount = memory.buffer.byteLength >>> 16;
|
||||||
|
const newPageCount = this.buf.byteLength + PAGE_SIZE-1 >>> 16;
|
||||||
|
if ( newPageCount > curPageCount ) {
|
||||||
|
memory.grow(newPageCount - curPageCount);
|
||||||
|
}
|
||||||
|
const buf = new Uint8Array(memory.buffer);
|
||||||
|
buf.set(this.buf);
|
||||||
|
this.buf = buf;
|
||||||
|
this.buf32 = new Uint32Array(this.buf.buffer);
|
||||||
|
this.matches = this.matchesWASM = instance.exports.matches;
|
||||||
|
this.add = this.addWASM = instance.exports.add;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code below is to attempt to load a WASM module which implements:
|
||||||
|
//
|
||||||
|
// - HNTrieContainer.add()
|
||||||
|
// - HNTrieContainer.matches()
|
||||||
|
//
|
||||||
|
// The WASM module is entirely optional, the JS implementations will be
|
||||||
|
// used should the WASM module be unavailable for whatever reason.
|
||||||
|
static async enableWASM() {
|
||||||
|
if ( HNTrieContainer.wasmModulePromise === undefined ) {
|
||||||
|
HNTrieContainer.wasmModulePromise = null;
|
||||||
|
if (
|
||||||
|
typeof WebAssembly !== 'object' ||
|
||||||
|
typeof WebAssembly.compileStreaming !== 'function'
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Soft-dependency on vAPI so that the code here can be used
|
||||||
|
// outside of uMatrix (i.e. tests, benchmarks)
|
||||||
|
if ( typeof vAPI === 'object' && vAPI.canWASM !== true ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// The wasm module will work only if CPU is natively little-endian,
|
||||||
|
// as we use native uint32 array in our js code.
|
||||||
|
const uint32s = new Uint32Array(1);
|
||||||
|
const uint8s = new Uint8Array(uint32s.buffer);
|
||||||
|
uint32s[0] = 1;
|
||||||
|
if ( uint8s[0] !== 1 ) { return null; }
|
||||||
|
|
||||||
|
HNTrieContainer.wasmModulePromise = fetch(
|
||||||
|
workingDir + 'wasm/hntrie.wasm',
|
||||||
|
{ mode: 'same-origin' }
|
||||||
|
).then(
|
||||||
|
WebAssembly.compileStreaming
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( HNTrieContainer.wasmModulePromise === null ) { return null; }
|
||||||
|
|
||||||
|
let module = null;
|
||||||
|
try {
|
||||||
|
module = await HNTrieContainer.wasmModulePromise;
|
||||||
|
} catch(ex) {
|
||||||
|
HNTrieContainer.wasmModulePromise = null;
|
||||||
|
}
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
HNTrieContainer.prototype.matches = HNTrieContainer.prototype.matchesJS;
|
||||||
|
HNTrieContainer.prototype.matchesWASM = null;
|
||||||
|
|
||||||
|
HNTrieContainer.prototype.add = HNTrieContainer.prototype.addJS;
|
||||||
|
HNTrieContainer.prototype.addWASM = null;
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
Class to hold reference to a specific trie
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
HNTrieContainer.prototype.HNTrieRef = class {
|
||||||
|
|
||||||
|
constructor(container, iroot, addCount, addedCount) {
|
||||||
|
this.container = container;
|
||||||
|
this.iroot = iroot;
|
||||||
|
this.addCount = addCount;
|
||||||
|
this.addedCount = addedCount;
|
||||||
|
this.needle = '';
|
||||||
|
this.last = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(hn) {
|
||||||
|
this.addCount += 1;
|
||||||
|
if ( this.container.setNeedle(hn).add(this.iroot) > 0 ) {
|
||||||
|
this.last = -1;
|
||||||
|
this.needle = '';
|
||||||
|
this.addedCount += 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addJS(hn) {
|
||||||
|
this.addCount += 1;
|
||||||
|
if ( this.container.setNeedle(hn).addJS(this.iroot) > 0 ) {
|
||||||
|
this.last = -1;
|
||||||
|
this.needle = '';
|
||||||
|
this.addedCount += 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addWASM(hn) {
|
||||||
|
this.addCount += 1;
|
||||||
|
if ( this.container.setNeedle(hn).addWASM(this.iroot) > 0 ) {
|
||||||
|
this.last = -1;
|
||||||
|
this.needle = '';
|
||||||
|
this.addedCount += 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(needle) {
|
||||||
|
if ( needle !== this.needle ) {
|
||||||
|
this.needle = needle;
|
||||||
|
this.last = this.container.setNeedle(needle).matches(this.iroot);
|
||||||
|
}
|
||||||
|
return this.last;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesJS(needle) {
|
||||||
|
if ( needle !== this.needle ) {
|
||||||
|
this.needle = needle;
|
||||||
|
this.last = this.container.setNeedle(needle).matchesJS(this.iroot);
|
||||||
|
}
|
||||||
|
return this.last;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchesWASM(needle) {
|
||||||
|
if ( needle !== this.needle ) {
|
||||||
|
this.needle = needle;
|
||||||
|
this.last = this.container.setNeedle(needle).matchesWASM(this.iroot);
|
||||||
|
}
|
||||||
|
return this.last;
|
||||||
|
}
|
||||||
|
|
||||||
|
dump() {
|
||||||
|
let hostnames = Array.from(this);
|
||||||
|
if ( String.prototype.padStart instanceof Function ) {
|
||||||
|
const maxlen = Math.min(
|
||||||
|
hostnames.reduce((maxlen, hn) => Math.max(maxlen, hn.length), 0),
|
||||||
|
64
|
||||||
|
);
|
||||||
|
hostnames = hostnames.map(hn => hn.padStart(maxlen));
|
||||||
|
}
|
||||||
|
for ( const hn of hostnames ) {
|
||||||
|
console.log(hn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return {
|
||||||
|
value: undefined,
|
||||||
|
done: false,
|
||||||
|
next: function() {
|
||||||
|
if ( this.icell === 0 ) {
|
||||||
|
if ( this.forks.length === 0 ) {
|
||||||
|
this.value = undefined;
|
||||||
|
this.done = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this.charPtr = this.forks.pop();
|
||||||
|
this.icell = this.forks.pop();
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
const idown = this.container.buf32[this.icell+0];
|
||||||
|
if ( idown !== 0 ) {
|
||||||
|
this.forks.push(idown, this.charPtr);
|
||||||
|
}
|
||||||
|
const v = this.container.buf32[this.icell+2];
|
||||||
|
let i0 = this.container.buf32[CHAR0_SLOT] + (v & 0x00FFFFFF);
|
||||||
|
const i1 = i0 + (v >>> 24);
|
||||||
|
while ( i0 < i1 ) {
|
||||||
|
this.charPtr -= 1;
|
||||||
|
this.charBuf[this.charPtr] = this.container.buf[i0];
|
||||||
|
i0 += 1;
|
||||||
|
}
|
||||||
|
this.icell = this.container.buf32[this.icell+1];
|
||||||
|
if ( this.icell === 0 ) {
|
||||||
|
return this.toHostname();
|
||||||
|
}
|
||||||
|
if ( this.container.buf32[this.icell+2] === 0 ) {
|
||||||
|
this.icell = this.container.buf32[this.icell+1];
|
||||||
|
return this.toHostname();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toHostname: function() {
|
||||||
|
this.value = this.textDecoder.decode(
|
||||||
|
new Uint8Array(this.charBuf.buffer, this.charPtr)
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
container: this.container,
|
||||||
|
icell: this.iroot,
|
||||||
|
charBuf: new Uint8Array(256),
|
||||||
|
charPtr: 256,
|
||||||
|
forks: [],
|
||||||
|
textDecoder: new TextDecoder()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
HNTrieContainer.prototype.HNTrieRef.prototype.last = -1;
|
||||||
|
HNTrieContainer.prototype.HNTrieRef.prototype.needle = '';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
µMatrix.HNTrieContainer = HNTrieContainer;
|
||||||
|
|
||||||
|
// end of local namespace
|
||||||
|
// *****************************************************************************
|
||||||
|
|
||||||
|
}
|
|
@ -25,18 +25,19 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var listDetails = {},
|
const lastUpdateTemplateString = vAPI.i18n('hostsFilesLastUpdate');
|
||||||
lastUpdateTemplateString = vAPI.i18n('hostsFilesLastUpdate'),
|
const reValidExternalList = /^[a-z-]+:\/\/\S*\/\S+$/m;
|
||||||
hostsFilesSettingsHash,
|
let listDetails = {};
|
||||||
reValidExternalList = /^[a-z-]+:\/\/\S*\/\S+$/m;
|
let hostsFilesSettingsHash;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.messaging.addListener(function onMessage(msg) {
|
vAPI.broadcastListener.add(msg => {
|
||||||
switch ( msg.what ) {
|
switch ( msg.what ) {
|
||||||
case 'assetUpdated':
|
case 'assetUpdated':
|
||||||
updateAssetStatus(msg);
|
updateAssetStatus(msg);
|
||||||
|
@ -58,27 +59,27 @@ vAPI.messaging.addListener(function onMessage(msg) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderNumber = function(value) {
|
const renderNumber = function(value) {
|
||||||
return value.toLocaleString();
|
return value.toLocaleString();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderHostsFiles = function(soft) {
|
const renderHostsFiles = function(soft) {
|
||||||
var listEntryTemplate = uDom('#templates .listEntry'),
|
const listEntryTemplate = uDom('#templates .listEntry');
|
||||||
listStatsTemplate = vAPI.i18n('hostsFilesPerFileStats'),
|
const listStatsTemplate = vAPI.i18n('hostsFilesPerFileStats');
|
||||||
renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString,
|
const renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString;
|
||||||
reExternalHostFile = /^https?:/;
|
const reExternalHostFile = /^https?:/;
|
||||||
|
|
||||||
// Assemble a pretty list name if possible
|
// Assemble a pretty list name if possible
|
||||||
var listNameFromListKey = function(collection, listKey) {
|
const listNameFromListKey = function(collection, listKey) {
|
||||||
let list = collection.get(listKey);
|
let list = collection.get(listKey);
|
||||||
return list && list.title || listKey;
|
return list && list.title || listKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
var liFromListEntry = function(collection, listKey, li) {
|
const liFromListEntry = function(collection, listKey, li) {
|
||||||
var entry = collection.get(listKey),
|
const entry = collection.get(listKey);
|
||||||
elem;
|
let elem;
|
||||||
if ( !li ) {
|
if ( !li ) {
|
||||||
li = listEntryTemplate.clone().nodeAt(0);
|
li = listEntryTemplate.clone().nodeAt(0);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +107,7 @@ var renderHostsFiles = function(soft) {
|
||||||
elem.checked = entry.selected === true;
|
elem.checked = entry.selected === true;
|
||||||
}
|
}
|
||||||
elem = li.querySelector('span.counts');
|
elem = li.querySelector('span.counts');
|
||||||
var text = '';
|
let text = '';
|
||||||
if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
|
if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
|
||||||
text = listStatsTemplate
|
text = listStatsTemplate
|
||||||
.replace('{{used}}', renderNumber(entry.selected ? entry.entryUsedCount : 0))
|
.replace('{{used}}', renderNumber(entry.selected ? entry.entryUsedCount : 0))
|
||||||
|
@ -114,8 +115,8 @@ var renderHostsFiles = function(soft) {
|
||||||
}
|
}
|
||||||
elem.textContent = text;
|
elem.textContent = text;
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/104
|
// https://github.com/chrisaljoudi/uBlock/issues/104
|
||||||
var asset = listDetails.cache[listKey] || {};
|
const asset = listDetails.cache[listKey] || {};
|
||||||
var remoteURL = asset.remoteURL;
|
const remoteURL = asset.remoteURL;
|
||||||
li.classList.toggle(
|
li.classList.toggle(
|
||||||
'unsecure',
|
'unsecure',
|
||||||
typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0
|
typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0
|
||||||
|
@ -132,50 +133,50 @@ var renderHostsFiles = function(soft) {
|
||||||
li.classList.remove('discard');
|
li.classList.remove('discard');
|
||||||
return li;
|
return li;
|
||||||
};
|
};
|
||||||
var onRenderAssetFiles = function(collection, listSelector) {
|
const onRenderAssetFiles = function(collection, listSelector) {
|
||||||
// Incremental rendering: this will allow us to easily discard unused
|
// Incremental rendering: this will allow us to easily discard unused
|
||||||
// DOM list entries.
|
// DOM list entries.
|
||||||
uDom(listSelector + ' .listEntry:not(.notAnAsset)').addClass('discard');
|
uDom(listSelector + ' .listEntry:not(.notAnAsset)').addClass('discard');
|
||||||
|
|
||||||
var assetKeys = Array.from(collection.keys());
|
const assetKeys = Array.from(collection.keys());
|
||||||
|
|
||||||
// Sort works this way:
|
// Sort works this way:
|
||||||
// - Send /^https?:/ items at the end (custom hosts file URL)
|
// - Send /^https?:/ items at the end (custom hosts file URL)
|
||||||
assetKeys.sort(function(a, b) {
|
assetKeys.sort(function(a, b) {
|
||||||
let ea = collection.get(a),
|
const ea = collection.get(a);
|
||||||
eb = collection.get(b);
|
const eb = collection.get(b);
|
||||||
if ( ea.submitter !== eb.submitter ) {
|
if ( ea.submitter !== eb.submitter ) {
|
||||||
return ea.submitter !== 'user' ? -1 : 1;
|
return ea.submitter !== 'user' ? -1 : 1;
|
||||||
}
|
}
|
||||||
let ta = ea.title || a,
|
const ta = ea.title || a;
|
||||||
tb = eb.title || b;
|
const tb = eb.title || b;
|
||||||
if ( reExternalHostFile.test(ta) === reExternalHostFile.test(tb) ) {
|
if ( reExternalHostFile.test(ta) === reExternalHostFile.test(tb) ) {
|
||||||
return ta.localeCompare(tb);
|
return ta.localeCompare(tb);
|
||||||
}
|
}
|
||||||
return reExternalHostFile.test(tb) ? -1 : 1;
|
return reExternalHostFile.test(tb) ? -1 : 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
let ulList = document.querySelector(listSelector),
|
const ulList = document.querySelector(listSelector);
|
||||||
liLast = ulList.querySelector('.notAnAsset');
|
const liLast = ulList.querySelector('.notAnAsset');
|
||||||
|
|
||||||
for ( let i = 0; i < assetKeys.length; i++ ) {
|
for ( let i = 0; i < assetKeys.length; i++ ) {
|
||||||
let liReuse = i < ulList.childElementCount ?
|
let liReuse = i < ulList.childElementCount
|
||||||
ulList.children[i] :
|
? ulList.children[i]
|
||||||
null;
|
: null;
|
||||||
if (
|
if (
|
||||||
liReuse !== null &&
|
liReuse !== null &&
|
||||||
liReuse.classList.contains('notAnAsset')
|
liReuse.classList.contains('notAnAsset')
|
||||||
) {
|
) {
|
||||||
liReuse = null;
|
liReuse = null;
|
||||||
}
|
}
|
||||||
let liEntry = liFromListEntry(collection, assetKeys[i], liReuse);
|
const liEntry = liFromListEntry(collection, assetKeys[i], liReuse);
|
||||||
if ( liEntry.parentElement === null ) {
|
if ( liEntry.parentElement === null ) {
|
||||||
ulList.insertBefore(liEntry, liLast);
|
ulList.insertBefore(liEntry, liLast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onAssetDataReceived = function(details) {
|
const onAssetDataReceived = function(details) {
|
||||||
// Preprocess.
|
// Preprocess.
|
||||||
details.hosts = new Map(details.hosts);
|
details.hosts = new Map(details.hosts);
|
||||||
details.recipes = new Map(details.recipes);
|
details.recipes = new Map(details.recipes);
|
||||||
|
@ -213,16 +214,16 @@ var renderHostsFiles = function(soft) {
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('dashboard', {
|
||||||
'hosts-files.js',
|
what: 'getAssets',
|
||||||
{ what: 'getAssets' },
|
}).then(details => {
|
||||||
onAssetDataReceived
|
onAssetDataReceived(details);
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderWidgets = function() {
|
const renderWidgets = function() {
|
||||||
uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('body:not(.updating) .assets .listEntry.obsolete > input[type="checkbox"]:checked') === null);
|
uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('body:not(.updating) .assets .listEntry.obsolete > input[type="checkbox"]:checked') === null);
|
||||||
uDom('#buttonPurgeAll').toggleClass('disabled', document.querySelector('.assets .listEntry.cached') === null);
|
uDom('#buttonPurgeAll').toggleClass('disabled', document.querySelector('.assets .listEntry.cached') === null);
|
||||||
uDom('#buttonApply').toggleClass('disabled', hostsFilesSettingsHash === hashFromCurrentFromSettings());
|
uDom('#buttonApply').toggleClass('disabled', hostsFilesSettingsHash === hashFromCurrentFromSettings());
|
||||||
|
@ -230,8 +231,8 @@ var renderWidgets = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var updateAssetStatus = function(details) {
|
const updateAssetStatus = function(details) {
|
||||||
var li = document.querySelector('.assets .listEntry[data-listkey="' + details.key + '"]');
|
const li = document.querySelector('.assets .listEntry[data-listkey="' + details.key + '"]');
|
||||||
if ( li === null ) { return; }
|
if ( li === null ) { return; }
|
||||||
li.classList.toggle('failed', !!details.failed);
|
li.classList.toggle('failed', !!details.failed);
|
||||||
li.classList.toggle('obsolete', !details.cached);
|
li.classList.toggle('obsolete', !details.cached);
|
||||||
|
@ -255,12 +256,12 @@ var updateAssetStatus = function(details) {
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var hashFromCurrentFromSettings = function() {
|
const hashFromCurrentFromSettings = function() {
|
||||||
let listHash = [],
|
const listHash = [];
|
||||||
listEntries = document.querySelectorAll(
|
const listEntries = document.querySelectorAll(
|
||||||
'.assets .listEntry[data-listkey]:not(.toRemove)'
|
'.assets .listEntry[data-listkey]:not(.toRemove)'
|
||||||
);
|
);
|
||||||
for ( let liEntry of listEntries ) {
|
for ( const liEntry of listEntries ) {
|
||||||
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
||||||
listHash.push(liEntry.getAttribute('data-listkey'));
|
listHash.push(liEntry.getAttribute('data-listkey'));
|
||||||
}
|
}
|
||||||
|
@ -289,7 +290,7 @@ var hashFromCurrentFromSettings = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var textFromTextarea = function(textarea) {
|
const textFromTextarea = function(textarea) {
|
||||||
if ( typeof textarea === 'string' ) {
|
if ( typeof textarea === 'string' ) {
|
||||||
textarea = document.querySelector(textarea);
|
textarea = document.querySelector(textarea);
|
||||||
}
|
}
|
||||||
|
@ -298,15 +299,15 @@ var textFromTextarea = function(textarea) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onHostsFilesSettingsChanged = function() {
|
const onHostsFilesSettingsChanged = function() {
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onRemoveExternalAsset = function(ev) {
|
const onRemoveExternalAsset = function(ev) {
|
||||||
var liEntry = uDom(this).ancestors('[data-listkey]'),
|
const liEntry = uDom(this).ancestors('[data-listkey]');
|
||||||
listKey = liEntry.attr('data-listkey');
|
const listKey = liEntry.attr('data-listkey');
|
||||||
if ( listKey ) {
|
if ( listKey ) {
|
||||||
liEntry.toggleClass('toRemove');
|
liEntry.toggleClass('toRemove');
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
|
@ -316,13 +317,16 @@ var onRemoveExternalAsset = function(ev) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onPurgeClicked = function() {
|
const onPurgeClicked = function(ev) {
|
||||||
var button = uDom(this),
|
const button = uDom(ev.target);
|
||||||
liEntry = button.ancestors('[data-listkey]'),
|
const liEntry = button.ancestors('[data-listkey]');
|
||||||
listKey = liEntry.attr('data-listkey');
|
const listKey = liEntry.attr('data-listkey');
|
||||||
if ( !listKey ) { return; }
|
if ( !listKey ) { return; }
|
||||||
|
|
||||||
vAPI.messaging.send('hosts-files.js', { what: 'purgeCache', assetKey: listKey });
|
vAPI.messaging.send('dashboard', {
|
||||||
|
what: 'purgeCache',
|
||||||
|
assetKey: listKey,
|
||||||
|
});
|
||||||
liEntry.addClass('obsolete');
|
liEntry.addClass('obsolete');
|
||||||
liEntry.removeClass('cached');
|
liEntry.removeClass('cached');
|
||||||
|
|
||||||
|
@ -333,9 +337,9 @@ var onPurgeClicked = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var selectAssets = function(callback) {
|
const selectAssets = function() {
|
||||||
var prepareChanges = function(listSelector) {
|
const prepareChanges = function(listSelector) {
|
||||||
var out = {
|
const out = {
|
||||||
toSelect: [],
|
toSelect: [],
|
||||||
toImport: '',
|
toImport: '',
|
||||||
toRemove: [],
|
toRemove: [],
|
||||||
|
@ -345,13 +349,13 @@ var selectAssets = function(callback) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let root = document.querySelector(listSelector);
|
const root = document.querySelector(listSelector);
|
||||||
|
|
||||||
// Lists to select or remove
|
// Lists to select or remove
|
||||||
let liEntries = root.querySelectorAll(
|
const liEntries = root.querySelectorAll(
|
||||||
'.listEntry[data-listkey]:not(.notAnAsset)'
|
'.listEntry[data-listkey]:not(.notAnAsset)'
|
||||||
);
|
);
|
||||||
for ( let liEntry of liEntries ) {
|
for ( const liEntry of liEntries ) {
|
||||||
if ( liEntry.classList.contains('toRemove') ) {
|
if ( liEntry.classList.contains('toRemove') ) {
|
||||||
out.toRemove.push(liEntry.getAttribute('data-listkey'));
|
out.toRemove.push(liEntry.getAttribute('data-listkey'));
|
||||||
} else if ( liEntry.querySelector('input[type="checkbox"]:checked') ) {
|
} else if ( liEntry.querySelector('input[type="checkbox"]:checked') ) {
|
||||||
|
@ -360,11 +364,11 @@ var selectAssets = function(callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// External hosts files to import
|
// External hosts files to import
|
||||||
let input = root.querySelector(
|
const input = root.querySelector(
|
||||||
'.toImport > input[type="checkbox"]:checked'
|
'.toImport > input[type="checkbox"]:checked'
|
||||||
);
|
);
|
||||||
if ( input !== null ) {
|
if ( input !== null ) {
|
||||||
let textarea = root.querySelector('.toImport textarea');
|
const textarea = root.querySelector('.toImport textarea');
|
||||||
out.toImport = textarea.value.trim();
|
out.toImport = textarea.value.trim();
|
||||||
textarea.value = '';
|
textarea.value = '';
|
||||||
input.checked = false;
|
input.checked = false;
|
||||||
|
@ -379,29 +383,26 @@ var selectAssets = function(callback) {
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.messaging.send(
|
|
||||||
'hosts-files.js',
|
|
||||||
{
|
|
||||||
what: 'selectAssets',
|
|
||||||
hosts: prepareChanges('#hosts'),
|
|
||||||
recipes: prepareChanges('#recipes')
|
|
||||||
},
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
|
|
||||||
hostsFilesSettingsHash = hashFromCurrentFromSettings();
|
hostsFilesSettingsHash = hashFromCurrentFromSettings();
|
||||||
|
|
||||||
|
return vAPI.messaging.send('dashboard', {
|
||||||
|
what: 'selectAssets',
|
||||||
|
hosts: prepareChanges('#hosts'),
|
||||||
|
recipes: prepareChanges('#recipes'),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var buttonApplyHandler = function() {
|
const buttonApplyHandler = function() {
|
||||||
uDom('#buttonApply').removeClass('enabled');
|
uDom('#buttonApply').removeClass('enabled');
|
||||||
selectAssets(function(response) {
|
selectAssets().then(response => {
|
||||||
if ( response && response.hostsChanged ) {
|
if ( response instanceof Object === false ) { return; }
|
||||||
vAPI.messaging.send('hosts-files.js', { what: 'reloadHostsFiles' });
|
if ( response.hostsChanged ) {
|
||||||
|
vAPI.messaging.send('dashboard', { what: 'reloadHostsFiles' });
|
||||||
}
|
}
|
||||||
if ( response && response.recipesChanged ) {
|
if ( response.recipesChanged ) {
|
||||||
vAPI.messaging.send('hosts-files.js', { what: 'reloadRecipeFiles' });
|
vAPI.messaging.send('dashboard', { what: 'reloadRecipeFiles' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
|
@ -409,11 +410,11 @@ var buttonApplyHandler = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var buttonUpdateHandler = function() {
|
const buttonUpdateHandler = function() {
|
||||||
uDom('#buttonUpdate').removeClass('enabled');
|
uDom('#buttonUpdate').removeClass('enabled');
|
||||||
selectAssets(function() {
|
selectAssets().then(( ) => {
|
||||||
document.body.classList.add('updating');
|
document.body.classList.add('updating');
|
||||||
vAPI.messaging.send('hosts-files.js', { what: 'forceUpdateAssets' });
|
vAPI.messaging.send('dashboard', { what: 'forceUpdateAssets' });
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
});
|
});
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
|
@ -421,28 +422,23 @@ var buttonUpdateHandler = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var buttonPurgeAllHandler = function() {
|
const buttonPurgeAllHandler = function() {
|
||||||
uDom('#buttonPurgeAll').removeClass('enabled');
|
uDom('#buttonPurgeAll').removeClass('enabled');
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('dashboard', {
|
||||||
'hosts-files.js',
|
what: 'purgeAllCaches',
|
||||||
{ what: 'purgeAllCaches' },
|
}).then(( ) => {
|
||||||
function() {
|
renderHostsFiles(true);
|
||||||
renderHostsFiles(true);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var autoUpdateCheckboxChanged = function() {
|
const autoUpdateCheckboxChanged = function(ev) {
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('dashboard', {
|
||||||
'hosts-files.js',
|
what: 'userSettings',
|
||||||
{
|
name: 'autoUpdate',
|
||||||
what: 'userSettings',
|
value: ev.target.checked,
|
||||||
name: 'autoUpdate',
|
});
|
||||||
value: this.checked
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -460,5 +456,5 @@ renderHostsFiles();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
|
|
|
@ -19,14 +19,12 @@
|
||||||
Home: https://github.com/gorhill/uMatrix
|
Home: https://github.com/gorhill/uMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global chrome, µMatrix */
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
var µm = µMatrix;
|
const µm = µMatrix;
|
||||||
µm.pMatrix = new µm.Matrix();
|
µm.pMatrix = new µm.Matrix();
|
||||||
µm.pMatrix.setSwitch('matrix-off', 'about-scheme', 1);
|
µm.pMatrix.setSwitch('matrix-off', 'about-scheme', 1);
|
||||||
µm.pMatrix.setSwitch('matrix-off', 'chrome-extension-scheme', 1);
|
µm.pMatrix.setSwitch('matrix-off', 'chrome-extension-scheme', 1);
|
||||||
|
@ -42,6 +40,7 @@
|
||||||
// Global rules
|
// Global rules
|
||||||
µm.pMatrix.setSwitch('referrer-spoof', '*', 1);
|
µm.pMatrix.setSwitch('referrer-spoof', '*', 1);
|
||||||
µm.pMatrix.setSwitch('noscript-spoof', '*', 1);
|
µm.pMatrix.setSwitch('noscript-spoof', '*', 1);
|
||||||
|
µm.pMatrix.setSwitch('cname-reveal', '*', 1);
|
||||||
µm.pMatrix.setCell('*', '*', '*', µm.Matrix.Red);
|
µm.pMatrix.setCell('*', '*', '*', µm.Matrix.Red);
|
||||||
µm.pMatrix.setCell('*', '*', 'css', µm.Matrix.Green);
|
µm.pMatrix.setCell('*', '*', 'css', µm.Matrix.Green);
|
||||||
µm.pMatrix.setCell('*', '*', 'image', µm.Matrix.Green);
|
µm.pMatrix.setCell('*', '*', 'image', µm.Matrix.Green);
|
||||||
|
@ -52,7 +51,7 @@
|
||||||
|
|
||||||
µm.tMatrix = new µm.Matrix();
|
µm.tMatrix = new µm.Matrix();
|
||||||
µm.tMatrix.assign(µm.pMatrix);
|
µm.tMatrix.assign(µm.pMatrix);
|
||||||
})();
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
224
src/js/i18n.js
224
src/js/i18n.js
|
@ -19,16 +19,15 @@
|
||||||
Home: https://github.com/gorhill/uMatrix
|
Home: https://github.com/gorhill/uMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global vAPI, uDom */
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// This file should always be included at the end of the `body` tag, so as
|
// This file should always be included at the end of the `body` tag, so as
|
||||||
// to ensure all i18n targets are already loaded.
|
// to ensure all i18n targets are already loaded.
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -41,28 +40,18 @@
|
||||||
// No HTML entities are allowed, there is code to handle existing HTML
|
// No HTML entities are allowed, there is code to handle existing HTML
|
||||||
// entities already present in translation files until they are all gone.
|
// entities already present in translation files until they are all gone.
|
||||||
|
|
||||||
var reSafeTags = /^([\s\S]*?)<(b|blockquote|code|em|i|kbd|span|sup)>(.+?)<\/\2>([\s\S]*)$/,
|
const reSafeTags = /^([\s\S]*?)<(b|code|em|i|span)>(.+?)<\/\2>([\s\S]*)$/;
|
||||||
reSafeInput = /^([\s\S]*?)<(input type="[^"]+")>(.*?)([\s\S]*)$/,
|
const reSafeLink = /^([\s\S]*?)<(a href=['"]https:\/\/[^'" <>]+['"])>(.+?)<\/a>([\s\S]*)$/;
|
||||||
reInput = /^input type=(['"])([a-z]+)\1$/,
|
const reLink = /^a href=(['"])(https:\/\/[^'"]+)\1$/;
|
||||||
reSafeLink = /^([\s\S]*?)<(a href=['"]https?:\/\/[^'" <>]+['"])>(.+?)<\/a>([\s\S]*)$/,
|
|
||||||
reLink = /^a href=(['"])(https?:\/\/[^'"]+)\1$/;
|
|
||||||
|
|
||||||
var safeTextToTagNode = function(text) {
|
const safeTextToTagNode = function(text) {
|
||||||
var matches, node;
|
|
||||||
if ( text.lastIndexOf('a ', 0) === 0 ) {
|
if ( text.lastIndexOf('a ', 0) === 0 ) {
|
||||||
matches = reLink.exec(text);
|
const matches = reLink.exec(text);
|
||||||
if ( matches === null ) { return null; }
|
if ( matches === null ) { return null; }
|
||||||
node = document.createElement('a');
|
const node = document.createElement('a');
|
||||||
node.setAttribute('href', matches[2]);
|
node.setAttribute('href', matches[2]);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
if ( text.lastIndexOf('input ', 0) === 0 ) {
|
|
||||||
matches = reInput.exec(text);
|
|
||||||
if ( matches === null ) { return null; }
|
|
||||||
node = document.createElement('input');
|
|
||||||
node.setAttribute('type', matches[2]);
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
// Firefox extension validator warns if using a variable as argument for
|
// Firefox extension validator warns if using a variable as argument for
|
||||||
// document.createElement().
|
// document.createElement().
|
||||||
switch ( text ) {
|
switch ( text ) {
|
||||||
|
@ -87,92 +76,162 @@ var safeTextToTagNode = function(text) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var safeTextToTextNode = function(text) {
|
const safeTextToTextNode = (( ) => {
|
||||||
// TODO: remove once no more HTML entities in translation files.
|
const entities = new Map([
|
||||||
if ( text.indexOf('&') !== -1 ) {
|
// TODO: Remove quote entities once no longer present in translation
|
||||||
text = text.replace(/“/g, '“')
|
// files. Other entities must stay.
|
||||||
.replace(/”/g, '”')
|
[ '“', '“' ],
|
||||||
.replace(/‘/g, '‘')
|
[ '”', '”' ],
|
||||||
.replace(/’/g, '’');
|
[ '‘', '‘' ],
|
||||||
}
|
[ '’', '’' ],
|
||||||
return document.createTextNode(text);
|
[ '<', '<' ],
|
||||||
};
|
[ '>', '>' ],
|
||||||
|
]);
|
||||||
|
const decodeEntities = match => {
|
||||||
|
return entities.get(match) || match;
|
||||||
|
};
|
||||||
|
return function(text) {
|
||||||
|
if ( text.indexOf('&') !== -1 ) {
|
||||||
|
text = text.replace(/&[a-z]+;/g, decodeEntities);
|
||||||
|
}
|
||||||
|
return document.createTextNode(text);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
var safeTextToDOM = function(text, parent) {
|
const safeTextToDOM = function(text, parent) {
|
||||||
if ( text === '' ) { return; }
|
if ( text === '' ) { return; }
|
||||||
|
|
||||||
// Fast path (most common).
|
// Fast path (most common).
|
||||||
if ( text.indexOf('<') === -1 ) {
|
if ( text.indexOf('<') === -1 ) {
|
||||||
return parent.appendChild(safeTextToTextNode(text));
|
parent.appendChild(safeTextToTextNode(text));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
// Slow path.
|
// Slow path.
|
||||||
// `<p>` no longer allowed. Code below can be remove once all <p>'s are
|
// `<p>` no longer allowed. Code below can be removed once all <p>'s are
|
||||||
// gone from translation files.
|
// gone from translation files.
|
||||||
text = text.replace(/^<p>|<\/p>/g, '')
|
text = text.replace(/^<p>|<\/p>/g, '')
|
||||||
.replace(/<p>/g, '\n\n');
|
.replace(/<p>/g, '\n\n');
|
||||||
// Parse allowed HTML tags.
|
// Parse allowed HTML tags.
|
||||||
var matches,
|
let matches = reSafeTags.exec(text);
|
||||||
matches1 = reSafeTags.exec(text),
|
|
||||||
matches2 = reSafeLink.exec(text);
|
|
||||||
if ( matches1 !== null && matches2 !== null ) {
|
|
||||||
matches = matches1.index < matches2.index ? matches1 : matches2;
|
|
||||||
} else if ( matches1 !== null ) {
|
|
||||||
matches = matches1;
|
|
||||||
} else if ( matches2 !== null ) {
|
|
||||||
matches = matches2;
|
|
||||||
} else {
|
|
||||||
matches = reSafeInput.exec(text);
|
|
||||||
}
|
|
||||||
if ( matches === null ) {
|
if ( matches === null ) {
|
||||||
parent.appendChild(safeTextToTextNode(text));
|
matches = reSafeLink.exec(text);
|
||||||
return;
|
if ( matches === null ) {
|
||||||
|
parent.appendChild(safeTextToTextNode(text));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
safeTextToDOM(matches[1], parent);
|
const fragment = document.createDocumentFragment();
|
||||||
var node = safeTextToTagNode(matches[2]) || parent;
|
safeTextToDOM(matches[1], fragment);
|
||||||
|
let node = safeTextToTagNode(matches[2]);
|
||||||
safeTextToDOM(matches[3], node);
|
safeTextToDOM(matches[3], node);
|
||||||
parent.appendChild(node);
|
fragment.appendChild(node);
|
||||||
safeTextToDOM(matches[4], parent);
|
safeTextToDOM(matches[4], fragment);
|
||||||
|
parent.appendChild(fragment);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
vAPI.i18n.safeTemplateToDOM = function(id, dict, parent) {
|
||||||
|
if ( parent === undefined ) {
|
||||||
|
parent = document.createDocumentFragment();
|
||||||
|
}
|
||||||
|
let textin = vAPI.i18n(id);
|
||||||
|
if ( textin === '' ) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
if ( textin.indexOf('{{') === -1 ) {
|
||||||
|
safeTextToDOM(textin, parent);
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
const re = /\{\{\w+\}\}/g;
|
||||||
|
let textout = '';
|
||||||
|
for (;;) {
|
||||||
|
let match = re.exec(textin);
|
||||||
|
if ( match === null ) {
|
||||||
|
textout += textin;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
textout += textin.slice(0, match.index);
|
||||||
|
let prop = match[0].slice(2, -2);
|
||||||
|
if ( dict.hasOwnProperty(prop) ) {
|
||||||
|
textout += dict[prop].replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
} else {
|
||||||
|
textout += prop;
|
||||||
|
}
|
||||||
|
textin = textin.slice(re.lastIndex);
|
||||||
|
}
|
||||||
|
safeTextToDOM(textout, parent);
|
||||||
|
return parent;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Helper to deal with the i18n'ing of HTML files.
|
// Helper to deal with the i18n'ing of HTML files.
|
||||||
vAPI.i18n.render = function(context) {
|
vAPI.i18n.render = function(context) {
|
||||||
var docu = document,
|
const docu = document;
|
||||||
root = context || docu,
|
const root = context || docu;
|
||||||
elems, n, i, elem, text;
|
|
||||||
|
|
||||||
elems = root.querySelectorAll('[data-i18n]');
|
for ( const elem of root.querySelectorAll('[data-i18n]') ) {
|
||||||
n = elems.length;
|
let text = vAPI.i18n(elem.getAttribute('data-i18n'));
|
||||||
for ( i = 0; i < n; i++ ) {
|
|
||||||
elem = elems[i];
|
|
||||||
text = vAPI.i18n(elem.getAttribute('data-i18n'));
|
|
||||||
if ( !text ) { continue; }
|
if ( !text ) { continue; }
|
||||||
// TODO: remove once it's all replaced with <input type="...">
|
if ( text.indexOf('{{') === -1 ) {
|
||||||
if ( text.indexOf('{') !== -1 ) {
|
safeTextToDOM(text, elem);
|
||||||
text = text.replace(/\{\{input:([^}]+)\}\}/g, '<input type="$1">');
|
continue;
|
||||||
}
|
}
|
||||||
safeTextToDOM(text, elem);
|
// Handle selector-based placeholders: these placeholders tell where
|
||||||
|
// existing child DOM element are to be positioned relative to the
|
||||||
|
// localized text nodes.
|
||||||
|
const parts = text.split(/(\{\{[^}]+\}\})/);
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
let textBefore = '';
|
||||||
|
for ( let part of parts ) {
|
||||||
|
if ( part === '' ) { continue; }
|
||||||
|
if ( part.startsWith('{{') && part.endsWith('}}') ) {
|
||||||
|
// TODO: remove detection of ':' once it no longer appears
|
||||||
|
// in translation files.
|
||||||
|
const pos = part.indexOf(':');
|
||||||
|
if ( pos !== -1 ) {
|
||||||
|
part = part.slice(0, pos) + part.slice(-2);
|
||||||
|
}
|
||||||
|
const node = elem.querySelector(part.slice(2, -2));
|
||||||
|
if ( node !== null ) {
|
||||||
|
safeTextToDOM(textBefore, fragment);
|
||||||
|
fragment.appendChild(node);
|
||||||
|
textBefore = '';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textBefore += part;
|
||||||
|
}
|
||||||
|
if ( textBefore !== '' ) {
|
||||||
|
safeTextToDOM(textBefore, fragment);
|
||||||
|
}
|
||||||
|
elem.appendChild(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
uDom('[title]', context).forEach(function(elem) {
|
for ( const elem of root.querySelectorAll('[data-i18n-title]') ) {
|
||||||
var title = vAPI.i18n(elem.attr('title'));
|
const text = vAPI.i18n(elem.getAttribute('data-i18n-title'));
|
||||||
if ( title ) {
|
if ( !text ) { continue; }
|
||||||
elem.attr('title', title);
|
elem.setAttribute('title', text);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
uDom('[placeholder]', context).forEach(function(elem) {
|
for ( const elem of root.querySelectorAll('[placeholder]') ) {
|
||||||
elem.attr('placeholder', vAPI.i18n(elem.attr('placeholder')));
|
elem.setAttribute(
|
||||||
});
|
'placeholder',
|
||||||
|
vAPI.i18n(elem.getAttribute('placeholder'))
|
||||||
uDom('[data-i18n-tip]', context).forEach(function(elem) {
|
|
||||||
elem.attr(
|
|
||||||
'data-tip',
|
|
||||||
vAPI.i18n(elem.attr('data-i18n-tip'))
|
|
||||||
.replace(/<br>/g, '\n')
|
|
||||||
.replace(/\n{3,}/g, '\n\n')
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
for ( const elem of root.querySelectorAll('[data-i18n-tip]') ) {
|
||||||
|
const text = vAPI.i18n(elem.getAttribute('data-i18n-tip'))
|
||||||
|
.replace(/<br>/g, '\n')
|
||||||
|
.replace(/\n{3,}/g, '\n\n');
|
||||||
|
elem.setAttribute('data-tip', text);
|
||||||
|
if ( elem.getAttribute('aria-label') === 'data-tip' ) {
|
||||||
|
elem.setAttribute('aria-label', text);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.i18n.render();
|
vAPI.i18n.render();
|
||||||
|
@ -180,7 +239,7 @@ vAPI.i18n.render();
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
vAPI.i18n.renderElapsedTimeToString = function(tstamp) {
|
vAPI.i18n.renderElapsedTimeToString = function(tstamp) {
|
||||||
var value = (Date.now() - tstamp) / 60000;
|
let value = (Date.now() - tstamp) / 60000;
|
||||||
if ( value < 2 ) {
|
if ( value < 2 ) {
|
||||||
return vAPI.i18n('elapsedOneMinuteAgo');
|
return vAPI.i18n('elapsedOneMinuteAgo');
|
||||||
}
|
}
|
||||||
|
@ -203,6 +262,7 @@ vAPI.i18n.renderElapsedTimeToString = function(tstamp) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µMatrix.LiquidDict = (function() {
|
µMatrix.LiquidDict = (( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ LiquidDict.prototype.reset = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
let selfieVersion = 1;
|
const selfieVersion = 1;
|
||||||
|
|
||||||
LiquidDict.prototype.toSelfie = function() {
|
LiquidDict.prototype.toSelfie = function() {
|
||||||
this.freeze();
|
this.freeze();
|
||||||
|
|
2792
src/js/logger-ui.js
2792
src/js/logger-ui.js
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
uMatrix - a browser extension to block requests.
|
uBlock Origin - a browser extension to block requests.
|
||||||
Copyright (C) 2015-present Raymond Hill
|
Copyright (C) 2015-present Raymond Hill
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -26,58 +26,57 @@
|
||||||
|
|
||||||
µMatrix.logger = (function() {
|
µMatrix.logger = (function() {
|
||||||
|
|
||||||
let LogEntry = function(details) {
|
|
||||||
this.init(details);
|
|
||||||
};
|
|
||||||
|
|
||||||
LogEntry.prototype.init = function(details) {
|
|
||||||
this.tstamp = Date.now();
|
|
||||||
this.details = JSON.stringify(details);
|
|
||||||
};
|
|
||||||
|
|
||||||
let buffer = null;
|
let buffer = null;
|
||||||
let lastReadTime = 0;
|
let lastReadTime = 0;
|
||||||
let writePtr = 0;
|
let writePtr = 0;
|
||||||
|
|
||||||
// After 60 seconds without being read, a buffer will be considered
|
// After 60 seconds without being read, a buffer will be considered
|
||||||
// unused, and thus removed from memory.
|
// unused, and thus removed from memory.
|
||||||
let logBufferObsoleteAfter = 30 * 1000;
|
const logBufferObsoleteAfter = 30 * 1000;
|
||||||
|
|
||||||
let janitor = function() {
|
const janitor = ( ) => {
|
||||||
if (
|
if (
|
||||||
buffer !== null &&
|
buffer !== null &&
|
||||||
lastReadTime < (Date.now() - logBufferObsoleteAfter)
|
lastReadTime < (Date.now() - logBufferObsoleteAfter)
|
||||||
) {
|
) {
|
||||||
|
api.enabled = false;
|
||||||
buffer = null;
|
buffer = null;
|
||||||
writePtr = 0;
|
writePtr = 0;
|
||||||
api.ownerId = undefined;
|
api.ownerId = undefined;
|
||||||
api.enabled = false;
|
vAPI.messaging.broadcast({ what: 'loggerDisabled' });
|
||||||
}
|
}
|
||||||
if ( buffer !== null ) {
|
if ( buffer !== null ) {
|
||||||
vAPI.setTimeout(janitor, logBufferObsoleteAfter);
|
vAPI.setTimeout(janitor, logBufferObsoleteAfter);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let api = {
|
const boxEntry = function(details) {
|
||||||
|
if ( details.tstamp === undefined ) {
|
||||||
|
details.tstamp = Date.now();
|
||||||
|
}
|
||||||
|
return JSON.stringify(details);
|
||||||
|
};
|
||||||
|
|
||||||
|
const api = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
ownerId: undefined,
|
ownerId: undefined,
|
||||||
writeOne: function(details) {
|
writeOne: function(details) {
|
||||||
if ( buffer === null ) { return; }
|
if ( buffer === null ) { return; }
|
||||||
if ( writePtr === buffer.length ) {
|
if ( writePtr === buffer.length ) {
|
||||||
buffer.push(new LogEntry(details));
|
buffer.push(boxEntry(details));
|
||||||
} else {
|
} else {
|
||||||
buffer[writePtr].init(details);
|
buffer[writePtr] = boxEntry(details);
|
||||||
}
|
}
|
||||||
writePtr += 1;
|
writePtr += 1;
|
||||||
},
|
},
|
||||||
readAll: function(ownerId) {
|
readAll: function(ownerId) {
|
||||||
this.ownerId = ownerId;
|
this.ownerId = ownerId;
|
||||||
this.enabled = true;
|
|
||||||
if ( buffer === null ) {
|
if ( buffer === null ) {
|
||||||
|
this.enabled = true;
|
||||||
buffer = [];
|
buffer = [];
|
||||||
vAPI.setTimeout(janitor, logBufferObsoleteAfter);
|
vAPI.setTimeout(janitor, logBufferObsoleteAfter);
|
||||||
}
|
}
|
||||||
let out = buffer.slice(0, writePtr);
|
const out = buffer.slice(0, writePtr);
|
||||||
writePtr = 0;
|
writePtr = 0;
|
||||||
lastReadTime = Date.now();
|
lastReadTime = Date.now();
|
||||||
return out;
|
return out;
|
||||||
|
@ -85,6 +84,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
return api;
|
return api;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
210
src/js/lz4.js
Normal file
210
src/js/lz4.js
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
uBlock Origin - a browser extension to block requests.
|
||||||
|
Copyright (C) 2018-present Raymond Hill
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/uBlock
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global lz4BlockCodec */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
Experimental support for storage compression.
|
||||||
|
|
||||||
|
For background information on the topic, see:
|
||||||
|
https://github.com/uBlockOrigin/uBlock-issues/issues/141#issuecomment-407737186
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
µMatrix.lz4Codec = (function() { // >>>> Start of private namespace
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
let lz4CodecInstance;
|
||||||
|
let pendingInitialization;
|
||||||
|
let textEncoder, textDecoder;
|
||||||
|
let ttlCount = 0;
|
||||||
|
let ttlTimer;
|
||||||
|
let ttlDelay = 60000;
|
||||||
|
|
||||||
|
const init = function() {
|
||||||
|
ttlDelay = µMatrix.rawSettings.autoUpdateAssetFetchPeriod * 1000 + 15000;
|
||||||
|
if ( lz4CodecInstance === null ) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
if ( lz4CodecInstance !== undefined ) {
|
||||||
|
return Promise.resolve(lz4CodecInstance);
|
||||||
|
}
|
||||||
|
if ( pendingInitialization === undefined ) {
|
||||||
|
let flavor;
|
||||||
|
if ( µMatrix.rawSettings.disableWebAssembly === true ) {
|
||||||
|
flavor = 'js';
|
||||||
|
}
|
||||||
|
pendingInitialization = lz4BlockCodec.createInstance(flavor)
|
||||||
|
.then(instance => {
|
||||||
|
lz4CodecInstance = instance;
|
||||||
|
pendingInitialization = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return pendingInitialization;
|
||||||
|
};
|
||||||
|
|
||||||
|
// We can't shrink memory usage of lz4 codec instances, and in the
|
||||||
|
// current case memory usage can grow to a significant amount given
|
||||||
|
// that a single contiguous memory buffer is required to accommodate
|
||||||
|
// both input and output data. Thus a time-to-live implementation
|
||||||
|
// which will cause the wasm instance to be forgotten after enough
|
||||||
|
// time elapse without the instance being used.
|
||||||
|
|
||||||
|
const destroy = function() {
|
||||||
|
//if ( lz4CodecInstance !== undefined ) {
|
||||||
|
// console.info(
|
||||||
|
// 'uBO: freeing lz4-block-codec instance (%s KB)',
|
||||||
|
// lz4CodecInstance.bytesInUse() >>> 10
|
||||||
|
// );
|
||||||
|
//}
|
||||||
|
lz4CodecInstance = undefined;
|
||||||
|
textEncoder = textDecoder = undefined;
|
||||||
|
ttlCount = 0;
|
||||||
|
ttlTimer = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ttlManage = function(count) {
|
||||||
|
if ( ttlTimer !== undefined ) {
|
||||||
|
clearTimeout(ttlTimer);
|
||||||
|
ttlTimer = undefined;
|
||||||
|
}
|
||||||
|
ttlCount += count;
|
||||||
|
if ( ttlCount > 0 ) { return; }
|
||||||
|
if ( lz4CodecInstance === null ) { return; }
|
||||||
|
ttlTimer = vAPI.setTimeout(destroy, ttlDelay);
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint8ArrayFromBlob = function(key, data) {
|
||||||
|
if ( data instanceof Blob === false ) {
|
||||||
|
return Promise.resolve({ key, data });
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
let blobReader = new FileReader();
|
||||||
|
blobReader.onloadend = ev => {
|
||||||
|
resolve({ key, data: new Uint8Array(ev.target.result) });
|
||||||
|
};
|
||||||
|
blobReader.readAsArrayBuffer(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodeValue = function(key, value) {
|
||||||
|
if ( !lz4CodecInstance ) { return; }
|
||||||
|
//let t0 = window.performance.now();
|
||||||
|
if ( textEncoder === undefined ) {
|
||||||
|
textEncoder = new TextEncoder();
|
||||||
|
}
|
||||||
|
let inputArray = textEncoder.encode(value);
|
||||||
|
let inputSize = inputArray.byteLength;
|
||||||
|
let outputArray = lz4CodecInstance.encodeBlock(inputArray, 8);
|
||||||
|
if ( outputArray instanceof Uint8Array === false ) { return; }
|
||||||
|
outputArray[0] = 0x18;
|
||||||
|
outputArray[1] = 0x4D;
|
||||||
|
outputArray[2] = 0x22;
|
||||||
|
outputArray[3] = 0x04;
|
||||||
|
outputArray[4] = (inputSize >>> 0) & 0xFF;
|
||||||
|
outputArray[5] = (inputSize >>> 8) & 0xFF;
|
||||||
|
outputArray[6] = (inputSize >>> 16) & 0xFF;
|
||||||
|
outputArray[7] = (inputSize >>> 24) & 0xFF;
|
||||||
|
//console.info(
|
||||||
|
// 'uBO: [%s] compressed %d KB => %d KB (%s%%) in %s ms',
|
||||||
|
// key,
|
||||||
|
// inputArray.byteLength >> 10,
|
||||||
|
// outputArray.byteLength >> 10,
|
||||||
|
// (outputArray.byteLength / inputArray.byteLength * 100).toFixed(0),
|
||||||
|
// (window.performance.now() - t0).toFixed(1)
|
||||||
|
//);
|
||||||
|
return outputArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodeValue = function(key, inputArray) {
|
||||||
|
if ( !lz4CodecInstance ) { return; }
|
||||||
|
//let t0 = window.performance.now();
|
||||||
|
if (
|
||||||
|
inputArray[0] !== 0x18 || inputArray[1] !== 0x4D ||
|
||||||
|
inputArray[2] !== 0x22 || inputArray[3] !== 0x04
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let outputSize =
|
||||||
|
(inputArray[4] << 0) | (inputArray[5] << 8) |
|
||||||
|
(inputArray[6] << 16) | (inputArray[7] << 24);
|
||||||
|
let outputArray = lz4CodecInstance.decodeBlock(inputArray, 8, outputSize);
|
||||||
|
if ( outputArray instanceof Uint8Array === false ) { return; }
|
||||||
|
if ( textDecoder === undefined ) {
|
||||||
|
textDecoder = new TextDecoder();
|
||||||
|
}
|
||||||
|
let value = textDecoder.decode(outputArray);
|
||||||
|
//console.info(
|
||||||
|
// 'uBO: [%s] decompressed %d KB => %d KB (%s%%) in %s ms',
|
||||||
|
// key,
|
||||||
|
// inputArray.byteLength >>> 10,
|
||||||
|
// outputSize >>> 10,
|
||||||
|
// (inputArray.byteLength / outputSize * 100).toFixed(0),
|
||||||
|
// (window.performance.now() - t0).toFixed(1)
|
||||||
|
//);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
encode: function(key, dataIn) {
|
||||||
|
if ( typeof dataIn !== 'string' || dataIn.length < 4096 ) {
|
||||||
|
return Promise.resolve({ key, data: dataIn });
|
||||||
|
}
|
||||||
|
ttlManage(1);
|
||||||
|
return init().then(( ) => {
|
||||||
|
ttlManage(-1);
|
||||||
|
let dataOut = encodeValue(key, dataIn) || dataIn;
|
||||||
|
if ( dataOut instanceof Uint8Array ) {
|
||||||
|
dataOut = new Blob([ dataOut ]);
|
||||||
|
}
|
||||||
|
return { key, data: dataOut || dataIn };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
decode: function(key, dataIn) {
|
||||||
|
if ( dataIn instanceof Blob === false ) {
|
||||||
|
return Promise.resolve({ key, data: dataIn });
|
||||||
|
}
|
||||||
|
ttlManage(1);
|
||||||
|
return Promise.all([
|
||||||
|
init(),
|
||||||
|
uint8ArrayFromBlob(key, dataIn)
|
||||||
|
]).then(results => {
|
||||||
|
ttlManage(-1);
|
||||||
|
let result = results[1];
|
||||||
|
return {
|
||||||
|
key: result.key,
|
||||||
|
data: decodeValue(result.key, result.data) || result.data
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
relinquish: function() {
|
||||||
|
ttlDelay = 1;
|
||||||
|
ttlManage(0);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
})(); // <<<< End of private namespace
|
|
@ -25,17 +25,18 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
let details = {};
|
let details = {};
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
let matches = /details=([^&]+)/.exec(window.location.search);
|
const matches = /details=([^&]+)/.exec(window.location.search);
|
||||||
if ( matches === null ) { return; }
|
if ( matches === null ) { return; }
|
||||||
try {
|
try {
|
||||||
details = JSON.parse(atob(matches[1]));
|
details = JSON.parse(decodeURIComponent(matches[1]));
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -51,15 +52,15 @@ uDom('.what').text(details.url);
|
||||||
// Code below originally imported from:
|
// Code below originally imported from:
|
||||||
// https://github.com/gorhill/uBlock/blob/master/src/js/document-blocked.js
|
// https://github.com/gorhill/uBlock/blob/master/src/js/document-blocked.js
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
let reURL = /^https?:\/\//;
|
const reURL = /^https?:\/\//;
|
||||||
|
|
||||||
let liFromParam = function(name, value) {
|
const liFromParam = function(name, value) {
|
||||||
if ( value === '' ) {
|
if ( value === '' ) {
|
||||||
value = name;
|
value = name;
|
||||||
name = '';
|
name = '';
|
||||||
}
|
}
|
||||||
let li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
let span = document.createElement('span');
|
let span = document.createElement('span');
|
||||||
span.textContent = name;
|
span.textContent = name;
|
||||||
li.appendChild(span);
|
li.appendChild(span);
|
||||||
|
@ -68,7 +69,7 @@ uDom('.what').text(details.url);
|
||||||
}
|
}
|
||||||
span = document.createElement('span');
|
span = document.createElement('span');
|
||||||
if ( reURL.test(value) ) {
|
if ( reURL.test(value) ) {
|
||||||
let a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = a.textContent = value;
|
a.href = a.textContent = value;
|
||||||
span.appendChild(a);
|
span.appendChild(a);
|
||||||
} else {
|
} else {
|
||||||
|
@ -78,7 +79,7 @@ uDom('.what').text(details.url);
|
||||||
return li;
|
return li;
|
||||||
};
|
};
|
||||||
|
|
||||||
let safeDecodeURIComponent = function(s) {
|
const safeDecodeURIComponent = function(s) {
|
||||||
try {
|
try {
|
||||||
s = decodeURIComponent(s);
|
s = decodeURIComponent(s);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
@ -86,30 +87,30 @@ uDom('.what').text(details.url);
|
||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
|
|
||||||
let renderParams = function(parentNode, rawURL) {
|
const renderParams = function(parentNode, rawURL) {
|
||||||
let a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = rawURL;
|
a.href = rawURL;
|
||||||
if ( a.search.length === 0 ) { return false; }
|
if ( a.search.length === 0 ) { return false; }
|
||||||
|
|
||||||
let pos = rawURL.indexOf('?');
|
const pos = rawURL.indexOf('?');
|
||||||
let li = liFromParam(
|
const li = liFromParam(
|
||||||
vAPI.i18n('mainBlockedNoParamsPrompt'),
|
vAPI.i18n('mainBlockedNoParamsPrompt'),
|
||||||
rawURL.slice(0, pos)
|
rawURL.slice(0, pos)
|
||||||
);
|
);
|
||||||
parentNode.appendChild(li);
|
parentNode.appendChild(li);
|
||||||
|
|
||||||
let params = a.search.slice(1).split('&');
|
const params = a.search.slice(1).split('&');
|
||||||
for ( var i = 0; i < params.length; i++ ) {
|
for ( let i = 0; i < params.length; i++ ) {
|
||||||
let param = params[i];
|
const param = params[i];
|
||||||
let pos = param.indexOf('=');
|
let pos = param.indexOf('=');
|
||||||
if ( pos === -1 ) {
|
if ( pos === -1 ) {
|
||||||
pos = param.length;
|
pos = param.length;
|
||||||
}
|
}
|
||||||
let name = safeDecodeURIComponent(param.slice(0, pos));
|
const name = safeDecodeURIComponent(param.slice(0, pos));
|
||||||
let value = safeDecodeURIComponent(param.slice(pos + 1));
|
const value = safeDecodeURIComponent(param.slice(pos + 1));
|
||||||
li = liFromParam(name, value);
|
const li = liFromParam(name, value);
|
||||||
if ( reURL.test(value) ) {
|
if ( reURL.test(value) ) {
|
||||||
let ul = document.createElement('ul');
|
const ul = document.createElement('ul');
|
||||||
renderParams(ul, value);
|
renderParams(ul, value);
|
||||||
li.appendChild(ul);
|
li.appendChild(ul);
|
||||||
}
|
}
|
||||||
|
@ -118,17 +119,17 @@ uDom('.what').text(details.url);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
let hasParams = renderParams(uDom.nodeFromId('parsed'), details.url);
|
const hasParams = renderParams(uDom.nodeFromId('parsed'), details.url);
|
||||||
if ( hasParams === false ) { return; }
|
if ( hasParams === false ) { return; }
|
||||||
|
|
||||||
let theURLNode = document.getElementById('theURL');
|
const theURLNode = document.getElementById('theURL');
|
||||||
theURLNode.classList.add('hasParams');
|
theURLNode.classList.add('hasParams');
|
||||||
theURLNode.classList.toggle(
|
theURLNode.classList.toggle(
|
||||||
'collapsed',
|
'collapsed',
|
||||||
vAPI.localStorage.getItem('document-blocked-collapse-url') === 'true'
|
vAPI.localStorage.getItem('document-blocked-collapse-url') === 'true'
|
||||||
);
|
);
|
||||||
|
|
||||||
let toggleCollapse = function() {
|
const toggleCollapse = function() {
|
||||||
vAPI.localStorage.setItem(
|
vAPI.localStorage.setItem(
|
||||||
'document-blocked-collapse-url',
|
'document-blocked-collapse-url',
|
||||||
theURLNode.classList.toggle('collapsed').toString()
|
theURLNode.classList.toggle('collapsed').toString()
|
||||||
|
@ -163,8 +164,8 @@ vAPI.messaging.send('main-blocked.js', {
|
||||||
what: 'mustBlock',
|
what: 'mustBlock',
|
||||||
scope: details.hn,
|
scope: details.hn,
|
||||||
hostname: details.hn,
|
hostname: details.hn,
|
||||||
type: details.type
|
type: details.type,
|
||||||
}, response => {
|
}).then(response => {
|
||||||
if ( response === false ) {
|
if ( response === false ) {
|
||||||
window.location.replace(details.url);
|
window.location.replace(details.url);
|
||||||
}
|
}
|
||||||
|
@ -172,6 +173,7 @@ vAPI.messaging.send('main-blocked.js', {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
310
src/js/matrix.js
310
src/js/matrix.js
|
@ -25,16 +25,16 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µMatrix.Matrix = (function() {
|
µMatrix.Matrix = (( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var µm = µMatrix;
|
const µm = µMatrix;
|
||||||
var selfieVersion = 1;
|
const selfieVersion = 1;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var Matrix = function() {
|
const Matrix = function() {
|
||||||
this.reset();
|
this.reset();
|
||||||
this.sourceRegister = '';
|
this.sourceRegister = '';
|
||||||
this.decomposedSourceRegister = [''];
|
this.decomposedSourceRegister = [''];
|
||||||
|
@ -60,7 +60,7 @@ Matrix.GrayIndirect = Matrix.Gray | Matrix.Indirect;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var typeBitOffsets = new Map([
|
const typeBitOffsets = new Map([
|
||||||
[ '*', 0 ],
|
[ '*', 0 ],
|
||||||
[ 'doc', 2 ],
|
[ 'doc', 2 ],
|
||||||
[ 'cookie', 4 ],
|
[ 'cookie', 4 ],
|
||||||
|
@ -68,49 +68,50 @@ var typeBitOffsets = new Map([
|
||||||
[ 'image', 8 ],
|
[ 'image', 8 ],
|
||||||
[ 'media', 10 ],
|
[ 'media', 10 ],
|
||||||
[ 'script', 12 ],
|
[ 'script', 12 ],
|
||||||
[ 'xhr', 14 ],
|
[ 'fetch', 14 ],
|
||||||
[ 'frame', 16 ],
|
[ 'frame', 16 ],
|
||||||
[ 'other', 18 ]
|
[ 'other', 18 ],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var stateToNameMap = new Map([
|
const stateToNameMap = new Map([
|
||||||
[ 1, 'block' ],
|
[ 1, 'block' ],
|
||||||
[ 2, 'allow' ],
|
[ 2, 'allow' ],
|
||||||
[ 3, 'inherit' ]
|
[ 3, 'inherit' ],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var nameToStateMap = {
|
const nameToStateMap = {
|
||||||
'block': 1,
|
'block': 1,
|
||||||
'allow': 2,
|
'allow': 2,
|
||||||
'noop': 2,
|
'noop': 2,
|
||||||
'inherit': 3
|
'inherit': 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
var switchBitOffsets = new Map([
|
const switchBitOffsets = new Map([
|
||||||
[ 'matrix-off', 0 ],
|
[ 'matrix-off', 0 ],
|
||||||
[ 'https-strict', 2 ],
|
[ 'https-strict', 2 ],
|
||||||
/* 4 is now unused, formerly assigned to UA spoofing */
|
/* 4 is now unused, formerly assigned to UA spoofing */
|
||||||
[ 'referrer-spoof', 6 ],
|
[ 'referrer-spoof', 6 ],
|
||||||
[ 'noscript-spoof', 8 ],
|
[ 'noscript-spoof', 8 ],
|
||||||
[ 'no-workers', 10 ]
|
[ 'no-workers', 10 ],
|
||||||
|
[ 'cname-reveal', 12 ],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var switchStateToNameMap = new Map([
|
const switchStateToNameMap = new Map([
|
||||||
[ 1, 'true' ],
|
[ 1, 'true' ],
|
||||||
[ 2, 'false' ]
|
[ 2, 'false' ],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var nameToSwitchStateMap = new Map([
|
const nameToSwitchStateMap = new Map([
|
||||||
[ 'true', 1 ],
|
[ 'true', 1 ],
|
||||||
[ 'false', 2 ]
|
[ 'false', 2 ],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.columnHeaderIndices = (function() {
|
Matrix.columnHeaderIndices = (( ) => {
|
||||||
var out = new Map(),
|
const out = new Map();
|
||||||
i = 0;
|
let i = 0;
|
||||||
for ( var type of typeBitOffsets.keys() ) {
|
for ( const type of typeBitOffsets.keys() ) {
|
||||||
out.set(type, i++);
|
out.set(type, i++);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
@ -122,14 +123,14 @@ Matrix.switchNames = new Set(switchBitOffsets.keys());
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// For performance purpose, as simple tests as possible
|
// For performance purpose, as simple tests as possible
|
||||||
var reHostnameVeryCoarse = /[g-z_-]/;
|
const reHostnameVeryCoarse = /[g-z_-]/;
|
||||||
var reIPv4VeryCoarse = /\.\d+$/;
|
const reIPv4VeryCoarse = /\.\d+$/;
|
||||||
|
|
||||||
// http://tools.ietf.org/html/rfc5952
|
// http://tools.ietf.org/html/rfc5952
|
||||||
// 4.3: "MUST be represented in lowercase"
|
// 4.3: "MUST be represented in lowercase"
|
||||||
// Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers
|
// Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers
|
||||||
|
|
||||||
var isIPAddress = function(hostname) {
|
const isIPAddress = function(hostname) {
|
||||||
if ( reHostnameVeryCoarse.test(hostname) ) {
|
if ( reHostnameVeryCoarse.test(hostname) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -141,19 +142,19 @@ var isIPAddress = function(hostname) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var punycodeIf = function(hn) {
|
const punycodeIf = function(hn) {
|
||||||
return reNotASCII.test(hn) ? punycode.toASCII(hn) : hn;
|
return reNotASCII.test(hn) ? punycode.toASCII(hn) : hn;
|
||||||
};
|
};
|
||||||
|
|
||||||
var unpunycodeIf = function(hn) {
|
const unpunycodeIf = function(hn) {
|
||||||
return hn.indexOf('xn--') !== -1 ? punycode.toUnicode(hn) : hn;
|
return hn.indexOf('xn--') !== -1 ? punycode.toUnicode(hn) : hn;
|
||||||
};
|
};
|
||||||
|
|
||||||
var reNotASCII = /[^\x20-\x7F]/;
|
const reNotASCII = /[^\x20-\x7F]/;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var toBroaderHostname = function(hostname) {
|
const toBroaderHostname = function(hostname) {
|
||||||
if ( hostname === '*' ) { return ''; }
|
if ( hostname === '*' ) { return ''; }
|
||||||
if ( isIPAddress(hostname) ) {
|
if ( isIPAddress(hostname) ) {
|
||||||
return toBroaderIPAddress(hostname);
|
return toBroaderIPAddress(hostname);
|
||||||
|
@ -165,12 +166,12 @@ var toBroaderHostname = function(hostname) {
|
||||||
return hostname.slice(pos + 1);
|
return hostname.slice(pos + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
var toBroaderIPAddress = function(ipaddress) {
|
const toBroaderIPAddress = function(ipaddress) {
|
||||||
// Can't broaden IPv6 (for now)
|
// Can't broaden IPv6 (for now)
|
||||||
if ( ipaddress.charAt(0) === '[' ) {
|
if ( ipaddress.charAt(0) === '[' ) {
|
||||||
return '*';
|
return '*';
|
||||||
}
|
}
|
||||||
var pos = ipaddress.lastIndexOf('.');
|
const pos = ipaddress.lastIndexOf('.');
|
||||||
return pos !== -1 ? ipaddress.slice(0, pos) : '*';
|
return pos !== -1 ? ipaddress.slice(0, pos) : '*';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -182,8 +183,12 @@ Matrix.toBroaderHostname = toBroaderHostname;
|
||||||
// speed. If desHostname is 1st-party to srcHostname, the domain is returned,
|
// speed. If desHostname is 1st-party to srcHostname, the domain is returned,
|
||||||
// otherwise the empty string.
|
// otherwise the empty string.
|
||||||
|
|
||||||
var extractFirstPartyDesDomain = function(srcHostname, desHostname) {
|
const extractFirstPartyDesDomain = function(srcHostname, desHostname) {
|
||||||
if ( srcHostname === '*' || desHostname === '*' || desHostname === '1st-party' ) {
|
if (
|
||||||
|
srcHostname === '*' ||
|
||||||
|
desHostname === '*' ||
|
||||||
|
desHostname === '1st-party'
|
||||||
|
) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
var µmuri = µm.URI;
|
var µmuri = µm.URI;
|
||||||
|
@ -229,9 +234,9 @@ Matrix.prototype.modified = function() {
|
||||||
|
|
||||||
Matrix.prototype.decomposeSource = function(srcHostname) {
|
Matrix.prototype.decomposeSource = function(srcHostname) {
|
||||||
if ( srcHostname === this.sourceRegister ) { return; }
|
if ( srcHostname === this.sourceRegister ) { return; }
|
||||||
var hn = srcHostname;
|
let hn = srcHostname;
|
||||||
this.decomposedSourceRegister[0] = this.sourceRegister = hn;
|
this.decomposedSourceRegister[0] = this.sourceRegister = hn;
|
||||||
var i = 1;
|
let i = 1;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
hn = toBroaderHostname(hn);
|
hn = toBroaderHostname(hn);
|
||||||
this.decomposedSourceRegister[i++] = hn;
|
this.decomposedSourceRegister[i++] = hn;
|
||||||
|
@ -245,25 +250,24 @@ Matrix.prototype.decomposeSource = function(srcHostname) {
|
||||||
// a live matrix.
|
// a live matrix.
|
||||||
|
|
||||||
Matrix.prototype.assign = function(other) {
|
Matrix.prototype.assign = function(other) {
|
||||||
var k, entry;
|
|
||||||
// Remove rules not in other
|
// Remove rules not in other
|
||||||
for ( k of this.rules.keys() ) {
|
for ( const k of this.rules.keys() ) {
|
||||||
if ( other.rules.has(k) === false ) {
|
if ( other.rules.has(k) === false ) {
|
||||||
this.rules.delete(k);
|
this.rules.delete(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove switches not in other
|
// Remove switches not in other
|
||||||
for ( k of this.switches.keys() ) {
|
for ( const k of this.switches.keys() ) {
|
||||||
if ( other.switches.has(k) === false ) {
|
if ( other.switches.has(k) === false ) {
|
||||||
this.switches.delete(k);
|
this.switches.delete(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add/change rules in other
|
// Add/change rules in other
|
||||||
for ( entry of other.rules ) {
|
for ( const entry of other.rules ) {
|
||||||
this.rules.set(entry[0], entry[1]);
|
this.rules.set(entry[0], entry[1]);
|
||||||
}
|
}
|
||||||
// Add/change switches in other
|
// Add/change switches in other
|
||||||
for ( entry of other.switches ) {
|
for ( const entry of other.switches ) {
|
||||||
this.switches.set(entry[0], entry[1]);
|
this.switches.set(entry[0], entry[1]);
|
||||||
}
|
}
|
||||||
this.modified();
|
this.modified();
|
||||||
|
@ -277,14 +281,14 @@ Matrix.prototype.assign = function(other) {
|
||||||
// If value is undefined, the switch is removed
|
// If value is undefined, the switch is removed
|
||||||
|
|
||||||
Matrix.prototype.setSwitch = function(switchName, srcHostname, newVal) {
|
Matrix.prototype.setSwitch = function(switchName, srcHostname, newVal) {
|
||||||
var bitOffset = switchBitOffsets.get(switchName);
|
const bitOffset = switchBitOffsets.get(switchName);
|
||||||
if ( bitOffset === undefined ) {
|
if ( bitOffset === undefined ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ( newVal === this.evaluateSwitch(switchName, srcHostname) ) {
|
if ( newVal === this.evaluateSwitch(switchName, srcHostname) ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var bits = this.switches.get(srcHostname) || 0;
|
let bits = this.switches.get(srcHostname) || 0;
|
||||||
bits &= ~(3 << bitOffset);
|
bits &= ~(3 << bitOffset);
|
||||||
bits |= newVal << bitOffset;
|
bits |= newVal << bitOffset;
|
||||||
if ( bits === 0 ) {
|
if ( bits === 0 ) {
|
||||||
|
@ -299,13 +303,13 @@ Matrix.prototype.setSwitch = function(switchName, srcHostname, newVal) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
|
Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
|
||||||
var bitOffset = typeBitOffsets.get(type),
|
const bitOffset = typeBitOffsets.get(type);
|
||||||
k = srcHostname + ' ' + desHostname,
|
const k = srcHostname + ' ' + desHostname;
|
||||||
oldBitmap = this.rules.get(k);
|
let oldBitmap = this.rules.get(k);
|
||||||
if ( oldBitmap === undefined ) {
|
if ( oldBitmap === undefined ) {
|
||||||
oldBitmap = 0;
|
oldBitmap = 0;
|
||||||
}
|
}
|
||||||
var newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset);
|
const newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset);
|
||||||
if ( newBitmap === oldBitmap ) {
|
if ( newBitmap === oldBitmap ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -321,15 +325,11 @@ Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.blacklistCell = function(srcHostname, desHostname, type) {
|
Matrix.prototype.blacklistCell = function(srcHostname, desHostname, type) {
|
||||||
var r = this.evaluateCellZ(srcHostname, desHostname, type);
|
let r = this.evaluateCellZ(srcHostname, desHostname, type);
|
||||||
if ( r === 1 ) {
|
if ( r === 1 ) { return false; }
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.setCell(srcHostname, desHostname, type, 0);
|
this.setCell(srcHostname, desHostname, type, 0);
|
||||||
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
||||||
if ( r === 1 ) {
|
if ( r === 1 ) { return true; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.setCell(srcHostname, desHostname, type, 1);
|
this.setCell(srcHostname, desHostname, type, 1);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -337,15 +337,11 @@ Matrix.prototype.blacklistCell = function(srcHostname, desHostname, type) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.whitelistCell = function(srcHostname, desHostname, type) {
|
Matrix.prototype.whitelistCell = function(srcHostname, desHostname, type) {
|
||||||
var r = this.evaluateCellZ(srcHostname, desHostname, type);
|
let r = this.evaluateCellZ(srcHostname, desHostname, type);
|
||||||
if ( r === 2 ) {
|
if ( r === 2 ) { return false; }
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.setCell(srcHostname, desHostname, type, 0);
|
this.setCell(srcHostname, desHostname, type, 0);
|
||||||
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
||||||
if ( r === 2 ) {
|
if ( r === 2 ) { return true; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.setCell(srcHostname, desHostname, type, 2);
|
this.setCell(srcHostname, desHostname, type, 2);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -353,15 +349,11 @@ Matrix.prototype.whitelistCell = function(srcHostname, desHostname, type) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.graylistCell = function(srcHostname, desHostname, type) {
|
Matrix.prototype.graylistCell = function(srcHostname, desHostname, type) {
|
||||||
var r = this.evaluateCellZ(srcHostname, desHostname, type);
|
let r = this.evaluateCellZ(srcHostname, desHostname, type);
|
||||||
if ( r === 0 || r === 3 ) {
|
if ( r === 0 || r === 3 ) { return false; }
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.setCell(srcHostname, desHostname, type, 0);
|
this.setCell(srcHostname, desHostname, type, 0);
|
||||||
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
r = this.evaluateCellZ(srcHostname, desHostname, type);
|
||||||
if ( r === 0 || r === 3 ) {
|
if ( r === 0 || r === 3 ) { return true; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.setCell(srcHostname, desHostname, type, 3);
|
this.setCell(srcHostname, desHostname, type, 3);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -369,11 +361,9 @@ Matrix.prototype.graylistCell = function(srcHostname, desHostname, type) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
|
Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
|
||||||
var key = srcHostname + ' ' + desHostname;
|
const key = srcHostname + ' ' + desHostname;
|
||||||
var bitmap = this.rules.get(key);
|
const bitmap = this.rules.get(key);
|
||||||
if ( bitmap === undefined ) {
|
if ( bitmap === undefined ) { return 0; }
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return bitmap >> typeBitOffsets.get(type) & 3;
|
return bitmap >> typeBitOffsets.get(type) & 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -382,12 +372,12 @@ Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) {
|
||||||
Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
|
Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
|
||||||
this.decomposeSource(srcHostname);
|
this.decomposeSource(srcHostname);
|
||||||
|
|
||||||
var bitOffset = typeBitOffsets.get(type),
|
const bitOffset = typeBitOffsets.get(type);
|
||||||
s, v, i = 0;
|
let i = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
s = this.decomposedSourceRegister[i++];
|
const s = this.decomposedSourceRegister[i++];
|
||||||
if ( s === '' ) { break; }
|
if ( s === '' ) { break; }
|
||||||
v = this.rules.get(s + ' ' + desHostname);
|
let v = this.rules.get(s + ' ' + desHostname);
|
||||||
if ( v !== undefined ) {
|
if ( v !== undefined ) {
|
||||||
v = v >> bitOffset & 3;
|
v = v >> bitOffset & 3;
|
||||||
if ( v !== 0 ) {
|
if ( v !== 0 ) {
|
||||||
|
@ -398,7 +388,7 @@ Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) {
|
||||||
// srcHostname is '*' at this point
|
// srcHostname is '*' at this point
|
||||||
|
|
||||||
// Preset blacklisted hostnames are blacklisted in global scope
|
// Preset blacklisted hostnames are blacklisted in global scope
|
||||||
if ( type === '*' && µm.ubiquitousBlacklist.test(desHostname) ) {
|
if ( type === '*' && µm.ubiquitousBlacklistRef.matches(desHostname) !== -1 ) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,17 +418,18 @@ Matrix.prototype.evaluateCellZXY = function(srcHostname, desHostname, type) {
|
||||||
|
|
||||||
// Specific-hostname specific-type cell
|
// Specific-hostname specific-type cell
|
||||||
this.specificityRegister = 1;
|
this.specificityRegister = 1;
|
||||||
var r = this.evaluateCellZ(srcHostname, desHostname, type);
|
let r = this.evaluateCellZ(srcHostname, desHostname, type);
|
||||||
if ( r === 1 ) { return Matrix.RedDirect; }
|
if ( r === 1 ) { return Matrix.RedDirect; }
|
||||||
if ( r === 2 ) { return Matrix.GreenDirect; }
|
if ( r === 2 ) { return Matrix.GreenDirect; }
|
||||||
|
|
||||||
// Specific-hostname any-type cell
|
// Specific-hostname any-type cell
|
||||||
this.specificityRegister = 2;
|
this.specificityRegister = 2;
|
||||||
var rl = this.evaluateCellZ(srcHostname, desHostname, '*');
|
let rl = this.evaluateCellZ(srcHostname, desHostname, '*');
|
||||||
if ( rl === 1 ) { return Matrix.RedIndirect; }
|
if ( rl === 1 ) { return Matrix.RedIndirect; }
|
||||||
|
|
||||||
var d = desHostname;
|
let d = desHostname;
|
||||||
var firstPartyDesDomain = extractFirstPartyDesDomain(srcHostname, desHostname);
|
const firstPartyDesDomain =
|
||||||
|
extractFirstPartyDesDomain(srcHostname, desHostname);
|
||||||
|
|
||||||
// Ancestor cells, up to 1st-party destination domain
|
// Ancestor cells, up to 1st-party destination domain
|
||||||
if ( firstPartyDesDomain !== '' ) {
|
if ( firstPartyDesDomain !== '' ) {
|
||||||
|
@ -504,13 +495,11 @@ Matrix.prototype.evaluateCellZXY = function(srcHostname, desHostname, type) {
|
||||||
return this.rootValue;
|
return this.rootValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://www.youtube.com/watch?v=4C5ZkwrnVfM
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.evaluateRowZXY = function(srcHostname, desHostname) {
|
Matrix.prototype.evaluateRowZXY = function(srcHostname, desHostname) {
|
||||||
let out = [];
|
const out = [];
|
||||||
for ( let type of typeBitOffsets.keys() ) {
|
for ( const type of typeBitOffsets.keys() ) {
|
||||||
out.push(this.evaluateCellZXY(srcHostname, desHostname, type));
|
out.push(this.evaluateCellZXY(srcHostname, desHostname, type));
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
@ -537,18 +526,14 @@ Matrix.prototype.desHostnameFromRule = function(rule) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.setSwitchZ = function(switchName, srcHostname, newState) {
|
Matrix.prototype.setSwitchZ = function(switchName, srcHostname, newState) {
|
||||||
var bitOffset = switchBitOffsets.get(switchName);
|
const bitOffset = switchBitOffsets.get(switchName);
|
||||||
if ( bitOffset === undefined ) {
|
if ( bitOffset === undefined ) { return false; }
|
||||||
return false;
|
let state = this.evaluateSwitchZ(switchName, srcHostname);
|
||||||
}
|
if ( newState === state ) { return false; }
|
||||||
var state = this.evaluateSwitchZ(switchName, srcHostname);
|
|
||||||
if ( newState === state ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if ( newState === undefined ) {
|
if ( newState === undefined ) {
|
||||||
newState = !state;
|
newState = !state;
|
||||||
}
|
}
|
||||||
var bits = this.switches.get(srcHostname) || 0;
|
let bits = this.switches.get(srcHostname) || 0;
|
||||||
bits &= ~(3 << bitOffset);
|
bits &= ~(3 << bitOffset);
|
||||||
if ( bits === 0 ) {
|
if ( bits === 0 ) {
|
||||||
this.switches.delete(srcHostname);
|
this.switches.delete(srcHostname);
|
||||||
|
@ -585,21 +570,20 @@ Matrix.prototype.evaluateSwitch = function(switchName, srcHostname) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.evaluateSwitchZ = function(switchName, srcHostname) {
|
Matrix.prototype.evaluateSwitchZ = function(switchName, srcHostname) {
|
||||||
var bitOffset = switchBitOffsets.get(switchName);
|
const bitOffset = switchBitOffsets.get(switchName);
|
||||||
if ( bitOffset === undefined ) { return false; }
|
if ( bitOffset === undefined ) { return false; }
|
||||||
|
|
||||||
this.decomposeSource(srcHostname);
|
this.decomposeSource(srcHostname);
|
||||||
|
|
||||||
var s, bits, i = 0;
|
let i = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
s = this.decomposedSourceRegister[i++];
|
const s = this.decomposedSourceRegister[i++];
|
||||||
if ( s === '' ) { break; }
|
if ( s === '' ) { break; }
|
||||||
bits = this.switches.get(s) || 0;
|
let bits = this.switches.get(s) || 0;
|
||||||
|
if ( bits === 0 ) { continue; }
|
||||||
|
bits = bits >> bitOffset & 3;
|
||||||
if ( bits !== 0 ) {
|
if ( bits !== 0 ) {
|
||||||
bits = bits >> bitOffset & 3;
|
return bits === 1;
|
||||||
if ( bits !== 0 ) {
|
|
||||||
return bits === 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -607,15 +591,15 @@ Matrix.prototype.evaluateSwitchZ = function(switchName, srcHostname) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.extractAllSourceHostnames = (function() {
|
Matrix.prototype.extractAllSourceHostnames = (( ) => {
|
||||||
var cachedResult = new Set();
|
const cachedResult = new Set();
|
||||||
var matrixId = 0;
|
let matrixId = 0;
|
||||||
var readTime = 0;
|
let readTime = 0;
|
||||||
|
|
||||||
return function() {
|
return function() {
|
||||||
if ( matrixId !== this.id || readTime !== this.modifiedTime ) {
|
if ( matrixId !== this.id || readTime !== this.modifiedTime ) {
|
||||||
cachedResult.clear();
|
cachedResult.clear();
|
||||||
for ( var rule of this.rules.keys() ) {
|
for ( const rule of this.rules.keys() ) {
|
||||||
cachedResult.add(rule.slice(0, rule.indexOf(' ')));
|
cachedResult.add(rule.slice(0, rule.indexOf(' ')));
|
||||||
}
|
}
|
||||||
matrixId = this.id;
|
matrixId = this.id;
|
||||||
|
@ -627,11 +611,8 @@ Matrix.prototype.extractAllSourceHostnames = (function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://github.com/gorhill/uMatrix/issues/759
|
|
||||||
// Backward compatibility: 'plugin' => 'media'
|
|
||||||
|
|
||||||
Matrix.prototype.partsFromLine = function(line) {
|
Matrix.prototype.partsFromLine = function(line) {
|
||||||
let fields = line.split(/\s+/);
|
const fields = line.split(/\s+/);
|
||||||
if ( fields.length < 3 ) { return; }
|
if ( fields.length < 3 ) { return; }
|
||||||
|
|
||||||
// Switches
|
// Switches
|
||||||
|
@ -649,7 +630,9 @@ Matrix.prototype.partsFromLine = function(line) {
|
||||||
if ( fields.length < 4 ) { return; }
|
if ( fields.length < 4 ) { return; }
|
||||||
fields[0] = punycodeIf(fields[0]);
|
fields[0] = punycodeIf(fields[0]);
|
||||||
fields[1] = punycodeIf(fields[1]);
|
fields[1] = punycodeIf(fields[1]);
|
||||||
if ( fields[2] === 'plugin' ) { fields[2] = 'media'; }
|
if ( this.renamedRules.has(fields[2]) ) {
|
||||||
|
fields[2] = this.renamedRules.get(fields[2]);
|
||||||
|
}
|
||||||
if ( typeBitOffsets.get(fields[2]) === undefined ) { return; }
|
if ( typeBitOffsets.get(fields[2]) === undefined ) { return; }
|
||||||
if ( nameToStateMap.hasOwnProperty(fields[3]) === false ) { return; }
|
if ( nameToStateMap.hasOwnProperty(fields[3]) === false ) { return; }
|
||||||
fields[3] = nameToStateMap[fields[3]];
|
fields[3] = nameToStateMap[fields[3]];
|
||||||
|
@ -658,11 +641,15 @@ Matrix.prototype.partsFromLine = function(line) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Matrix.prototype.reSwitchRule = /^[0-9a-z-]+:$/;
|
Matrix.prototype.reSwitchRule = /^[0-9a-z-]+:$/;
|
||||||
|
Matrix.prototype.renamedRules = new Map([
|
||||||
|
[ 'plugin', 'media' ],
|
||||||
|
[ 'xhr', 'fetch' ],
|
||||||
|
]);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.fromArray = function(lines, append) {
|
Matrix.prototype.fromArray = function(lines, append) {
|
||||||
let matrix = append === true ? this : new Matrix();
|
const matrix = append === true ? this : new Matrix();
|
||||||
for ( let line of lines ) {
|
for ( let line of lines ) {
|
||||||
matrix.addFromLine(line);
|
matrix.addFromLine(line);
|
||||||
}
|
}
|
||||||
|
@ -673,12 +660,12 @@ Matrix.prototype.fromArray = function(lines, append) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Matrix.prototype.toArray = function() {
|
Matrix.prototype.toArray = function() {
|
||||||
let out = [];
|
const out = [];
|
||||||
for ( let rule of this.rules.keys() ) {
|
for ( const rule of this.rules.keys() ) {
|
||||||
let srcHostname = this.srcHostnameFromRule(rule);
|
const srcHostname = this.srcHostnameFromRule(rule);
|
||||||
let desHostname = this.desHostnameFromRule(rule);
|
const desHostname = this.desHostnameFromRule(rule);
|
||||||
for ( let type of typeBitOffsets.keys() ) {
|
for ( let type of typeBitOffsets.keys() ) {
|
||||||
let val = this.evaluateCell(srcHostname, desHostname, type);
|
const val = this.evaluateCell(srcHostname, desHostname, type);
|
||||||
if ( val === 0 ) { continue; }
|
if ( val === 0 ) { continue; }
|
||||||
out.push(
|
out.push(
|
||||||
unpunycodeIf(srcHostname) + ' ' +
|
unpunycodeIf(srcHostname) + ' ' +
|
||||||
|
@ -688,9 +675,9 @@ Matrix.prototype.toArray = function() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ( let srcHostname of this.switches.keys() ) {
|
for ( const srcHostname of this.switches.keys() ) {
|
||||||
for ( let switchName of switchBitOffsets.keys() ) {
|
for ( const switchName of switchBitOffsets.keys() ) {
|
||||||
let val = this.evaluateSwitch(switchName, srcHostname);
|
const val = this.evaluateSwitch(switchName, srcHostname);
|
||||||
if ( val === 0 ) { continue; }
|
if ( val === 0 ) { continue; }
|
||||||
out.push(
|
out.push(
|
||||||
switchName + ': ' +
|
switchName + ': ' +
|
||||||
|
@ -705,8 +692,8 @@ Matrix.prototype.toArray = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.fromString = function(text, append) {
|
Matrix.prototype.fromString = function(text, append) {
|
||||||
let matrix = append === true ? this : new Matrix();
|
const matrix = append === true ? this : new Matrix();
|
||||||
let textEnd = text.length;
|
const textEnd = text.length;
|
||||||
let lineBeg = 0;
|
let lineBeg = 0;
|
||||||
|
|
||||||
while ( lineBeg < textEnd ) {
|
while ( lineBeg < textEnd ) {
|
||||||
|
@ -719,7 +706,7 @@ Matrix.prototype.fromString = function(text, append) {
|
||||||
}
|
}
|
||||||
let line = text.slice(lineBeg, lineEnd).trim();
|
let line = text.slice(lineBeg, lineEnd).trim();
|
||||||
lineBeg = lineEnd + 1;
|
lineBeg = lineEnd + 1;
|
||||||
let pos = line.indexOf('# ');
|
const pos = line.indexOf('# ');
|
||||||
if ( pos !== -1 ) {
|
if ( pos !== -1 ) {
|
||||||
line = line.slice(0, pos).trim();
|
line = line.slice(0, pos).trim();
|
||||||
}
|
}
|
||||||
|
@ -741,30 +728,28 @@ Matrix.prototype.toString = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.addFromLine = function(line) {
|
Matrix.prototype.addFromLine = function(line) {
|
||||||
let fields = this.partsFromLine(line);
|
const fields = this.partsFromLine(line);
|
||||||
if ( fields !== undefined ) {
|
if ( fields === undefined ) { return; }
|
||||||
// Switches
|
// Switches
|
||||||
if ( fields.length === 3 ) {
|
if ( fields.length === 3 ) {
|
||||||
return this.setSwitch(fields[0], fields[1], fields[2]);
|
return this.setSwitch(fields[0], fields[1], fields[2]);
|
||||||
}
|
}
|
||||||
// Rules
|
// Rules
|
||||||
if ( fields.length === 4 ) {
|
if ( fields.length === 4 ) {
|
||||||
return this.setCell(fields[0], fields[1], fields[2], fields[3]);
|
return this.setCell(fields[0], fields[1], fields[2], fields[3]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Matrix.prototype.removeFromLine = function(line) {
|
Matrix.prototype.removeFromLine = function(line) {
|
||||||
let fields = this.partsFromLine(line);
|
const fields = this.partsFromLine(line);
|
||||||
if ( fields !== undefined ) {
|
if ( fields === undefined ) { return; }
|
||||||
// Switches
|
// Switches
|
||||||
if ( fields.length === 3 ) {
|
if ( fields.length === 3 ) {
|
||||||
return this.setSwitch(fields[0], fields[1], 0);
|
return this.setSwitch(fields[0], fields[1], 0);
|
||||||
}
|
}
|
||||||
// Rules
|
// Rules
|
||||||
if ( fields.length === 4 ) {
|
if ( fields.length === 4 ) {
|
||||||
return this.setCell(fields[0], fields[1], fields[2], 0);
|
return this.setCell(fields[0], fields[1], fields[2], 0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -789,13 +774,11 @@ Matrix.prototype.toSelfie = function() {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.diff = function(other, srcHostname, desHostnames) {
|
Matrix.prototype.diff = function(other, srcHostname, desHostnames) {
|
||||||
var out = [];
|
const out = [];
|
||||||
var desHostname, type;
|
|
||||||
var switchName, i, thisVal, otherVal;
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
for ( switchName of switchBitOffsets.keys() ) {
|
for ( const switchName of switchBitOffsets.keys() ) {
|
||||||
thisVal = this.evaluateSwitch(switchName, srcHostname);
|
const thisVal = this.evaluateSwitch(switchName, srcHostname);
|
||||||
otherVal = other.evaluateSwitch(switchName, srcHostname);
|
const otherVal = other.evaluateSwitch(switchName, srcHostname);
|
||||||
if ( thisVal !== otherVal ) {
|
if ( thisVal !== otherVal ) {
|
||||||
out.push({
|
out.push({
|
||||||
'what': switchName,
|
'what': switchName,
|
||||||
|
@ -803,12 +786,12 @@ Matrix.prototype.diff = function(other, srcHostname, desHostnames) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = desHostnames.length;
|
let i = desHostnames.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
desHostname = desHostnames[i];
|
const desHostname = desHostnames[i];
|
||||||
for ( type of typeBitOffsets.keys() ) {
|
for ( const type of typeBitOffsets.keys() ) {
|
||||||
thisVal = this.evaluateCell(srcHostname, desHostname, type);
|
const thisVal = this.evaluateCell(srcHostname, desHostname, type);
|
||||||
otherVal = other.evaluateCell(srcHostname, desHostname, type);
|
const otherVal = other.evaluateCell(srcHostname, desHostname, type);
|
||||||
if ( thisVal === otherVal ) { continue; }
|
if ( thisVal === otherVal ) { continue; }
|
||||||
out.push({
|
out.push({
|
||||||
'what': 'rule',
|
'what': 'rule',
|
||||||
|
@ -827,18 +810,15 @@ Matrix.prototype.diff = function(other, srcHostname, desHostnames) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
Matrix.prototype.applyDiff = function(diff, from) {
|
Matrix.prototype.applyDiff = function(diff, from) {
|
||||||
var changed = false;
|
let changed = false;
|
||||||
var i = diff.length;
|
for ( const action of diff ) {
|
||||||
var action, val;
|
|
||||||
while ( i-- ) {
|
|
||||||
action = diff[i];
|
|
||||||
if ( action.what === 'rule' ) {
|
if ( action.what === 'rule' ) {
|
||||||
val = from.evaluateCell(action.src, action.des, action.type);
|
const val = from.evaluateCell(action.src, action.des, action.type);
|
||||||
changed = this.setCell(action.src, action.des, action.type, val) || changed;
|
changed = this.setCell(action.src, action.des, action.type, val) || changed;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ( switchBitOffsets.has(action.what) ) {
|
if ( switchBitOffsets.has(action.what) ) {
|
||||||
val = from.evaluateSwitch(action.what, action.src);
|
const val = from.evaluateSwitch(action.what, action.src);
|
||||||
changed = this.setSwitch(action.what, action.src, val) || changed;
|
changed = this.setSwitch(action.what, action.src, val) || changed;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -848,19 +828,19 @@ Matrix.prototype.applyDiff = function(diff, from) {
|
||||||
|
|
||||||
Matrix.prototype.copyRuleset = function(entries, from, deep) {
|
Matrix.prototype.copyRuleset = function(entries, from, deep) {
|
||||||
let changed = false;
|
let changed = false;
|
||||||
for ( let entry of entries ) {
|
for ( const entry of entries ) {
|
||||||
let srcHn = entry.srcHn;
|
let srcHn = entry.srcHn;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (
|
if (
|
||||||
entry.switchName !== undefined &&
|
entry.switchName !== undefined &&
|
||||||
switchBitOffsets.has(entry.switchName)
|
switchBitOffsets.has(entry.switchName)
|
||||||
) {
|
) {
|
||||||
let val = from.evaluateSwitch(entry.switchName, srcHn);
|
const val = from.evaluateSwitch(entry.switchName, srcHn);
|
||||||
if ( this.setSwitch(entry.switchName, srcHn, val) ) {
|
if ( this.setSwitch(entry.switchName, srcHn, val) ) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
} else if ( entry.desHn && entry.type ) {
|
} else if ( entry.desHn && entry.type ) {
|
||||||
let val = from.evaluateCell(srcHn, entry.desHn, entry.type);
|
const val = from.evaluateCell(srcHn, entry.desHn, entry.type);
|
||||||
if ( this.setCell(srcHn, entry.desHn, entry.type, val) ) {
|
if ( this.setCell(srcHn, entry.desHn, entry.type, val) ) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
@ -879,8 +859,6 @@ return Matrix;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// https://www.youtube.com/watch?v=wlNrQGmj6oQ
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,42 +23,39 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µMatrix.pageStoreFactory = (function() {
|
µMatrix.pageStoreFactory = (( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var µm = µMatrix;
|
const µm = µMatrix;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var BlockedCollapsibles = function() {
|
const BlockedCollapsibles = class {
|
||||||
this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this);
|
constructor() {
|
||||||
this.blocked = new Map();
|
this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this);
|
||||||
this.hash = 0;
|
this.blocked = new Map();
|
||||||
this.timer = null;
|
this.hash = 0;
|
||||||
this.tOrigin = Date.now();
|
this.timer = null;
|
||||||
};
|
this.tOrigin = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
BlockedCollapsibles.prototype = {
|
add(type, url, isSpecific) {
|
||||||
|
|
||||||
shelfLife: 10 * 1000,
|
|
||||||
|
|
||||||
add: function(type, url, isSpecific) {
|
|
||||||
if ( this.blocked.size === 0 ) { this.pruneAsync(); }
|
if ( this.blocked.size === 0 ) { this.pruneAsync(); }
|
||||||
let tStamp = Date.now() - this.tOrigin;
|
let tStamp = Date.now() - this.tOrigin;
|
||||||
// The following "trick" is to encode the specifity into the lsb of the
|
// The following "trick" is to encode the specifity into the lsb of the
|
||||||
// time stamp so as to avoid to have to allocate a memory structure to
|
// time stamp so as to avoid to have to allocate a memory structure to
|
||||||
// store both time stamp and specificity.
|
// store both time stamp and specificity.
|
||||||
if ( isSpecific ) {
|
if ( isSpecific ) {
|
||||||
tStamp |= 0x00000001;
|
tStamp |= 1;
|
||||||
} else {
|
} else {
|
||||||
tStamp &= 0xFFFFFFFE;
|
tStamp &= ~1;
|
||||||
}
|
}
|
||||||
this.blocked.set(type + ' ' + url, tStamp);
|
this.blocked.set(type + ' ' + url, tStamp);
|
||||||
this.hash += 1;
|
this.hash += 1;
|
||||||
},
|
}
|
||||||
|
|
||||||
reset: function() {
|
reset() {
|
||||||
this.blocked.clear();
|
this.blocked.clear();
|
||||||
this.hash = 0;
|
this.hash = 0;
|
||||||
if ( this.timer !== null ) {
|
if ( this.timer !== null ) {
|
||||||
|
@ -66,23 +63,23 @@ BlockedCollapsibles.prototype = {
|
||||||
this.timer = null;
|
this.timer = null;
|
||||||
}
|
}
|
||||||
this.tOrigin = Date.now();
|
this.tOrigin = Date.now();
|
||||||
},
|
}
|
||||||
|
|
||||||
pruneAsync: function() {
|
pruneAsync() {
|
||||||
if ( this.timer === null ) {
|
if ( this.timer === null ) {
|
||||||
this.timer = vAPI.setTimeout(
|
this.timer = vAPI.setTimeout(
|
||||||
this.boundPruneAsyncCallback,
|
this.boundPruneAsyncCallback,
|
||||||
this.shelfLife * 2
|
this.shelfLife * 2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
pruneAsyncCallback: function() {
|
pruneAsyncCallback() {
|
||||||
this.timer = null;
|
this.timer = null;
|
||||||
let tObsolete = Date.now() - this.tOrigin - this.shelfLife;
|
const tObsolete = Date.now() - this.tOrigin - this.shelfLife;
|
||||||
for ( let entry of this.blocked ) {
|
for ( const [ key, tStamp ] of this.blocked ) {
|
||||||
if ( entry[1] <= tObsolete ) {
|
if ( tStamp <= tObsolete ) {
|
||||||
this.blocked.delete(entry[0]);
|
this.blocked.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( this.blocked.size !== 0 ) {
|
if ( this.blocked.size !== 0 ) {
|
||||||
|
@ -93,6 +90,8 @@ BlockedCollapsibles.prototype = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BlockedCollapsibles.prototype.shelfLife = 10 * 1000;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Ref: Given a URL, returns a (somewhat) unique 32-bit value
|
// Ref: Given a URL, returns a (somewhat) unique 32-bit value
|
||||||
|
@ -100,19 +99,15 @@ BlockedCollapsibles.prototype = {
|
||||||
// http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source
|
// http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source
|
||||||
// The rest is custom, suited for uMatrix.
|
// The rest is custom, suited for uMatrix.
|
||||||
|
|
||||||
var PageStore = function(tabContext) {
|
const PageStore = class {
|
||||||
this.hostnameTypeCells = new Map();
|
constructor(tabContext) {
|
||||||
this.domains = new Set();
|
this.hostnameTypeCells = new Map();
|
||||||
this.blockedCollapsibles = new BlockedCollapsibles();
|
this.domains = new Set();
|
||||||
this.init(tabContext);
|
this.blockedCollapsibles = new BlockedCollapsibles();
|
||||||
};
|
this.init(tabContext);
|
||||||
|
}
|
||||||
|
|
||||||
PageStore.prototype = {
|
init(tabContext) {
|
||||||
|
|
||||||
collapsibleTypes: new Set([ 'image' ]),
|
|
||||||
pageStoreJunkyard: [],
|
|
||||||
|
|
||||||
init: function(tabContext) {
|
|
||||||
this.tabId = tabContext.tabId;
|
this.tabId = tabContext.tabId;
|
||||||
this.rawURL = tabContext.rawURL;
|
this.rawURL = tabContext.rawURL;
|
||||||
this.pageUrl = tabContext.normalURL;
|
this.pageUrl = tabContext.normalURL;
|
||||||
|
@ -130,13 +125,14 @@ PageStore.prototype = {
|
||||||
this.hasMixedContent = false;
|
this.hasMixedContent = false;
|
||||||
this.hasNoscriptTags = false;
|
this.hasNoscriptTags = false;
|
||||||
this.hasWebWorkers = false;
|
this.hasWebWorkers = false;
|
||||||
|
this.hasHostnameAliases = false;
|
||||||
this.incinerationTimer = null;
|
this.incinerationTimer = null;
|
||||||
this.mtxContentModifiedTime = 0;
|
this.mtxContentModifiedTime = 0;
|
||||||
this.mtxCountModifiedTime = 0;
|
this.mtxCountModifiedTime = 0;
|
||||||
return this;
|
return this;
|
||||||
},
|
}
|
||||||
|
|
||||||
dispose: function() {
|
dispose() {
|
||||||
this.tabId = '';
|
this.tabId = '';
|
||||||
this.rawURL = '';
|
this.rawURL = '';
|
||||||
this.pageUrl = '';
|
this.pageUrl = '';
|
||||||
|
@ -154,9 +150,9 @@ PageStore.prototype = {
|
||||||
if ( this.pageStoreJunkyard.length < 8 ) {
|
if ( this.pageStoreJunkyard.length < 8 ) {
|
||||||
this.pageStoreJunkyard.push(this);
|
this.pageStoreJunkyard.push(this);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
cacheBlockedCollapsible: function(type, url, specificity) {
|
cacheBlockedCollapsible(type, url, specificity) {
|
||||||
if ( this.collapsibleTypes.has(type) ) {
|
if ( this.collapsibleTypes.has(type) ) {
|
||||||
this.blockedCollapsibles.add(
|
this.blockedCollapsibles.add(
|
||||||
type,
|
type,
|
||||||
|
@ -164,20 +160,20 @@ PageStore.prototype = {
|
||||||
specificity !== 0 && specificity < 5
|
specificity !== 0 && specificity < 5
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
lookupBlockedCollapsibles: function(request, response) {
|
lookupBlockedCollapsibles(request, response) {
|
||||||
var tabContext = µm.tabContextManager.lookup(this.tabId);
|
const tabContext = µm.tabContextManager.lookup(this.tabId);
|
||||||
if ( tabContext === null ) { return; }
|
if ( tabContext === null ) { return; }
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Array.isArray(request.toFilter) &&
|
Array.isArray(request.toFilter) &&
|
||||||
request.toFilter.length !== 0
|
request.toFilter.length !== 0
|
||||||
) {
|
) {
|
||||||
let roothn = tabContext.rootHostname,
|
const roothn = tabContext.rootHostname;
|
||||||
hnFromURI = µm.URI.hostnameFromURI,
|
const hnFromURI = vAPI.hostnameFromURI;
|
||||||
tMatrix = µm.tMatrix;
|
const tMatrix = µm.tMatrix;
|
||||||
for ( let entry of request.toFilter ) {
|
for ( const entry of request.toFilter ) {
|
||||||
if ( tMatrix.mustBlock(roothn, hnFromURI(entry.url), entry.type) ) {
|
if ( tMatrix.mustBlock(roothn, hnFromURI(entry.url), entry.type) ) {
|
||||||
this.blockedCollapsibles.add(
|
this.blockedCollapsibles.add(
|
||||||
entry.type,
|
entry.type,
|
||||||
|
@ -191,19 +187,19 @@ PageStore.prototype = {
|
||||||
if ( this.blockedCollapsibles.hash === response.hash ) { return; }
|
if ( this.blockedCollapsibles.hash === response.hash ) { return; }
|
||||||
response.hash = this.blockedCollapsibles.hash;
|
response.hash = this.blockedCollapsibles.hash;
|
||||||
|
|
||||||
let collapseBlacklisted = µm.userSettings.collapseBlacklisted,
|
const collapseBlacklisted = µm.userSettings.collapseBlacklisted;
|
||||||
collapseBlocked = µm.userSettings.collapseBlocked,
|
const collapseBlocked = µm.userSettings.collapseBlocked;
|
||||||
blockedResources = response.blockedResources;
|
const blockedResources = response.blockedResources;
|
||||||
|
|
||||||
for ( let entry of this.blockedCollapsibles.blocked ) {
|
for ( const entry of this.blockedCollapsibles.blocked ) {
|
||||||
blockedResources.push([
|
blockedResources.push([
|
||||||
entry[0],
|
entry[0],
|
||||||
collapseBlocked || collapseBlacklisted && (entry[1] & 1) !== 0
|
collapseBlocked || collapseBlacklisted && (entry[1] & 1) !== 0
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
recordRequest: function(type, url, block) {
|
recordRequest(type, url, block) {
|
||||||
if ( this.tabId <= 0 ) { return; }
|
if ( this.tabId <= 0 ) { return; }
|
||||||
|
|
||||||
if ( block ) {
|
if ( block ) {
|
||||||
|
@ -216,19 +212,19 @@ PageStore.prototype = {
|
||||||
// - remember which hostname/type were seen
|
// - remember which hostname/type were seen
|
||||||
// - count the number of distinct URLs for any given
|
// - count the number of distinct URLs for any given
|
||||||
// hostname-type pair
|
// hostname-type pair
|
||||||
var hostname = µm.URI.hostnameFromURI(url),
|
const hostname = vAPI.hostnameFromURI(url);
|
||||||
key = hostname + ' ' + type,
|
const key = hostname + ' ' + type;
|
||||||
uids = this.hostnameTypeCells.get(key);
|
let uids = this.hostnameTypeCells.get(key);
|
||||||
if ( uids === undefined ) {
|
if ( uids === undefined ) {
|
||||||
this.hostnameTypeCells.set(key, (uids = new Set()));
|
this.hostnameTypeCells.set(key, (uids = new Set()));
|
||||||
} else if ( uids.size > 99 ) {
|
} else if ( uids.size > 99 ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var uid = this.uidFromURL(url);
|
const uid = this.uidFromURL(url);
|
||||||
if ( uids.has(uid) ) { return; }
|
if ( uids.has(uid) ) { return; }
|
||||||
uids.add(uid);
|
uids.add(uid);
|
||||||
|
|
||||||
µm.updateBadgeAsync(this.tabId);
|
µm.updateToolbarIcon(this.tabId);
|
||||||
|
|
||||||
this.mtxCountModifiedTime = Date.now();
|
this.mtxCountModifiedTime = Date.now();
|
||||||
|
|
||||||
|
@ -237,24 +233,27 @@ PageStore.prototype = {
|
||||||
this.allHostnamesString += hostname + ' ';
|
this.allHostnamesString += hostname + ' ';
|
||||||
this.mtxContentModifiedTime = Date.now();
|
this.mtxContentModifiedTime = Date.now();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
uidFromURL: function(uri) {
|
uidFromURL(uri) {
|
||||||
var hint = 0x811c9dc5,
|
let hint = 0x811c9dc5;
|
||||||
i = uri.length;
|
let i = uri.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
hint ^= uri.charCodeAt(i) | 0;
|
hint ^= uri.charCodeAt(i);
|
||||||
hint += (hint<<1) + (hint<<4) + (hint<<7) + (hint<<8) + (hint<<24) | 0;
|
hint += (hint<<1) + (hint<<4) + (hint<<7) + (hint<<8) + (hint<<24);
|
||||||
hint >>>= 0;
|
hint >>>= 0;
|
||||||
}
|
}
|
||||||
return hint;
|
return hint;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PageStore.prototype.collapsibleTypes = new Set([ 'image' ]);
|
||||||
|
PageStore.prototype.pageStoreJunkyard = [];
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
return function pageStoreFactory(tabContext) {
|
return function pageStoreFactory(tabContext) {
|
||||||
var entry = PageStore.prototype.pageStoreJunkyard.pop();
|
const entry = PageStore.prototype.pageStoreJunkyard.pop();
|
||||||
if ( entry ) {
|
if ( entry ) {
|
||||||
return entry.init(tabContext);
|
return entry.init(tabContext);
|
||||||
}
|
}
|
||||||
|
|
1117
src/js/popup.js
1117
src/js/popup.js
File diff suppressed because it is too large
Load diff
|
@ -19,23 +19,34 @@
|
||||||
Home: https://github.com/gorhill/uBlock
|
Home: https://github.com/gorhill/uBlock
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global uDom */
|
/* global CodeMirror, uDom, uBlockDashboard */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var messaging = vAPI.messaging;
|
const cmEditor = new CodeMirror(
|
||||||
var cachedData = '';
|
document.getElementById('rawSettings'),
|
||||||
var rawSettingsInput = uDom.nodeFromId('rawSettings');
|
{
|
||||||
|
autofocus: true,
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
styleActiveLine: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
uBlockDashboard.patchCodeMirrorEditor(cmEditor);
|
||||||
|
|
||||||
|
let cachedData = '';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var hashFromRawSettings = function(raw) {
|
const hashFromRawSettings = function(raw) {
|
||||||
return raw.trim().replace(/\s+/g, '|');
|
return raw.trim().replace(/\s+/g, '|');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,63 +54,63 @@ var hashFromRawSettings = function(raw) {
|
||||||
|
|
||||||
// This is to give a visual hint that the content of user blacklist has changed.
|
// This is to give a visual hint that the content of user blacklist has changed.
|
||||||
|
|
||||||
var rawSettingsChanged = (function () {
|
const rawSettingsChanged = (( ) => {
|
||||||
var timer = null;
|
let timer;
|
||||||
|
|
||||||
var handler = function() {
|
const handler = function() {
|
||||||
timer = null;
|
timer = undefined;
|
||||||
var changed =
|
const changed =
|
||||||
hashFromRawSettings(rawSettingsInput.value) !== cachedData;
|
hashFromRawSettings(cmEditor.getValue()) !== cachedData;
|
||||||
uDom.nodeFromId('rawSettingsApply').disabled = !changed;
|
uDom.nodeFromId('rawSettingsApply').disabled = changed === false;
|
||||||
|
CodeMirror.commands.save = changed ? applyChanges : function(){};
|
||||||
};
|
};
|
||||||
|
|
||||||
return function() {
|
return function() {
|
||||||
if ( timer !== null ) {
|
if ( timer !== undefined ) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
timer = vAPI.setTimeout(handler, 100);
|
timer = vAPI.setTimeout(handler, 100);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
cmEditor.on('changes', rawSettingsChanged);
|
||||||
|
|
||||||
function renderRawSettings() {
|
|
||||||
var onRead = function(raw) {
|
|
||||||
cachedData = hashFromRawSettings(raw);
|
|
||||||
var pretty = [],
|
|
||||||
whitespaces = ' ',
|
|
||||||
lines = raw.split('\n'),
|
|
||||||
max = 0,
|
|
||||||
pos,
|
|
||||||
i, n = lines.length;
|
|
||||||
for ( i = 0; i < n; i++ ) {
|
|
||||||
pos = lines[i].indexOf(' ');
|
|
||||||
if ( pos > max ) {
|
|
||||||
max = pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ( i = 0; i < n; i++ ) {
|
|
||||||
pos = lines[i].indexOf(' ');
|
|
||||||
pretty.push(whitespaces.slice(0, max - pos) + lines[i]);
|
|
||||||
}
|
|
||||||
rawSettingsInput.value = pretty.join('\n') + '\n';
|
|
||||||
rawSettingsChanged();
|
|
||||||
rawSettingsInput.focus();
|
|
||||||
};
|
|
||||||
messaging.send('dashboard', { what: 'readRawSettings' }, onRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var applyChanges = function() {
|
const renderRawSettings = async function(first) {
|
||||||
messaging.send(
|
const raw = await vAPI.messaging.send('dashboard', {
|
||||||
'dashboard',
|
what: 'readRawSettings'
|
||||||
{
|
});
|
||||||
what: 'writeRawSettings',
|
cachedData = hashFromRawSettings(raw);
|
||||||
content: rawSettingsInput.value
|
const lines = raw.split('\n');
|
||||||
},
|
const n = lines.length;
|
||||||
renderRawSettings
|
let max = 0;
|
||||||
);
|
for ( let i = 0; i < n; i++ ) {
|
||||||
|
const pos = lines[i].indexOf(' ');
|
||||||
|
if ( pos > max ) { max = pos; }
|
||||||
|
}
|
||||||
|
const pretty = [];
|
||||||
|
for ( let i = 0; i < n; i++ ) {
|
||||||
|
const pos = lines[i].indexOf(' ');
|
||||||
|
pretty.push(' '.repeat(max - pos) + lines[i]);
|
||||||
|
}
|
||||||
|
pretty.push('');
|
||||||
|
cmEditor.setValue(pretty.join('\n'));
|
||||||
|
if ( first ) {
|
||||||
|
cmEditor.clearHistory();
|
||||||
|
}
|
||||||
|
rawSettingsChanged();
|
||||||
|
cmEditor.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const applyChanges = async function() {
|
||||||
|
await vAPI.messaging.send('dashboard', {
|
||||||
|
what: 'writeRawSettings',
|
||||||
|
content: cmEditor.getValue(),
|
||||||
|
});
|
||||||
|
renderRawSettings();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -108,8 +119,9 @@ var applyChanges = function() {
|
||||||
uDom('#rawSettings').on('input', rawSettingsChanged);
|
uDom('#rawSettings').on('input', rawSettingsChanged);
|
||||||
uDom('#rawSettingsApply').on('click', applyChanges);
|
uDom('#rawSettingsApply').on('click', applyChanges);
|
||||||
|
|
||||||
renderRawSettings();
|
renderRawSettings(true);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
|
|
|
@ -25,37 +25,38 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var cachedSettings = {};
|
let cachedSettings = {};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function changeUserSettings(name, value) {
|
const changeUserSettings = function(name, value) {
|
||||||
vAPI.messaging.send('settings.js', {
|
vAPI.messaging.send('dashboard', {
|
||||||
what: 'userSettings',
|
what: 'userSettings',
|
||||||
name: name,
|
name,
|
||||||
value: value
|
value
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function changeMatrixSwitch(name, state) {
|
const changeMatrixSwitch = function(switchName, state) {
|
||||||
vAPI.messaging.send('settings.js', {
|
vAPI.messaging.send('dashboard', {
|
||||||
what: 'setMatrixSwitch',
|
what: 'setMatrixSwitch',
|
||||||
switchName: name,
|
switchName,
|
||||||
state: state
|
state
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function onChangeValueHandler(elem, setting, min, max) {
|
const onChangeValueHandler = function (elem, setting, min, max) {
|
||||||
var oldVal = cachedSettings.userSettings[setting];
|
const oldVal = cachedSettings.userSettings[setting];
|
||||||
var newVal = Math.round(parseFloat(elem.value));
|
let newVal = Math.round(parseFloat(elem.value));
|
||||||
if ( typeof newVal !== 'number' ) {
|
if ( typeof newVal !== 'number' ) {
|
||||||
newVal = oldVal;
|
newVal = oldVal;
|
||||||
} else {
|
} else {
|
||||||
|
@ -66,11 +67,11 @@ function onChangeValueHandler(elem, setting, min, max) {
|
||||||
if ( newVal !== oldVal ) {
|
if ( newVal !== oldVal ) {
|
||||||
changeUserSettings(setting, newVal);
|
changeUserSettings(setting, newVal);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function prepareToDie() {
|
const prepareToDie = function() {
|
||||||
onChangeValueHandler(
|
onChangeValueHandler(
|
||||||
uDom.nodeFromId('deleteUnusedSessionCookiesAfter'),
|
uDom.nodeFromId('deleteUnusedSessionCookiesAfter'),
|
||||||
'deleteUnusedSessionCookiesAfter',
|
'deleteUnusedSessionCookiesAfter',
|
||||||
|
@ -81,12 +82,12 @@ function prepareToDie() {
|
||||||
'clearBrowserCacheAfter',
|
'clearBrowserCacheAfter',
|
||||||
15, 1440
|
15, 1440
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function onInputChanged(ev) {
|
const onInputChanged = function(ev) {
|
||||||
var target = ev.target;
|
const target = ev.target;
|
||||||
|
|
||||||
switch ( target.id ) {
|
switch ( target.id ) {
|
||||||
case 'displayTextSize':
|
case 'displayTextSize':
|
||||||
|
@ -133,63 +134,60 @@ function onInputChanged(ev) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function synchronizeWidgets() {
|
const synchronizeWidgets = function() {
|
||||||
var e1, e2;
|
const e1 = uDom.nodeFromId('collapseBlocked');
|
||||||
|
const e2 = uDom.nodeFromId('collapseBlacklisted');
|
||||||
e1 = uDom.nodeFromId('collapseBlocked');
|
|
||||||
e2 = uDom.nodeFromId('collapseBlacklisted');
|
|
||||||
if ( e1.checked ) {
|
if ( e1.checked ) {
|
||||||
e2.setAttribute('disabled', '');
|
e2.setAttribute('disabled', '');
|
||||||
} else {
|
} else {
|
||||||
e2.removeAttribute('disabled');
|
e2.removeAttribute('disabled');
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
vAPI.messaging.send('dashboard', {
|
||||||
|
what: 'getUserSettings'
|
||||||
|
}).then(settings => {
|
||||||
|
// Cache copy
|
||||||
|
cachedSettings = settings;
|
||||||
|
|
||||||
|
const userSettings = settings.userSettings;
|
||||||
|
const matrixSwitches = settings.matrixSwitches;
|
||||||
|
|
||||||
|
uDom('[data-setting-bool]').forEach(function(elem){
|
||||||
|
elem.prop('checked', userSettings[elem.prop('id')] === true);
|
||||||
|
});
|
||||||
|
|
||||||
|
uDom('[data-matrix-switch]').forEach(function(elem){
|
||||||
|
const switchName = elem.attr('data-matrix-switch');
|
||||||
|
if ( typeof switchName === 'string' && switchName !== '' ) {
|
||||||
|
elem.prop('checked', matrixSwitches[switchName] === true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
uDom.nodeFromId('displayTextSize').value =
|
||||||
|
parseInt(userSettings.displayTextSize, 10) || 14;
|
||||||
|
|
||||||
|
uDom.nodeFromId('popupScopeLevel').value = userSettings.popupScopeLevel;
|
||||||
|
uDom.nodeFromId('deleteUnusedSessionCookiesAfter').value =
|
||||||
|
userSettings.deleteUnusedSessionCookiesAfter;
|
||||||
|
uDom.nodeFromId('clearBrowserCacheAfter').value =
|
||||||
|
userSettings.clearBrowserCacheAfter;
|
||||||
|
|
||||||
|
synchronizeWidgets();
|
||||||
|
|
||||||
|
document.addEventListener('change', onInputChanged);
|
||||||
|
|
||||||
|
// https://github.com/gorhill/httpswitchboard/issues/197
|
||||||
|
uDom(window).on('beforeunload', prepareToDie);
|
||||||
|
});
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// <<<<< end of local scope
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
vAPI.messaging.send(
|
|
||||||
'settings.js',
|
|
||||||
{ what: 'getUserSettings' },
|
|
||||||
function onSettingsReceived(settings) {
|
|
||||||
// Cache copy
|
|
||||||
cachedSettings = settings;
|
|
||||||
|
|
||||||
var userSettings = settings.userSettings;
|
|
||||||
var matrixSwitches = settings.matrixSwitches;
|
|
||||||
|
|
||||||
uDom('[data-setting-bool]').forEach(function(elem){
|
|
||||||
elem.prop('checked', userSettings[elem.prop('id')] === true);
|
|
||||||
});
|
|
||||||
|
|
||||||
uDom('[data-matrix-switch]').forEach(function(elem){
|
|
||||||
var switchName = elem.attr('data-matrix-switch');
|
|
||||||
if ( typeof switchName === 'string' && switchName !== '' ) {
|
|
||||||
elem.prop('checked', matrixSwitches[switchName] === true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
uDom.nodeFromId('displayTextSize').value =
|
|
||||||
parseInt(userSettings.displayTextSize, 10) || 14;
|
|
||||||
|
|
||||||
uDom.nodeFromId('popupScopeLevel').value = userSettings.popupScopeLevel;
|
|
||||||
uDom.nodeFromId('deleteUnusedSessionCookiesAfter').value =
|
|
||||||
userSettings.deleteUnusedSessionCookiesAfter;
|
|
||||||
uDom.nodeFromId('clearBrowserCacheAfter').value =
|
|
||||||
userSettings.clearBrowserCacheAfter;
|
|
||||||
|
|
||||||
synchronizeWidgets();
|
|
||||||
|
|
||||||
document.addEventListener('change', onInputChanged);
|
|
||||||
|
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/197
|
|
||||||
uDom(window).on('beforeunload', prepareToDie);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
114
src/js/start.js
114
src/js/start.js
|
@ -19,37 +19,56 @@
|
||||||
Home: https://github.com/gorhill/uMatrix
|
Home: https://github.com/gorhill/uMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ORDER IS IMPORTANT
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Load everything
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var µm = µMatrix;
|
(async ( ) => {
|
||||||
|
const µm = µMatrix;
|
||||||
|
|
||||||
/******************************************************************************/
|
await Promise.all([
|
||||||
|
µm.loadPublicSuffixList(),
|
||||||
|
µm.loadUserSettings(),
|
||||||
|
]);
|
||||||
|
log.info(`PSL and user settings ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
|
||||||
var processCallbackQueue = function(queue, callback) {
|
{
|
||||||
var processOne = function() {
|
let trieDetails;
|
||||||
var fn = queue.pop();
|
try {
|
||||||
if ( fn ) {
|
trieDetails = JSON.parse(
|
||||||
fn(processOne);
|
vAPI.localStorage.getItem('ubiquitousBlacklist.trieDetails')
|
||||||
} else if ( typeof callback === 'function' ) {
|
);
|
||||||
callback();
|
} catch(ex) {
|
||||||
}
|
}
|
||||||
};
|
µm.ubiquitousBlacklist = new µm.HNTrieContainer(trieDetails);
|
||||||
processOne();
|
µm.ubiquitousBlacklist.initWASM();
|
||||||
};
|
}
|
||||||
|
log.info(`Ubiquitous block container ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
|
||||||
/******************************************************************************/
|
await Promise.all([
|
||||||
|
µm.loadRawSettings(),
|
||||||
|
µm.loadMatrix(),
|
||||||
|
µm.loadHostsFiles(),
|
||||||
|
]);
|
||||||
|
log.info(`Ubiquitous block rules ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
|
||||||
|
{
|
||||||
|
const pageStore =
|
||||||
|
µm.pageStoreFactory(µm.tabContextManager.mustLookup(vAPI.noTabId));
|
||||||
|
pageStore.title = vAPI.i18n('statsPageDetailedBehindTheScenePage');
|
||||||
|
µm.pageStores.set(vAPI.noTabId, pageStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = await vAPI.tabs.query({ url: '<all_urls>' });
|
||||||
|
if ( Array.isArray(tabs) ) {
|
||||||
|
for ( const tab of tabs ) {
|
||||||
|
µm.tabContextManager.push(tab.id, tab.url, 'newURL');
|
||||||
|
µm.bindTabToPageStats(tab.id);
|
||||||
|
µm.setPageStoreTitle(tab.id, tab.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info(`Tab stores ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
|
||||||
var onAllDone = function() {
|
|
||||||
µm.webRequest.start();
|
µm.webRequest.start();
|
||||||
|
|
||||||
µm.loadRecipes();
|
µm.loadRecipes();
|
||||||
|
@ -59,57 +78,6 @@ var onAllDone = function() {
|
||||||
// asset updater.
|
// asset updater.
|
||||||
µm.assets.addObserver(µm.assetObserver.bind(µm));
|
µm.assets.addObserver(µm.assetObserver.bind(µm));
|
||||||
µm.scheduleAssetUpdater(µm.userSettings.autoUpdate ? 7 * 60 * 1000 : 0);
|
µm.scheduleAssetUpdater(µm.userSettings.autoUpdate ? 7 * 60 * 1000 : 0);
|
||||||
|
|
||||||
vAPI.cloud.start([ 'myRulesPane' ]);
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var onPSLReady = function() {
|
|
||||||
// TODO: Promisify
|
|
||||||
let count = 4;
|
|
||||||
const countdown = ( ) => {
|
|
||||||
count -= 1;
|
|
||||||
if ( count !== 0 ) { return; }
|
|
||||||
onAllDone();
|
|
||||||
};
|
|
||||||
|
|
||||||
µm.loadRawSettings(countdown);
|
|
||||||
µm.loadMatrix(countdown);
|
|
||||||
µm.loadHostsFiles(countdown);
|
|
||||||
|
|
||||||
vAPI.tabs.getAll(tabs => {
|
|
||||||
const pageStore =
|
|
||||||
µm.pageStoreFactory(µm.tabContextManager.mustLookup(vAPI.noTabId));
|
|
||||||
pageStore.title = vAPI.i18n('statsPageDetailedBehindTheScenePage');
|
|
||||||
µm.pageStores.set(vAPI.noTabId, pageStore);
|
|
||||||
|
|
||||||
if ( Array.isArray(tabs) ) {
|
|
||||||
for ( const tab of tabs ) {
|
|
||||||
µm.tabContextManager.push(tab.id, tab.url, 'newURL');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
countdown();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
processCallbackQueue(µm.onBeforeStartQueue, function() {
|
|
||||||
// TODO: Promisify
|
|
||||||
let count = 2;
|
|
||||||
const countdown = ( ) => {
|
|
||||||
count -= 1;
|
|
||||||
if ( count !== 0 ) { return; }
|
|
||||||
onPSLReady();
|
|
||||||
};
|
|
||||||
|
|
||||||
µm.publicSuffixList.load(countdown);
|
|
||||||
µm.loadUserSettings(countdown);
|
|
||||||
});
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
File diff suppressed because it is too large
Load diff
553
src/js/tab.js
553
src/js/tab.js
|
@ -19,16 +19,16 @@
|
||||||
Home: https://github.com/gorhill/uMatrix
|
Home: https://github.com/gorhill/uMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var µm = µMatrix;
|
(( ) => {
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const µm = µMatrix;
|
||||||
|
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/303
|
// https://github.com/gorhill/httpswitchboard/issues/303
|
||||||
// Some kind of trick going on here:
|
// Some kind of trick going on here:
|
||||||
|
@ -152,35 +152,9 @@ housekeep itself.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
µm.tabContextManager = (function() {
|
µMatrix.tabContextManager = (( ) => {
|
||||||
let tabContexts = new Map();
|
const µm = µMatrix;
|
||||||
|
const tabContexts = new Map();
|
||||||
let urlToTabIds = {
|
|
||||||
associations: new Map(),
|
|
||||||
associate: function(tabId, url) {
|
|
||||||
let tabIds = this.associations.get(url);
|
|
||||||
if ( tabIds === undefined ) {
|
|
||||||
this.associations.set(url, (tabIds = []));
|
|
||||||
} else {
|
|
||||||
let i = tabIds.indexOf(tabId);
|
|
||||||
if ( i !== -1 ) {
|
|
||||||
tabIds.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tabIds.push(tabId);
|
|
||||||
},
|
|
||||||
dissociate: function(tabId, url) {
|
|
||||||
let tabIds = this.associations.get(url);
|
|
||||||
if ( tabIds === undefined ) { return; }
|
|
||||||
let i = tabIds.indexOf(tabId);
|
|
||||||
if ( i !== -1 ) {
|
|
||||||
tabIds.splice(i, 1);
|
|
||||||
}
|
|
||||||
if ( tabIds.length === 0 ) {
|
|
||||||
this.associations.delete(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/1001
|
// https://github.com/chrisaljoudi/uBlock/issues/1001
|
||||||
// This is to be used as last-resort fallback in case a tab is found to not
|
// This is to be used as last-resort fallback in case a tab is found to not
|
||||||
|
@ -188,27 +162,32 @@ housekeep itself.
|
||||||
let mostRecentRootDocURL = '';
|
let mostRecentRootDocURL = '';
|
||||||
let mostRecentRootDocURLTimestamp = 0;
|
let mostRecentRootDocURLTimestamp = 0;
|
||||||
|
|
||||||
let gcPeriod = 31 * 60 * 1000; // every 31 minutes
|
const onTabCreated = async function(/* createDetails */) {
|
||||||
|
};
|
||||||
|
|
||||||
|
const gcPeriod = 10 * 60 * 1000;
|
||||||
|
|
||||||
// A pushed entry is removed from the stack unless it is committed with
|
// A pushed entry is removed from the stack unless it is committed with
|
||||||
// a set time.
|
// a set time.
|
||||||
let StackEntry = function(url, commit) {
|
const StackEntry = function(url, commit) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.committed = commit;
|
this.committed = commit;
|
||||||
this.tstamp = Date.now();
|
this.tstamp = Date.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
let TabContext = function(tabId) {
|
const TabContext = function(tabId) {
|
||||||
this.tabId = tabId;
|
this.tabId = tabId;
|
||||||
this.stack = [];
|
this.stack = [];
|
||||||
this.rawURL =
|
this.rawURL =
|
||||||
this.normalURL =
|
this.normalURL =
|
||||||
this.scheme =
|
this.origin =
|
||||||
this.rootHostname =
|
this.rootHostname =
|
||||||
this.rootDomain = '';
|
this.rootDomain = '';
|
||||||
this.secure = false;
|
|
||||||
this.commitTimer = null;
|
this.commitTimer = null;
|
||||||
this.gcTimer = null;
|
this.gcTimer = null;
|
||||||
|
this.onGCBarrier = false;
|
||||||
|
this.netFiltering = true;
|
||||||
|
this.netFilteringReadTime = 0;
|
||||||
|
|
||||||
tabContexts.set(tabId, this);
|
tabContexts.set(tabId, this);
|
||||||
};
|
};
|
||||||
|
@ -219,36 +198,44 @@ housekeep itself.
|
||||||
clearTimeout(this.gcTimer);
|
clearTimeout(this.gcTimer);
|
||||||
this.gcTimer = null;
|
this.gcTimer = null;
|
||||||
}
|
}
|
||||||
urlToTabIds.dissociate(this.tabId, this.rawURL);
|
|
||||||
tabContexts.delete(this.tabId);
|
tabContexts.delete(this.tabId);
|
||||||
};
|
};
|
||||||
|
|
||||||
TabContext.prototype.onTab = function(tab) {
|
TabContext.prototype.onTab = function(tab) {
|
||||||
if ( tab ) {
|
if ( tab ) {
|
||||||
this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod);
|
this.gcTimer = vAPI.setTimeout(( ) => this.onGC(), gcPeriod);
|
||||||
} else {
|
} else {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TabContext.prototype.onGC = function() {
|
TabContext.prototype.onGC = async function() {
|
||||||
this.gcTimer = null;
|
|
||||||
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
|
||||||
vAPI.tabs.get(this.tabId, this.onTab.bind(this));
|
// https://github.com/gorhill/uBlock/issues/1713
|
||||||
|
// For unknown reasons, Firefox's setTimeout() will sometimes
|
||||||
|
// causes the callback function to be called immediately, bypassing
|
||||||
|
// the main event loop. For now this should prevent uBO from crashing
|
||||||
|
// as a result of the bad setTimeout() behavior.
|
||||||
|
if ( this.onGCBarrier ) { return; }
|
||||||
|
this.onGCBarrier = true;
|
||||||
|
this.gcTimer = null;
|
||||||
|
const tab = await vAPI.tabs.get(this.tabId);
|
||||||
|
this.onTab(tab);
|
||||||
|
this.onGCBarrier = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/248
|
// https://github.com/gorhill/uBlock/issues/248
|
||||||
// Stack entries have to be committed to stick. Non-committed stack
|
// Stack entries have to be committed to stick. Non-committed stack
|
||||||
// entries are removed after a set delay.
|
// entries are removed after a set delay.
|
||||||
TabContext.prototype.onCommit = function() {
|
TabContext.prototype.onCommit = function() {
|
||||||
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.commitTimer = null;
|
this.commitTimer = null;
|
||||||
// Remove uncommitted entries at the top of the stack.
|
// Remove uncommitted entries at the top of the stack.
|
||||||
let i = this.stack.length;
|
let i = this.stack.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
if ( this.stack[i].committed ) {
|
if ( this.stack[i].committed ) { break; }
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// https://github.com/gorhill/uBlock/issues/300
|
// https://github.com/gorhill/uBlock/issues/300
|
||||||
// If no committed entry was found, fall back on the bottom-most one
|
// If no committed entry was found, fall back on the bottom-most one
|
||||||
|
@ -261,7 +248,6 @@ housekeep itself.
|
||||||
if ( i < this.stack.length ) {
|
if ( i < this.stack.length ) {
|
||||||
this.stack.length = i;
|
this.stack.length = i;
|
||||||
this.update();
|
this.update();
|
||||||
µm.bindTabToPageStats(this.tabId, 'newURL');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -270,76 +256,103 @@ housekeep itself.
|
||||||
// want to flush it.
|
// want to flush it.
|
||||||
TabContext.prototype.autodestroy = function() {
|
TabContext.prototype.autodestroy = function() {
|
||||||
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
|
||||||
this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod);
|
this.gcTimer = vAPI.setTimeout(( ) => this.onGC(), gcPeriod);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update just force all properties to be updated to match the most recent
|
// Update just force all properties to be updated to match the most recent
|
||||||
// root URL.
|
// root URL.
|
||||||
TabContext.prototype.update = function() {
|
TabContext.prototype.update = function() {
|
||||||
urlToTabIds.dissociate(this.tabId, this.rawURL);
|
this.netFilteringReadTime = 0;
|
||||||
if ( this.stack.length === 0 ) {
|
if ( this.stack.length === 0 ) {
|
||||||
this.rawURL = this.normalURL = this.scheme =
|
this.rawURL =
|
||||||
this.rootHostname = this.rootDomain = '';
|
this.normalURL =
|
||||||
this.secure = false;
|
this.origin =
|
||||||
|
this.rootHostname =
|
||||||
|
this.rootDomain = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.rawURL = this.stack[this.stack.length - 1].url;
|
const stackEntry = this.stack[this.stack.length - 1];
|
||||||
|
this.rawURL = stackEntry.url;
|
||||||
this.normalURL = µm.normalizePageURL(this.tabId, this.rawURL);
|
this.normalURL = µm.normalizePageURL(this.tabId, this.rawURL);
|
||||||
this.scheme = µm.URI.schemeFromURI(this.rawURL);
|
this.origin = µm.URI.originFromURI(this.normalURL);
|
||||||
this.rootHostname = µm.URI.hostnameFromURI(this.normalURL);
|
this.rootHostname = µm.URI.hostnameFromURI(this.origin);
|
||||||
this.rootDomain = µm.URI.domainFromHostname(this.rootHostname) || this.rootHostname;
|
this.rootDomain =
|
||||||
this.secure = µm.URI.isSecureScheme(this.scheme);
|
µm.URI.domainFromHostname(this.rootHostname) ||
|
||||||
urlToTabIds.associate(this.tabId, this.rawURL);
|
this.rootHostname;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Called whenever a candidate root URL is spotted for the tab.
|
// Called whenever a candidate root URL is spotted for the tab.
|
||||||
TabContext.prototype.push = function(url, context) {
|
TabContext.prototype.push = function(url) {
|
||||||
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
|
||||||
let committed = context !== undefined;
|
|
||||||
let count = this.stack.length;
|
|
||||||
let topEntry = this.stack[count - 1];
|
|
||||||
if ( topEntry && topEntry.url === url ) {
|
|
||||||
if ( committed ) {
|
|
||||||
topEntry.committed = true;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const count = this.stack.length;
|
||||||
|
if ( count !== 0 && this.stack[count - 1].url === url ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.stack.push(new StackEntry(url));
|
||||||
|
this.update();
|
||||||
if ( this.commitTimer !== null ) {
|
if ( this.commitTimer !== null ) {
|
||||||
clearTimeout(this.commitTimer);
|
clearTimeout(this.commitTimer);
|
||||||
}
|
}
|
||||||
if ( committed ) {
|
this.commitTimer = vAPI.setTimeout(( ) => this.onCommit(), 500);
|
||||||
this.stack = [new StackEntry(url, true)];
|
};
|
||||||
} else {
|
|
||||||
this.stack.push(new StackEntry(url));
|
// This tells that the url is definitely the one to be associated with the
|
||||||
this.commitTimer = vAPI.setTimeout(this.onCommit.bind(this), 1000);
|
// tab, there is no longer any ambiguity about which root URL is really
|
||||||
|
// sitting in which tab.
|
||||||
|
TabContext.prototype.commit = function(url) {
|
||||||
|
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
|
||||||
|
if ( this.stack.length !== 0 ) {
|
||||||
|
const top = this.stack[this.stack.length - 1];
|
||||||
|
if ( top.url === url && top.committed ) { return false; }
|
||||||
}
|
}
|
||||||
|
this.stack = [new StackEntry(url, true)];
|
||||||
this.update();
|
this.update();
|
||||||
µm.bindTabToPageStats(this.tabId, context);
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
TabContext.prototype.getNetFilteringSwitch = function() {
|
||||||
|
if ( this.netFilteringReadTime > µm.netWhitelistModifyTime ) {
|
||||||
|
return this.netFiltering;
|
||||||
|
}
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/1078
|
||||||
|
// Use both the raw and normalized URLs.
|
||||||
|
this.netFiltering = µm.getNetFilteringSwitch(this.normalURL);
|
||||||
|
if (
|
||||||
|
this.netFiltering &&
|
||||||
|
this.rawURL !== this.normalURL &&
|
||||||
|
this.rawURL !== ''
|
||||||
|
) {
|
||||||
|
this.netFiltering = µm.getNetFilteringSwitch(this.rawURL);
|
||||||
|
}
|
||||||
|
this.netFilteringReadTime = Date.now();
|
||||||
|
return this.netFiltering;
|
||||||
};
|
};
|
||||||
|
|
||||||
// These are to be used for the API of the tab context manager.
|
// These are to be used for the API of the tab context manager.
|
||||||
|
|
||||||
let push = function(tabId, url, context) {
|
const push = function(tabId, url) {
|
||||||
let entry = tabContexts.get(tabId);
|
let entry = tabContexts.get(tabId);
|
||||||
if ( entry === undefined ) {
|
if ( entry === undefined ) {
|
||||||
entry = new TabContext(tabId);
|
entry = new TabContext(tabId);
|
||||||
entry.autodestroy();
|
entry.autodestroy();
|
||||||
}
|
}
|
||||||
entry.push(url, context);
|
entry.push(url);
|
||||||
mostRecentRootDocURL = url;
|
mostRecentRootDocURL = url;
|
||||||
mostRecentRootDocURLTimestamp = Date.now();
|
mostRecentRootDocURLTimestamp = Date.now();
|
||||||
return entry;
|
return entry;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Find a tab context for a specific tab.
|
||||||
|
const lookup = function(tabId) {
|
||||||
|
return tabContexts.get(tabId) || null;
|
||||||
|
};
|
||||||
|
|
||||||
// Find a tab context for a specific tab. If none is found, attempt to
|
// Find a tab context for a specific tab. If none is found, attempt to
|
||||||
// fix this. When all fail, the behind-the-scene context is returned.
|
// fix this. When all fail, the behind-the-scene context is returned.
|
||||||
let mustLookup = function(tabId, url) {
|
const mustLookup = function(tabId) {
|
||||||
let entry;
|
const entry = tabContexts.get(tabId);
|
||||||
if ( url !== undefined ) {
|
|
||||||
entry = push(tabId, url);
|
|
||||||
} else {
|
|
||||||
entry = tabContexts.get(tabId);
|
|
||||||
}
|
|
||||||
if ( entry !== undefined ) {
|
if ( entry !== undefined ) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
@ -347,7 +360,10 @@ housekeep itself.
|
||||||
// Google Hangout popup opens without a root frame. So for now we will
|
// Google Hangout popup opens without a root frame. So for now we will
|
||||||
// just discard that best-guess root frame if it is too far in the
|
// just discard that best-guess root frame if it is too far in the
|
||||||
// future, at which point it ceases to be a "best guess".
|
// future, at which point it ceases to be a "best guess".
|
||||||
if ( mostRecentRootDocURL !== '' && mostRecentRootDocURLTimestamp + 500 < Date.now() ) {
|
if (
|
||||||
|
mostRecentRootDocURL !== '' &&
|
||||||
|
mostRecentRootDocURLTimestamp + 500 < Date.now()
|
||||||
|
) {
|
||||||
mostRecentRootDocURL = '';
|
mostRecentRootDocURL = '';
|
||||||
}
|
}
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/1001
|
// https://github.com/chrisaljoudi/uBlock/issues/1001
|
||||||
|
@ -367,67 +383,139 @@ housekeep itself.
|
||||||
return tabContexts.get(vAPI.noTabId);
|
return tabContexts.get(vAPI.noTabId);
|
||||||
};
|
};
|
||||||
|
|
||||||
let lookup = function(tabId) {
|
const commit = function(tabId, url) {
|
||||||
return tabContexts.get(tabId) || null;
|
let entry = tabContexts.get(tabId);
|
||||||
|
if ( entry === undefined ) {
|
||||||
|
entry = push(tabId, url);
|
||||||
|
} else {
|
||||||
|
entry.commit(url);
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
};
|
};
|
||||||
|
|
||||||
let tabIdFromURL = function(url) {
|
const exists = function(tabId) {
|
||||||
let tabIds = urlToTabIds.associations.get(url);
|
return tabContexts.get(tabId) !== undefined;
|
||||||
if ( tabIds === undefined ) { return -1; }
|
|
||||||
return tabIds[tabIds.length - 1];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Behind-the-scene tab context
|
// Behind-the-scene tab context
|
||||||
(function() {
|
{
|
||||||
let entry = new TabContext(vAPI.noTabId);
|
const entry = new TabContext(vAPI.noTabId);
|
||||||
entry.stack.push(new StackEntry('', true));
|
entry.stack.push(new StackEntry('', true));
|
||||||
entry.rawURL = '';
|
entry.rawURL = '';
|
||||||
entry.normalURL = µm.normalizePageURL(entry.tabId);
|
entry.normalURL = µm.normalizePageURL(entry.tabId);
|
||||||
entry.rootHostname = µm.URI.hostnameFromURI(entry.normalURL);
|
entry.origin = µm.URI.originFromURI(entry.normalURL);
|
||||||
entry.rootDomain = µm.URI.domainFromHostname(entry.rootHostname) || entry.rootHostname;
|
entry.rootHostname = µm.URI.hostnameFromURI(entry.origin);
|
||||||
})();
|
entry.rootDomain = µm.URI.domainFromHostname(entry.rootHostname);
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/gorhill/uMatrix/issues/513
|
// Context object, typically to be used to feed filtering engines.
|
||||||
// Force a badge update here, it could happen that all the subsequent
|
const contextJunkyard = [];
|
||||||
// network requests are already in the page store, which would cause
|
const Context = class {
|
||||||
// the badge to no be updated for these network requests.
|
constructor(tabId) {
|
||||||
|
this.init(tabId);
|
||||||
vAPI.tabs.onNavigation = function(details) {
|
}
|
||||||
let tabId = details.tabId;
|
init(tabId) {
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
const tabContext = lookup(tabId);
|
||||||
push(tabId, details.url, 'newURL');
|
this.rootHostname = tabContext.rootHostname;
|
||||||
µm.updateBadgeAsync(tabId);
|
this.rootDomain = tabContext.rootDomain;
|
||||||
};
|
this.pageHostname =
|
||||||
|
this.pageDomain =
|
||||||
// https://github.com/gorhill/uMatrix/issues/872
|
this.requestURL =
|
||||||
// `changeInfo.url` may not always be available (Firefox).
|
this.origin =
|
||||||
|
this.requestHostname =
|
||||||
vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
|
this.requestDomain = '';
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
return this;
|
||||||
if ( typeof tab.url !== 'string' || tab.url === '' ) { return; }
|
}
|
||||||
let url = changeInfo.url || tab.url;
|
dispose() {
|
||||||
if ( url ) {
|
contextJunkyard.push(this);
|
||||||
push(tabId, url, 'updateURL');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.tabs.onClosed = function(tabId) {
|
const createContext = function(tabId) {
|
||||||
µm.unbindTabFromPageStats(tabId);
|
if ( contextJunkyard.length ) {
|
||||||
let entry = tabContexts.get(tabId);
|
return contextJunkyard.pop().init(tabId);
|
||||||
if ( entry instanceof TabContext ) {
|
|
||||||
entry.destroy();
|
|
||||||
}
|
}
|
||||||
|
return new Context(tabId);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
push: push,
|
push,
|
||||||
lookup: lookup,
|
commit,
|
||||||
mustLookup: mustLookup,
|
lookup,
|
||||||
tabIdFromURL: tabIdFromURL
|
mustLookup,
|
||||||
|
exists,
|
||||||
|
createContext,
|
||||||
|
onTabCreated,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
vAPI.tabs.registerListeners();
|
/******************************************************************************/
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
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
|
||||||
|
µMatrix.updateToolbarIcon(details.tabId);
|
||||||
|
//µMatrix.contextMenu.update(details.tabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed(tabId) {
|
||||||
|
super.onClosed(tabId);
|
||||||
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||||
|
µMatrix.unbindTabFromPageStats(tabId);
|
||||||
|
//µMatrix.contextMenu.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
onCreated(details) {
|
||||||
|
super.onCreated(details);
|
||||||
|
µMatrix.tabContextManager.onTabCreated(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 µm = µMatrix;
|
||||||
|
if ( details.frameId === 0 ) {
|
||||||
|
µm.tabContextManager.commit(details.tabId, details.url);
|
||||||
|
µm.bindTabToPageStats(details.tabId, 'tabCommitted');
|
||||||
|
}
|
||||||
|
if ( µm.canInjectScriptletsNow ) {
|
||||||
|
const pageStore = µm.pageStoreFromTabId(details.tabId);
|
||||||
|
if ( pageStore !== null && pageStore.getNetFilteringSwitch() ) {
|
||||||
|
µm.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 ( typeof tab.url !== 'string' || tab.url === '' ) { return; }
|
||||||
|
if ( typeof changeInfo.url === 'string' && changeInfo.url !== '' ) {
|
||||||
|
µMatrix.tabContextManager.commit(tabId, changeInfo.url);
|
||||||
|
µMatrix.bindTabToPageStats(tabId, 'tabUpdated');
|
||||||
|
}
|
||||||
|
if ( typeof changeInfo.title === 'string' && changeInfo.title !== '' ) {
|
||||||
|
µMatrix.setPageStoreTitle(tabId, changeInfo.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
vAPI.tabs = new vAPI.Tabs();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -435,12 +523,12 @@ vAPI.tabs.registerListeners();
|
||||||
// Create an entry for the tab if it doesn't exist
|
// Create an entry for the tab if it doesn't exist
|
||||||
|
|
||||||
µm.bindTabToPageStats = function(tabId, context) {
|
µm.bindTabToPageStats = function(tabId, context) {
|
||||||
this.updateBadgeAsync(tabId);
|
this.updateToolbarIcon(tabId);
|
||||||
|
|
||||||
// Do not create a page store for URLs which are of no interests
|
// Do not create a page store for URLs which are of no interests
|
||||||
// Example: dev console
|
// Example: dev console
|
||||||
let tabContext = this.tabContextManager.lookup(tabId);
|
const tabContext = this.tabContextManager.lookup(tabId);
|
||||||
if ( tabContext === null ) { return; }
|
if ( tabContext === null ) { return null; }
|
||||||
|
|
||||||
// rhill 2013-11-24: Never ever rebind behind-the-scene
|
// rhill 2013-11-24: Never ever rebind behind-the-scene
|
||||||
// virtual tab.
|
// virtual tab.
|
||||||
|
@ -449,7 +537,7 @@ vAPI.tabs.registerListeners();
|
||||||
return this.pageStores.get(tabId);
|
return this.pageStores.get(tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
let normalURL = tabContext.normalURL;
|
const normalURL = tabContext.normalURL;
|
||||||
let pageStore = this.pageStores.get(tabId);
|
let pageStore = this.pageStores.get(tabId);
|
||||||
|
|
||||||
// The previous page URL, if any, associated with the tab
|
// The previous page URL, if any, associated with the tab
|
||||||
|
@ -460,18 +548,17 @@ vAPI.tabs.registerListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/gorhill/uMatrix/issues/37
|
// https://github.com/gorhill/uMatrix/issues/37
|
||||||
// Just rebind whenever possible: the URL changed, but the document
|
// Just rebind whenever possible: the URL changed, but the document
|
||||||
// maybe is the same.
|
// maybe is the same.
|
||||||
// Example: Google Maps, Github
|
// Example: Google Maps, Github
|
||||||
// https://github.com/gorhill/uMatrix/issues/72
|
// https://github.com/gorhill/uMatrix/issues/72
|
||||||
// Need to double-check that the new scope is same as old scope
|
// Need to double-check that the new scope is same as old scope
|
||||||
if (
|
if (
|
||||||
context === 'updateURL' &&
|
context === 'tabUpdated' &&
|
||||||
pageStore.pageHostname === tabContext.rootHostname
|
pageStore.pageHostname === tabContext.rootHostname
|
||||||
) {
|
) {
|
||||||
pageStore.rawURL = tabContext.rawURL;
|
pageStore.rawURL = tabContext.rawURL;
|
||||||
pageStore.pageUrl = normalURL;
|
pageStore.pageUrl = normalURL;
|
||||||
this.updateTitle(tabId);
|
|
||||||
this.pageStoresToken = Date.now();
|
this.pageStoresToken = Date.now();
|
||||||
return pageStore;
|
return pageStore;
|
||||||
}
|
}
|
||||||
|
@ -486,7 +573,6 @@ vAPI.tabs.registerListeners();
|
||||||
pageStore = this.pageStoreFactory(tabContext);
|
pageStore = this.pageStoreFactory(tabContext);
|
||||||
}
|
}
|
||||||
this.pageStores.set(tabId, pageStore);
|
this.pageStores.set(tabId, pageStore);
|
||||||
this.updateTitle(tabId);
|
|
||||||
this.pageStoresToken = Date.now();
|
this.pageStoresToken = Date.now();
|
||||||
|
|
||||||
return pageStore;
|
return pageStore;
|
||||||
|
@ -497,7 +583,7 @@ vAPI.tabs.registerListeners();
|
||||||
µm.unbindTabFromPageStats = function(tabId) {
|
µm.unbindTabFromPageStats = function(tabId) {
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||||
|
|
||||||
let pageStore = this.pageStores.get(tabId);
|
const pageStore = this.pageStores.get(tabId);
|
||||||
if ( pageStore === undefined ) { return; }
|
if ( pageStore === undefined ) { return; }
|
||||||
|
|
||||||
this.pageStores.delete(tabId);
|
this.pageStores.delete(tabId);
|
||||||
|
@ -513,11 +599,13 @@ vAPI.tabs.registerListeners();
|
||||||
this.pageStoreCemetery.set(tabId, (pageStoreCrypt = new Map()));
|
this.pageStoreCemetery.set(tabId, (pageStoreCrypt = new Map()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let pageURL = pageStore.pageUrl;
|
const pageURL = pageStore.pageUrl;
|
||||||
pageStoreCrypt.set(pageURL, pageStore);
|
pageStoreCrypt.set(pageURL, pageStore);
|
||||||
|
|
||||||
pageStore.incinerationTimer = vAPI.setTimeout(
|
pageStore.incinerationTimer = vAPI.setTimeout(
|
||||||
this.incineratePageStore.bind(this, tabId, pageURL),
|
( ) => {
|
||||||
|
this.incineratePageStore(tabId, pageURL);
|
||||||
|
},
|
||||||
4 * 60 * 1000
|
4 * 60 * 1000
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -580,122 +668,86 @@ vAPI.tabs.registerListeners();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
µm.setPageStoreTitle = function(tabId, title) {
|
||||||
|
const pageStore = this.pageStoreFromTabId(tabId);
|
||||||
|
if ( pageStore === null ) { return; }
|
||||||
|
if ( title === pageStore.title ) { return; }
|
||||||
|
pageStore.title = title;
|
||||||
|
this.pageStoresToken = Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
µm.forceReload = function(tabId, bypassCache) {
|
µm.forceReload = function(tabId, bypassCache) {
|
||||||
vAPI.tabs.reload(tabId, bypassCache);
|
vAPI.tabs.reload(tabId, bypassCache);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µm.updateBadgeAsync = (function() {
|
µMatrix.updateToolbarIcon = (( ) => {
|
||||||
let tabIdToTimer = new Map();
|
const µm = µMatrix;
|
||||||
|
const tabIdToDetails = new Map();
|
||||||
|
|
||||||
let updateBadge = function(tabId) {
|
const updateBadge = tabId => {
|
||||||
tabIdToTimer.delete(tabId);
|
let parts = tabIdToDetails.get(tabId);
|
||||||
|
tabIdToDetails.delete(tabId);
|
||||||
|
|
||||||
|
let badge = '';
|
||||||
|
let color = '#666';
|
||||||
let iconId = 'off';
|
let iconId = 'off';
|
||||||
let badgeStr = '';
|
|
||||||
|
|
||||||
let pageStore = this.pageStoreFromTabId(tabId);
|
let pageStore = µm.pageStoreFromTabId(tabId);
|
||||||
if ( pageStore !== null ) {
|
if ( pageStore !== null ) {
|
||||||
let total = pageStore.perLoadAllowedRequestCount +
|
const totalBlocked = pageStore.perLoadBlockedRequestCount;
|
||||||
pageStore.perLoadBlockedRequestCount;
|
const total = pageStore.perLoadAllowedRequestCount + totalBlocked;
|
||||||
if ( total ) {
|
const squareSize = 19;
|
||||||
let squareSize = 19;
|
if ( total !== 0 ) {
|
||||||
let greenSize = squareSize * Math.sqrt(
|
const greenSize = squareSize * Math.sqrt(
|
||||||
pageStore.perLoadAllowedRequestCount / total
|
pageStore.perLoadAllowedRequestCount / total
|
||||||
);
|
);
|
||||||
iconId = greenSize < squareSize/2 ?
|
iconId = greenSize < squareSize / 2 ?
|
||||||
Math.ceil(greenSize) :
|
Math.ceil(greenSize) :
|
||||||
Math.floor(greenSize);
|
Math.floor(greenSize);
|
||||||
|
} else {
|
||||||
|
iconId = squareSize;
|
||||||
}
|
}
|
||||||
if (
|
if ( totalBlocked !== 0 && (parts & 0b0010) !== 0 ) {
|
||||||
this.userSettings.iconBadgeEnabled &&
|
badge = µm.formatCount(totalBlocked);
|
||||||
pageStore.perLoadBlockedRequestCount !== 0
|
|
||||||
) {
|
|
||||||
badgeStr = this.formatCount(pageStore.perLoadBlockedRequestCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vAPI.setIcon(
|
// https://www.reddit.com/r/uBlockOrigin/comments/d33d37/
|
||||||
tabId,
|
if ( µm.userSettings.iconBadgeEnabled === false ) {
|
||||||
'img/browsericons/icon19-' + iconId + '.png',
|
parts |= 0b1000;
|
||||||
{ text: badgeStr, color: '#666' }
|
}
|
||||||
);
|
|
||||||
|
vAPI.setIcon(tabId, {
|
||||||
|
parts,
|
||||||
|
src: `/img/browsericons/icon19-${iconId}.png`,
|
||||||
|
badge,
|
||||||
|
color
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return function(tabId) {
|
// parts: bit 0 = icon
|
||||||
if ( tabIdToTimer.has(tabId) ) { return; }
|
// bit 1 = badge text
|
||||||
|
// bit 2 = badge color
|
||||||
|
// bit 3 = hide badge
|
||||||
|
|
||||||
|
return function(tabId, newParts = 0b0111) {
|
||||||
|
if ( typeof tabId !== 'number' ) { return; }
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
||||||
tabIdToTimer.set(
|
const currentParts = tabIdToDetails.get(tabId);
|
||||||
tabId,
|
if ( currentParts === newParts ) { return; }
|
||||||
vAPI.setTimeout(updateBadge.bind(this, tabId), 750)
|
if ( currentParts === undefined ) {
|
||||||
);
|
self.requestIdleCallback(
|
||||||
};
|
( ) => updateBadge(tabId),
|
||||||
})();
|
{ timeout: 701 }
|
||||||
|
);
|
||||||
/******************************************************************************/
|
} else {
|
||||||
|
newParts |= currentParts;
|
||||||
µm.updateTitle = (function() {
|
|
||||||
let tabIdToTimer = new Map();
|
|
||||||
let tabIdToTryCount = new Map();
|
|
||||||
let delay = 499;
|
|
||||||
|
|
||||||
let tryNoMore = function(tabId) {
|
|
||||||
tabIdToTryCount.delete(tabId);
|
|
||||||
};
|
|
||||||
|
|
||||||
let tryAgain = function(tabId) {
|
|
||||||
let count = tabIdToTryCount.get(tabId);
|
|
||||||
if ( count === undefined ) { return false; }
|
|
||||||
if ( count === 1 ) {
|
|
||||||
tabIdToTryCount.delete(tabId);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
tabIdToTryCount.set(tabId, count - 1);
|
tabIdToDetails.set(tabId, newParts);
|
||||||
tabIdToTimer.set(
|
|
||||||
tabId,
|
|
||||||
vAPI.setTimeout(updateTitle.bind(µm, tabId), delay)
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
var onTabReady = function(tabId, tab) {
|
|
||||||
if ( !tab ) {
|
|
||||||
return tryNoMore(tabId);
|
|
||||||
}
|
|
||||||
var pageStore = this.pageStoreFromTabId(tabId);
|
|
||||||
if ( pageStore === null ) {
|
|
||||||
return tryNoMore(tabId);
|
|
||||||
}
|
|
||||||
if ( !tab.title && tryAgain(tabId) ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// https://github.com/gorhill/uMatrix/issues/225
|
|
||||||
// Sometimes title changes while page is loading.
|
|
||||||
var settled = tab.title && tab.title === pageStore.title;
|
|
||||||
pageStore.title = tab.title || tab.url || '';
|
|
||||||
this.pageStoresToken = Date.now();
|
|
||||||
if ( settled || !tryAgain(tabId) ) {
|
|
||||||
tryNoMore(tabId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var updateTitle = function(tabId) {
|
|
||||||
tabIdToTimer.delete(tabId);
|
|
||||||
vAPI.tabs.get(tabId, onTabReady.bind(this, tabId));
|
|
||||||
};
|
|
||||||
|
|
||||||
return function(tabId) {
|
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; }
|
|
||||||
let timer = tabIdToTimer.get(tabId);
|
|
||||||
if ( timer !== undefined ) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
}
|
|
||||||
tabIdToTimer.set(
|
|
||||||
tabId,
|
|
||||||
vAPI.setTimeout(updateTitle.bind(this, tabId), delay)
|
|
||||||
);
|
|
||||||
tabIdToTryCount.set(tabId, 5);
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -704,28 +756,25 @@ vAPI.tabs.registerListeners();
|
||||||
// Stale page store entries janitor
|
// Stale page store entries janitor
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/455
|
// https://github.com/chrisaljoudi/uBlock/issues/455
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
var cleanupPeriod = 7 * 60 * 1000;
|
const cleanupPeriod = 7 * 60 * 1000;
|
||||||
var cleanupSampleAt = 0;
|
const cleanupSampleSize = 11;
|
||||||
var cleanupSampleSize = 11;
|
let cleanupSampleAt = 0;
|
||||||
|
|
||||||
var cleanup = function() {
|
const cleanup = function() {
|
||||||
var vapiTabs = vAPI.tabs;
|
const tabIds = Array.from(µm.pageStores.keys()).sort();
|
||||||
var tabIds = Array.from(µm.pageStores.keys()).sort();
|
const checkTab = function(tabId) {
|
||||||
var checkTab = function(tabId) {
|
vAPI.tabs.get(tabId).then(tab => {
|
||||||
vapiTabs.get(tabId, function(tab) {
|
if ( tab instanceof Object ) { return; }
|
||||||
if ( !tab ) {
|
µm.unbindTabFromPageStats(tabId);
|
||||||
µm.unbindTabFromPageStats(tabId);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if ( cleanupSampleAt >= tabIds.length ) {
|
if ( cleanupSampleAt >= tabIds.length ) {
|
||||||
cleanupSampleAt = 0;
|
cleanupSampleAt = 0;
|
||||||
}
|
}
|
||||||
var tabId;
|
const n = Math.min(cleanupSampleAt + cleanupSampleSize, tabIds.length);
|
||||||
var n = Math.min(cleanupSampleAt + cleanupSampleSize, tabIds.length);
|
for ( let i = cleanupSampleAt; i < n; i++ ) {
|
||||||
for ( var i = cleanupSampleAt; i < n; i++ ) {
|
const tabId = tabIds[i];
|
||||||
tabId = tabIds[i];
|
|
||||||
if ( vAPI.isBehindTheSceneTabId(tabId) ) { continue; }
|
if ( vAPI.isBehindTheSceneTabId(tabId) ) { continue; }
|
||||||
checkTab(tabId);
|
checkTab(tabId);
|
||||||
}
|
}
|
||||||
|
@ -735,7 +784,7 @@ vAPI.tabs.registerListeners();
|
||||||
};
|
};
|
||||||
|
|
||||||
vAPI.setTimeout(cleanup, cleanupPeriod);
|
vAPI.setTimeout(cleanup, cleanupPeriod);
|
||||||
})();
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -25,45 +25,56 @@
|
||||||
|
|
||||||
// Start isolation from global scope
|
// Start isolation from global scope
|
||||||
|
|
||||||
µMatrix.webRequest = (function() {
|
µMatrix.webRequest = (( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Intercept and filter web requests according to white and black lists.
|
// Intercept and filter web requests according to white and black lists.
|
||||||
|
|
||||||
var onBeforeRootFrameRequestHandler = function(details) {
|
const onBeforeRootFrameRequestHandler = function(fctxt) {
|
||||||
let µm = µMatrix;
|
const µm = µMatrix;
|
||||||
let desURL = details.url;
|
const desURL = fctxt.url;
|
||||||
let desHn = µm.URI.hostnameFromURI(desURL);
|
const desHn = fctxt.getHostname();
|
||||||
let type = requestTypeNormalizer[details.type] || 'other';
|
const type = fctxt.type;
|
||||||
let tabId = details.tabId;
|
const tabId = fctxt.tabId;
|
||||||
|
const srcHn = fctxt.getTabHostname();
|
||||||
µm.tabContextManager.push(tabId, desURL);
|
|
||||||
|
|
||||||
let tabContext = µm.tabContextManager.mustLookup(tabId);
|
|
||||||
let srcHn = tabContext.rootHostname;
|
|
||||||
|
|
||||||
// Disallow request as per matrix?
|
// Disallow request as per matrix?
|
||||||
let blocked = µm.mustBlock(srcHn, desHn, type);
|
const blocked = µm.mustBlock(srcHn, desHn, type);
|
||||||
|
|
||||||
let pageStore = µm.pageStoreFromTabId(tabId);
|
const pageStore = µm.bindTabToPageStats(tabId);
|
||||||
pageStore.recordRequest(type, desURL, blocked);
|
if ( pageStore !== null ) {
|
||||||
pageStore.perLoadAllowedRequestCount = 0;
|
pageStore.recordRequest(type, desURL, blocked);
|
||||||
pageStore.perLoadBlockedRequestCount = 0;
|
pageStore.perLoadAllowedRequestCount = 0;
|
||||||
µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type, blocked });
|
pageStore.perLoadBlockedRequestCount = 0;
|
||||||
|
pageStore.perLoadBlockedReferrerCount = 0;
|
||||||
|
if ( blocked !== true ) {
|
||||||
|
µm.cookieHunter.recordPageCookies(pageStore);
|
||||||
|
}
|
||||||
|
if ( fctxt.aliasURL !== undefined ) {
|
||||||
|
pageStore.hasHostnameAliases = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( µm.logger.enabled ) {
|
||||||
|
fctxt.setRealm('network').setFilter(blocked).toLogger();
|
||||||
|
}
|
||||||
|
|
||||||
// Not blocked
|
// Not blocked
|
||||||
if ( !blocked ) {
|
if ( blocked !== true ) {
|
||||||
let redirectURL = maybeRedirectRootFrame(desHn, desURL);
|
const redirectUrl = maybeRedirectRootFrame(desHn, desURL);
|
||||||
if ( redirectURL !== desURL ) {
|
if ( redirectUrl !== desURL ) {
|
||||||
return { redirectUrl: redirectURL };
|
return { redirectUrl };
|
||||||
|
}
|
||||||
|
if ( µm.tMatrix.evaluateSwitchZ('cname-reveal', srcHn) === false ) {
|
||||||
|
return { cancel: false };
|
||||||
}
|
}
|
||||||
µm.cookieHunter.recordPageCookies(pageStore);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocked
|
// Blocked
|
||||||
let query = btoa(JSON.stringify({ url: desURL, hn: desHn, type, why: '?' }));
|
const query = encodeURIComponent(
|
||||||
|
JSON.stringify({ url: desURL, hn: desHn, type, why: '?' })
|
||||||
|
);
|
||||||
|
|
||||||
vAPI.tabs.replace(tabId, vAPI.getURL('main-blocked.html?details=') + query);
|
vAPI.tabs.replace(tabId, vAPI.getURL('main-blocked.html?details=') + query);
|
||||||
|
|
||||||
|
@ -74,18 +85,18 @@ var onBeforeRootFrameRequestHandler = function(details) {
|
||||||
|
|
||||||
// https://twitter.com/thatcks/status/958776519765225473
|
// https://twitter.com/thatcks/status/958776519765225473
|
||||||
|
|
||||||
var maybeRedirectRootFrame = function(hostname, url) {
|
const maybeRedirectRootFrame = function(hostname, url) {
|
||||||
let µm = µMatrix;
|
const µm = µMatrix;
|
||||||
if ( µm.rawSettings.enforceEscapedFragment !== true ) { return url; }
|
if ( µm.rawSettings.enforceEscapedFragment !== true ) { return url; }
|
||||||
let block1pScripts = µm.mustBlock(hostname, hostname, 'script');
|
const block1pScripts = µm.mustBlock(hostname, hostname, 'script');
|
||||||
let reEscapedFragment = /[?&]_escaped_fragment_=/;
|
const reEscapedFragment = /[?&]_escaped_fragment_=/;
|
||||||
if ( reEscapedFragment.test(url) ) {
|
if ( reEscapedFragment.test(url) ) {
|
||||||
return block1pScripts ? url : url.replace(reEscapedFragment, '#!') ;
|
return block1pScripts ? url : url.replace(reEscapedFragment, '#!') ;
|
||||||
}
|
}
|
||||||
if ( block1pScripts === false ) { return url; }
|
if ( block1pScripts === false ) { return url; }
|
||||||
let pos = url.indexOf('#!');
|
const pos = url.indexOf('#!');
|
||||||
if ( pos === -1 ) { return url; }
|
if ( pos === -1 ) { return url; }
|
||||||
let separator = url.lastIndexOf('?', pos) === -1 ? '?' : '&';
|
const separator = url.lastIndexOf('?', pos) === -1 ? '?' : '&';
|
||||||
return url.slice(0, pos) +
|
return url.slice(0, pos) +
|
||||||
separator + '_escaped_fragment_=' +
|
separator + '_escaped_fragment_=' +
|
||||||
url.slice(pos + 2);
|
url.slice(pos + 2);
|
||||||
|
@ -95,21 +106,24 @@ var maybeRedirectRootFrame = function(hostname, url) {
|
||||||
|
|
||||||
// Intercept and filter web requests according to white and black lists.
|
// Intercept and filter web requests according to white and black lists.
|
||||||
|
|
||||||
var onBeforeRequestHandler = function(details) {
|
const onBeforeRequestHandler = function(details) {
|
||||||
let µm = µMatrix,
|
const µm = µMatrix;
|
||||||
µmuri = µm.URI,
|
const fctxt = µm.filteringContext.fromWebrequestDetails(details);
|
||||||
desURL = details.url,
|
const µmuri = µm.URI;
|
||||||
desScheme = µmuri.schemeFromURI(desURL);
|
const desURL = fctxt.url;
|
||||||
|
const desScheme = µmuri.schemeFromURI(desURL);
|
||||||
|
|
||||||
if ( µmuri.isNetworkScheme(desScheme) === false ) { return; }
|
if ( µmuri.isNetworkScheme(desScheme) === false ) {
|
||||||
|
return { cancel: false };
|
||||||
|
}
|
||||||
|
|
||||||
let type = requestTypeNormalizer[details.type] || 'other';
|
const type = fctxt.type;
|
||||||
|
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/303
|
// https://github.com/gorhill/httpswitchboard/issues/303
|
||||||
// Wherever the main doc comes from, create a receiver page URL: synthetize
|
// Wherever the main doc comes from, create a receiver page URL: synthetize
|
||||||
// one if needed.
|
// one if needed.
|
||||||
if ( type === 'doc' && details.parentFrameId === -1 ) {
|
if ( type === 'doc' && details.parentFrameId === -1 ) {
|
||||||
return onBeforeRootFrameRequestHandler(details);
|
return onBeforeRootFrameRequestHandler(fctxt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-classify orphan HTTP requests as behind-the-scene requests. There is
|
// Re-classify orphan HTTP requests as behind-the-scene requests. There is
|
||||||
|
@ -119,30 +133,11 @@ var onBeforeRequestHandler = function(details) {
|
||||||
// to scope on unknown scheme? Etc.
|
// to scope on unknown scheme? Etc.
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/191
|
// https://github.com/gorhill/httpswitchboard/issues/191
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
|
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
|
||||||
let tabContext = µm.tabContextManager.mustLookup(details.tabId),
|
const tabContext = µm.tabContextManager.mustLookup(details.tabId);
|
||||||
tabId = tabContext.tabId,
|
const tabId = fctxt.tabId;
|
||||||
srcHn = tabContext.rootHostname,
|
const srcHn = fctxt.getTabHostname();
|
||||||
desHn = µmuri.hostnameFromURI(desURL),
|
const desHn = fctxt.getHostname();
|
||||||
docURL = details.documentUrl,
|
let specificity = 0;
|
||||||
specificity = 0;
|
|
||||||
|
|
||||||
if ( docURL !== undefined ) {
|
|
||||||
// Extract context from initiator for behind-the-scene requests.
|
|
||||||
if ( tabId < 0 ) {
|
|
||||||
srcHn = µmuri.hostnameFromURI(µm.normalizePageURL(0, docURL));
|
|
||||||
}
|
|
||||||
// https://github.com/uBlockOrigin/uMatrix-issues/issues/72
|
|
||||||
// Workaround of weird Firefox behavior: when a service worker exists
|
|
||||||
// for a site, the `doc` requests when loading a page from that site
|
|
||||||
// are not being made: this potentially prevents uMatrix to properly
|
|
||||||
// keep track of the context in which requests are made.
|
|
||||||
else if (
|
|
||||||
details.parentFrameId === -1 &&
|
|
||||||
docURL !== tabContext.rawURL
|
|
||||||
) {
|
|
||||||
srcHn = µmuri.hostnameFromURI(µm.normalizePageURL(0, docURL));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let blocked = µm.tMatrix.mustBlock(srcHn, desHn, type);
|
let blocked = µm.tMatrix.mustBlock(srcHn, desHn, type);
|
||||||
if ( blocked ) {
|
if ( blocked ) {
|
||||||
|
@ -155,7 +150,7 @@ var onBeforeRequestHandler = function(details) {
|
||||||
// processing has already been performed, and that a synthetic URL has
|
// processing has already been performed, and that a synthetic URL has
|
||||||
// been constructed for logging purpose. Use this synthetic URL if
|
// been constructed for logging purpose. Use this synthetic URL if
|
||||||
// it is available.
|
// it is available.
|
||||||
let pageStore = µm.mustPageStoreFromTabId(tabId);
|
const pageStore = µm.mustPageStoreFromTabId(tabId);
|
||||||
|
|
||||||
// Enforce strict secure connection?
|
// Enforce strict secure connection?
|
||||||
if ( tabContext.secure && µmuri.isSecureScheme(desScheme) === false ) {
|
if ( tabContext.secure && µmuri.isSecureScheme(desScheme) === false ) {
|
||||||
|
@ -165,14 +160,22 @@ var onBeforeRequestHandler = function(details) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( fctxt.aliasURL !== undefined ) {
|
||||||
|
pageStore.hasHostnameAliases = true;
|
||||||
|
}
|
||||||
|
|
||||||
pageStore.recordRequest(type, desURL, blocked);
|
pageStore.recordRequest(type, desURL, blocked);
|
||||||
if ( µm.logger.enabled ) {
|
if ( µm.logger.enabled ) {
|
||||||
µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type, blocked });
|
fctxt.setRealm('network').setFilter(blocked).toLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( blocked ) {
|
if ( blocked ) {
|
||||||
pageStore.cacheBlockedCollapsible(type, desURL, specificity);
|
pageStore.cacheBlockedCollapsible(type, desURL, specificity);
|
||||||
return { 'cancel': true };
|
return { cancel: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( µm.tMatrix.evaluateSwitchZ('cname-reveal', srcHn) === false ) {
|
||||||
|
return { cancel: false };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -180,36 +183,15 @@ var onBeforeRequestHandler = function(details) {
|
||||||
|
|
||||||
// Sanitize outgoing headers as per user settings.
|
// Sanitize outgoing headers as per user settings.
|
||||||
|
|
||||||
var onBeforeSendHeadersHandler = function(details) {
|
const onBeforeSendHeadersHandler = function(details) {
|
||||||
let µm = µMatrix,
|
const µm = µMatrix;
|
||||||
µmuri = µm.URI,
|
const µmuri = µm.URI;
|
||||||
desURL = details.url,
|
const fctxt = µm.filteringContext.fromWebrequestDetails(details);
|
||||||
desScheme = µmuri.schemeFromURI(desURL);
|
|
||||||
|
|
||||||
// Ignore non-network schemes
|
// Ignore non-network schemes
|
||||||
if ( µmuri.isNetworkScheme(desScheme) === false ) { return; }
|
if ( µmuri.isNetworkScheme(µmuri.schemeFromURI(fctxt.url)) === false ) {
|
||||||
|
return;
|
||||||
// Re-classify orphan HTTP requests as behind-the-scene requests. There is
|
}
|
||||||
// not much else which can be done, because there are URLs
|
|
||||||
// which cannot be handled by HTTP Switchboard, i.e. `opera://startpage`,
|
|
||||||
// as this would lead to complications with no obvious solution, like how
|
|
||||||
// to scope on unknown scheme? Etc.
|
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/191
|
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275
|
|
||||||
const tabId = details.tabId;
|
|
||||||
const pageStore = µm.mustPageStoreFromTabId(tabId);
|
|
||||||
const desHn = µmuri.hostnameFromURI(desURL);
|
|
||||||
const requestType = requestTypeNormalizer[details.type] || 'other';
|
|
||||||
const requestHeaders = details.requestHeaders;
|
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uMatrix-issues/issues/155
|
|
||||||
// https://github.com/uBlockOrigin/uMatrix-issues/issues/159
|
|
||||||
// TODO: import all filtering context improvements from uBO.
|
|
||||||
const srcHn = tabId < 0 ||
|
|
||||||
details.parentFrameId < 0 ||
|
|
||||||
details.parentFrameId === 0 && details.type === 'sub_frame'
|
|
||||||
? µmuri.hostnameFromURI(details.documentUrl) || pageStore.pageHostname
|
|
||||||
: pageStore.pageHostname;
|
|
||||||
|
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/342
|
// https://github.com/gorhill/httpswitchboard/issues/342
|
||||||
// Is this hyperlink auditing?
|
// Is this hyperlink auditing?
|
||||||
|
@ -232,45 +214,19 @@ var onBeforeSendHeadersHandler = function(details) {
|
||||||
// With hyperlink-auditing, removing header(s) is pointless, the whole
|
// With hyperlink-auditing, removing header(s) is pointless, the whole
|
||||||
// request must be cancelled.
|
// request must be cancelled.
|
||||||
|
|
||||||
let headerIndex = headerIndexFromName('ping-to', requestHeaders);
|
if ( onBeforeSendPing(fctxt, details) ) {
|
||||||
if ( headerIndex !== -1 ) {
|
return { cancel: true };
|
||||||
let headerValue = requestHeaders[headerIndex].value;
|
|
||||||
if ( headerValue !== '' ) {
|
|
||||||
let blocked = µm.userSettings.processHyperlinkAuditing;
|
|
||||||
pageStore.recordRequest('other', desURL + '{Ping-To:' + headerValue + '}', blocked);
|
|
||||||
µm.logger.writeOne({ tabId, srcHn, desHn, desURL, type: 'ping', blocked });
|
|
||||||
if ( blocked ) {
|
|
||||||
µm.hyperlinkAuditingFoiledCounter += 1;
|
|
||||||
return { 'cancel': true };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we reach this point, request is not blocked, so what is left to do
|
// If we reach this point, request is not blocked, so what is left to do
|
||||||
// is to sanitize headers.
|
// is to sanitize headers.
|
||||||
|
|
||||||
let modified = false;
|
let modified = false;
|
||||||
|
|
||||||
// Process `Cookie` header.
|
// Process `Cookie` header.
|
||||||
|
|
||||||
headerIndex = headerIndexFromName('cookie', requestHeaders);
|
if ( onBeforeSendCookie(fctxt, details) ) {
|
||||||
if (
|
|
||||||
headerIndex !== -1 &&
|
|
||||||
µm.mustBlock(srcHn, desHn, 'cookie')
|
|
||||||
) {
|
|
||||||
modified = true;
|
modified = true;
|
||||||
let headerValue = requestHeaders[headerIndex].value;
|
|
||||||
requestHeaders.splice(headerIndex, 1);
|
|
||||||
µm.cookieHeaderFoiledCounter++;
|
|
||||||
if ( requestType === 'doc' ) {
|
|
||||||
pageStore.perLoadBlockedRequestCount++;
|
|
||||||
µm.logger.writeOne({
|
|
||||||
tabId,
|
|
||||||
srcHn,
|
|
||||||
header: { name: 'COOKIE', value: headerValue },
|
|
||||||
change: -1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process `Referer` header.
|
// Process `Referer` header.
|
||||||
|
@ -293,50 +249,132 @@ var onBeforeSendHeadersHandler = function(details) {
|
||||||
// https://github.com/gorhill/uMatrix/issues/773
|
// https://github.com/gorhill/uMatrix/issues/773
|
||||||
// For non-GET requests, remove `Referer` header instead of spoofing it.
|
// For non-GET requests, remove `Referer` header instead of spoofing it.
|
||||||
|
|
||||||
headerIndex = headerIndexFromName('referer', requestHeaders);
|
if ( onBeforeSendReferrer(fctxt, details) ) {
|
||||||
if ( headerIndex !== -1 ) {
|
modified = true;
|
||||||
let headerValue = requestHeaders[headerIndex].value;
|
|
||||||
if ( headerValue !== '' ) {
|
|
||||||
let toDomain = µmuri.domainFromHostname(desHn);
|
|
||||||
if ( toDomain !== '' && toDomain !== µmuri.domainFromURI(headerValue) ) {
|
|
||||||
pageStore.has3pReferrer = true;
|
|
||||||
if ( µm.tMatrix.evaluateSwitchZ('referrer-spoof', srcHn) ) {
|
|
||||||
modified = true;
|
|
||||||
let newValue;
|
|
||||||
if ( details.method === 'GET' ) {
|
|
||||||
newValue = requestHeaders[headerIndex].value =
|
|
||||||
desScheme + '://' + desHn + '/';
|
|
||||||
} else {
|
|
||||||
requestHeaders.splice(headerIndex, 1);
|
|
||||||
}
|
|
||||||
if ( pageStore.perLoadBlockedReferrerCount === 0 ) {
|
|
||||||
pageStore.perLoadBlockedRequestCount += 1;
|
|
||||||
µm.logger.writeOne({
|
|
||||||
tabId,
|
|
||||||
srcHn,
|
|
||||||
header: { name: 'REFERER', value: headerValue },
|
|
||||||
change: -1
|
|
||||||
});
|
|
||||||
if ( newValue !== undefined ) {
|
|
||||||
µm.logger.writeOne({
|
|
||||||
tabId,
|
|
||||||
srcHn,
|
|
||||||
header: { name: 'REFERER', value: newValue },
|
|
||||||
change: +1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pageStore.perLoadBlockedReferrerCount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( modified !== true ) { return; }
|
if ( modified !== true ) { return; }
|
||||||
|
|
||||||
µm.updateBadgeAsync(tabId);
|
µm.updateToolbarIcon(fctxt.tabId);
|
||||||
|
|
||||||
return { requestHeaders: requestHeaders };
|
return { requestHeaders: details.requestHeaders };
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const onBeforeSendPing = function(fctxt, details) {
|
||||||
|
const requestHeaders = details.requestHeaders;
|
||||||
|
const iHeader = headerIndexFromName('ping-to', requestHeaders);
|
||||||
|
if ( iHeader === -1 ) { return false; }
|
||||||
|
|
||||||
|
const headerValue = requestHeaders[iHeader].value;
|
||||||
|
if ( headerValue === '' ) { return false; }
|
||||||
|
|
||||||
|
const µm = µMatrix;
|
||||||
|
const blocked = µm.userSettings.processHyperlinkAuditing;
|
||||||
|
|
||||||
|
const pageStore = µm.mustPageStoreFromTabId(fctxt.tabId);
|
||||||
|
pageStore.recordRequest(
|
||||||
|
'other',
|
||||||
|
fctxt.url + '{Ping-To:' + headerValue + '}',
|
||||||
|
blocked
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( µm.logger.enabled ) {
|
||||||
|
fctxt.setRealm('network')
|
||||||
|
.setType('ping')
|
||||||
|
.setFilter(blocked)
|
||||||
|
.toLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( blocked === false ) { return false; }
|
||||||
|
|
||||||
|
µm.hyperlinkAuditingFoiledCounter += 1;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const onBeforeSendCookie = function(fctxt, details) {
|
||||||
|
const requestHeaders = details.requestHeaders;
|
||||||
|
const iHeader = headerIndexFromName('cookie', requestHeaders);
|
||||||
|
if ( iHeader === -1 ) { return false; }
|
||||||
|
|
||||||
|
const µm = µMatrix;
|
||||||
|
const blocked = µm.mustBlock(
|
||||||
|
fctxt.getTabHostname(),
|
||||||
|
fctxt.getHostname(),
|
||||||
|
'cookie'
|
||||||
|
);
|
||||||
|
if ( blocked === false ) { return false; }
|
||||||
|
|
||||||
|
const headerValue = requestHeaders[iHeader].value;
|
||||||
|
requestHeaders.splice(iHeader, 1);
|
||||||
|
µm.cookieHeaderFoiledCounter++;
|
||||||
|
|
||||||
|
if ( fctxt.type === 'doc' ) {
|
||||||
|
const pageStore = µm.mustPageStoreFromTabId(fctxt.tabId);
|
||||||
|
pageStore.perLoadBlockedRequestCount++;
|
||||||
|
if ( µm.logger.enabled ) {
|
||||||
|
fctxt.setRealm('network')
|
||||||
|
.setType('COOKIE')
|
||||||
|
.setFilter({ value: headerValue, change: -1 })
|
||||||
|
.toLogger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const onBeforeSendReferrer = function(fctxt, details) {
|
||||||
|
const requestHeaders = details.requestHeaders;
|
||||||
|
const iHeader = headerIndexFromName('referer', requestHeaders);
|
||||||
|
if ( iHeader === -1 ) { return false; }
|
||||||
|
|
||||||
|
const referrer = requestHeaders[iHeader].value;
|
||||||
|
if ( referrer === '' ) { return false; }
|
||||||
|
|
||||||
|
const toDomain = vAPI.domainFromHostname(fctxt.getHostname());
|
||||||
|
if ( toDomain === '' || toDomain === vAPI.domainFromURI(referrer) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const µm = µMatrix;
|
||||||
|
const pageStore = µm.mustPageStoreFromTabId(fctxt.tabId);
|
||||||
|
pageStore.has3pReferrer = true;
|
||||||
|
|
||||||
|
const mustSpoof =
|
||||||
|
µm.tMatrix.evaluateSwitchZ('referrer-spoof', fctxt.getTabHostname());
|
||||||
|
if ( mustSpoof === false ) { return false; }
|
||||||
|
|
||||||
|
let spoofedReferrer;
|
||||||
|
if ( details.method === 'GET' ) {
|
||||||
|
spoofedReferrer = requestHeaders[iHeader].value =
|
||||||
|
fctxt.originFromURI(fctxt.url) + '/';
|
||||||
|
} else {
|
||||||
|
requestHeaders.splice(iHeader, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( pageStore.perLoadBlockedReferrerCount === 0 ) {
|
||||||
|
pageStore.perLoadBlockedRequestCount += 1;
|
||||||
|
if ( µm.logger.enabled ) {
|
||||||
|
fctxt.setRealm('network')
|
||||||
|
.setType('REFERER')
|
||||||
|
.setFilter({ value: referrer, change: -1 })
|
||||||
|
.toLogger();
|
||||||
|
if ( spoofedReferrer !== undefined ) {
|
||||||
|
fctxt.setRealm('network')
|
||||||
|
.setType('REFERER')
|
||||||
|
.setFilter({ value: spoofedReferrer, change: +1 })
|
||||||
|
.toLogger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageStore.perLoadBlockedReferrerCount += 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -349,32 +387,26 @@ var onBeforeSendHeadersHandler = function(details) {
|
||||||
// This fixes:
|
// This fixes:
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/35
|
// https://github.com/gorhill/httpswitchboard/issues/35
|
||||||
|
|
||||||
var onHeadersReceivedHandler = function(details) {
|
const onHeadersReceivedHandler = function(details) {
|
||||||
// Ignore schemes other than 'http...'
|
const µm = µMatrix;
|
||||||
let µm = µMatrix,
|
const fctxt = µm.filteringContext.fromWebrequestDetails(details);
|
||||||
tabId = details.tabId,
|
const requestType = fctxt.type;
|
||||||
requestURL = details.url,
|
const headers = details.responseHeaders;
|
||||||
requestType = requestTypeNormalizer[details.type] || 'other',
|
|
||||||
headers = details.responseHeaders;
|
|
||||||
|
|
||||||
// https://github.com/gorhill/uMatrix/issues/145
|
// https://github.com/gorhill/uMatrix/issues/145
|
||||||
// Check if the main_frame is a download
|
// Check if the main_frame is a download
|
||||||
if ( requestType === 'doc' ) {
|
if ( requestType === 'doc' ) {
|
||||||
µm.tabContextManager.push(tabId, requestURL);
|
const contentType = typeFromHeaders(headers);
|
||||||
let contentType = typeFromHeaders(headers);
|
|
||||||
if ( contentType !== undefined ) {
|
if ( contentType !== undefined ) {
|
||||||
details.type = contentType;
|
details.type = contentType;
|
||||||
return onBeforeRootFrameRequestHandler(details);
|
return onBeforeRootFrameRequestHandler(fctxt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let tabContext = µm.tabContextManager.lookup(tabId);
|
const csp = [];
|
||||||
if ( tabContext === null ) { return; }
|
const cspReport = [];
|
||||||
|
const srcHn = fctxt.getTabHostname();
|
||||||
let csp = [],
|
const desHn = fctxt.getHostname();
|
||||||
cspReport = [],
|
|
||||||
srcHn = tabContext.rootHostname,
|
|
||||||
desHn = µm.URI.hostnameFromURI(requestURL);
|
|
||||||
|
|
||||||
// Inline script tags.
|
// Inline script tags.
|
||||||
if ( µm.mustBlock(srcHn, desHn, 'script' ) ) {
|
if ( µm.mustBlock(srcHn, desHn, 'script' ) ) {
|
||||||
|
@ -401,7 +433,7 @@ var onHeadersReceivedHandler = function(details) {
|
||||||
// them here.
|
// them here.
|
||||||
|
|
||||||
if ( csp.length !== 0 ) {
|
if ( csp.length !== 0 ) {
|
||||||
let cspRight = csp.join(', ');
|
const cspRight = csp.join(', ');
|
||||||
let cspTotal = cspRight;
|
let cspTotal = cspRight;
|
||||||
if ( µm.cantMergeCSPHeaders ) {
|
if ( µm.cantMergeCSPHeaders ) {
|
||||||
let i = headerIndexFromName(
|
let i = headerIndexFromName(
|
||||||
|
@ -417,18 +449,16 @@ var onHeadersReceivedHandler = function(details) {
|
||||||
name: 'Content-Security-Policy',
|
name: 'Content-Security-Policy',
|
||||||
value: cspTotal
|
value: cspTotal
|
||||||
});
|
});
|
||||||
if ( requestType === 'doc' ) {
|
if ( µm.logger.enabled && requestType === 'doc' ) {
|
||||||
µm.logger.writeOne({
|
fctxt.setRealm('network')
|
||||||
tabId,
|
.setType('CSP')
|
||||||
srcHn,
|
.setFilter({ value: cspRight, change: +1 })
|
||||||
header: { name: 'CSP', value: cspRight },
|
.toLogger();
|
||||||
change: +1
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( cspReport.length !== 0 ) {
|
if ( cspReport.length !== 0 ) {
|
||||||
let cspRight = cspReport.join(', ');
|
const cspRight = cspReport.join(', ');
|
||||||
let cspTotal = cspRight;
|
let cspTotal = cspRight;
|
||||||
if ( µm.cantMergeCSPHeaders ) {
|
if ( µm.cantMergeCSPHeaders ) {
|
||||||
let i = headerIndexFromName(
|
let i = headerIndexFromName(
|
||||||
|
@ -469,8 +499,8 @@ window.addEventListener('webextFlavor', function() {
|
||||||
|
|
||||||
// Caller must ensure headerName is normalized to lower case.
|
// Caller must ensure headerName is normalized to lower case.
|
||||||
|
|
||||||
var headerIndexFromName = function(headerName, headers) {
|
const headerIndexFromName = function(headerName, headers) {
|
||||||
var i = headers.length;
|
let i = headers.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
if ( headers[i].name.toLowerCase() === headerName ) {
|
if ( headers[i].name.toLowerCase() === headerName ) {
|
||||||
return i;
|
return i;
|
||||||
|
@ -483,33 +513,16 @@ var headerIndexFromName = function(headerName, headers) {
|
||||||
|
|
||||||
// Extract request type from content headers.
|
// Extract request type from content headers.
|
||||||
|
|
||||||
let typeFromHeaders = function(headers) {
|
const typeFromHeaders = function(headers) {
|
||||||
let i = headerIndexFromName('content-type', headers);
|
const i = headerIndexFromName('content-type', headers);
|
||||||
if ( i === -1 ) { return; }
|
if ( i === -1 ) { return; }
|
||||||
let mime = headers[i].value.toLowerCase();
|
const mime = headers[i].value.toLowerCase();
|
||||||
if ( mime.startsWith('image/') ) { return 'image'; }
|
if ( mime.startsWith('image/') ) { return 'image'; }
|
||||||
if ( mime.startsWith('video/') || mime.startsWith('audio/') ) {
|
if ( mime.startsWith('video/') || mime.startsWith('audio/') ) {
|
||||||
return 'media';
|
return 'media';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var requestTypeNormalizer = {
|
|
||||||
'font' : 'css',
|
|
||||||
'image' : 'image',
|
|
||||||
'imageset' : 'image',
|
|
||||||
'main_frame' : 'doc',
|
|
||||||
'media' : 'media',
|
|
||||||
'object' : 'media',
|
|
||||||
'other' : 'other',
|
|
||||||
'script' : 'script',
|
|
||||||
'stylesheet' : 'css',
|
|
||||||
'sub_frame' : 'frame',
|
|
||||||
'websocket' : 'xhr',
|
|
||||||
'xmlhttprequest': 'xhr'
|
|
||||||
};
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
|
||||||
Use a `http-equiv` `meta` tag to enforce CSP directives for documents
|
Use a `http-equiv` `meta` tag to enforce CSP directives for documents
|
||||||
|
@ -517,11 +530,11 @@ var requestTypeNormalizer = {
|
||||||
handler to be called).
|
handler to be called).
|
||||||
|
|
||||||
Idea borrowed from NoScript:
|
Idea borrowed from NoScript:
|
||||||
https://github.com/hackademix/noscript/commit/6e80d3f130773fc9a9123c5c4c2e97d63e90fa2a
|
https://github.com/hackademix/noscript/commit/6e80d3f13077
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
if (
|
if (
|
||||||
typeof self.browser !== 'object' ||
|
typeof self.browser !== 'object' ||
|
||||||
typeof browser.contentScripts !== 'object'
|
typeof browser.contentScripts !== 'object'
|
||||||
|
@ -529,7 +542,7 @@ var requestTypeNormalizer = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let csRules = [
|
const csRules = [
|
||||||
{
|
{
|
||||||
name: 'script',
|
name: 'script',
|
||||||
file: '/js/contentscript-no-inline-script.js',
|
file: '/js/contentscript-no-inline-script.js',
|
||||||
|
@ -539,7 +552,7 @@ var requestTypeNormalizer = {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let csSwitches = [
|
const csSwitches = [
|
||||||
{
|
{
|
||||||
name: 'no-workers',
|
name: 'no-workers',
|
||||||
file: '/js/contentscript-no-workers.js',
|
file: '/js/contentscript-no-workers.js',
|
||||||
|
@ -549,7 +562,7 @@ var requestTypeNormalizer = {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let register = function(entry) {
|
const register = function(entry) {
|
||||||
if ( entry.pending !== undefined ) { return; }
|
if ( entry.pending !== undefined ) { return; }
|
||||||
entry.pending = browser.contentScripts.register({
|
entry.pending = browser.contentScripts.register({
|
||||||
js: [ { file: entry.file } ],
|
js: [ { file: entry.file } ],
|
||||||
|
@ -569,16 +582,16 @@ var requestTypeNormalizer = {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let unregister = function(entry) {
|
const unregister = function(entry) {
|
||||||
if ( entry.registered === undefined ) { return; }
|
if ( entry.registered === undefined ) { return; }
|
||||||
entry.registered.unregister();
|
entry.registered.unregister();
|
||||||
entry.registered = undefined;
|
entry.registered = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
let handler = function(ev) {
|
const handler = function(ev) {
|
||||||
let matrix = ev && ev.detail;
|
const matrix = ev && ev.detail;
|
||||||
if ( matrix !== µMatrix.tMatrix ) { return; }
|
if ( matrix !== µMatrix.tMatrix ) { return; }
|
||||||
for ( let cs of csRules ) {
|
for ( const cs of csRules ) {
|
||||||
cs.mustRegister = matrix.mustBlock('file-scheme', 'file-scheme', cs.name);
|
cs.mustRegister = matrix.mustBlock('file-scheme', 'file-scheme', cs.name);
|
||||||
if ( cs.mustRegister === (cs.registered !== undefined) ) { continue; }
|
if ( cs.mustRegister === (cs.registered !== undefined) ) { continue; }
|
||||||
if ( cs.mustRegister ) {
|
if ( cs.mustRegister ) {
|
||||||
|
@ -587,7 +600,7 @@ var requestTypeNormalizer = {
|
||||||
unregister(cs);
|
unregister(cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for ( let cs of csSwitches ) {
|
for ( const cs of csSwitches ) {
|
||||||
cs.mustRegister = matrix.evaluateSwitchZ(cs.name, 'file-scheme');
|
cs.mustRegister = matrix.evaluateSwitchZ(cs.name, 'file-scheme');
|
||||||
if ( cs.mustRegister === (cs.registered !== undefined) ) { continue; }
|
if ( cs.mustRegister === (cs.registered !== undefined) ) { continue; }
|
||||||
if ( cs.mustRegister ) {
|
if ( cs.mustRegister ) {
|
||||||
|
@ -603,58 +616,52 @@ var requestTypeNormalizer = {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const start = (function() {
|
return {
|
||||||
if (
|
start: (( ) => {
|
||||||
vAPI.net.onBeforeReady instanceof Object &&
|
vAPI.net = new vAPI.Net();
|
||||||
(
|
|
||||||
vAPI.net.onBeforeReady.experimental !== true ||
|
|
||||||
µMatrix.rawSettings.suspendTabsUntilReady
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
vAPI.net.onBeforeReady.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
vAPI.net.addListener(
|
|
||||||
'onBeforeRequest',
|
|
||||||
onBeforeRequestHandler,
|
|
||||||
{ },
|
|
||||||
[ 'blocking' ]
|
|
||||||
);
|
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uMatrix-issues/issues/74#issuecomment-450687707
|
|
||||||
// https://groups.google.com/a/chromium.org/forum/#!topic/chromium-extensions/vYIaeezZwfQ
|
|
||||||
// Chromium 72+: use `extraHeaders` to keep the ability to access
|
|
||||||
// the `Cookie`, `Referer` headers.
|
|
||||||
const beforeSendHeadersExtra = [ 'blocking', 'requestHeaders' ];
|
|
||||||
const wrObsho = browser.webRequest.OnBeforeSendHeadersOptions;
|
|
||||||
if (
|
if (
|
||||||
wrObsho instanceof Object &&
|
vAPI.net.canSuspend() &&
|
||||||
wrObsho.hasOwnProperty('EXTRA_HEADERS')
|
µMatrix.rawSettings.suspendTabsUntilReady !== 'no' ||
|
||||||
|
vAPI.net.canSuspend() !== true &&
|
||||||
|
µMatrix.rawSettings.suspendTabsUntilReady === 'yes'
|
||||||
) {
|
) {
|
||||||
beforeSendHeadersExtra.push(wrObsho.EXTRA_HEADERS);
|
vAPI.net.suspend(true);
|
||||||
}
|
}
|
||||||
vAPI.net.addListener(
|
|
||||||
'onBeforeSendHeaders',
|
|
||||||
onBeforeSendHeadersHandler,
|
|
||||||
{ },
|
|
||||||
beforeSendHeadersExtra
|
|
||||||
);
|
|
||||||
|
|
||||||
vAPI.net.addListener(
|
return function() {
|
||||||
'onHeadersReceived',
|
vAPI.net.setSuspendableListener(onBeforeRequestHandler);
|
||||||
onHeadersReceivedHandler,
|
// https://github.com/uBlockOrigin/uMatrix-issues/issues/74#issuecomment-450687707
|
||||||
{ types: [ 'main_frame', 'sub_frame' ] },
|
// https://groups.google.com/a/chromium.org/forum/#!topic/chromium-extensions/vYIaeezZwfQ
|
||||||
[ 'blocking', 'responseHeaders' ]
|
// Chromium 72+: use `extraHeaders` to keep the ability to access
|
||||||
);
|
// the `Cookie`, `Referer` headers.
|
||||||
|
const beforeSendHeadersExtra = [ 'blocking', 'requestHeaders' ];
|
||||||
if ( vAPI.net.onBeforeReady instanceof Object ) {
|
const wrObsho = browser.webRequest.OnBeforeSendHeadersOptions;
|
||||||
vAPI.net.onBeforeReady.stop(onBeforeRequestHandler);
|
if (
|
||||||
}
|
wrObsho instanceof Object &&
|
||||||
};
|
wrObsho.hasOwnProperty('EXTRA_HEADERS')
|
||||||
})();
|
) {
|
||||||
|
beforeSendHeadersExtra.push(wrObsho.EXTRA_HEADERS);
|
||||||
return { start };
|
}
|
||||||
|
vAPI.net.addListener(
|
||||||
|
'onBeforeSendHeaders',
|
||||||
|
onBeforeSendHeadersHandler,
|
||||||
|
{ },
|
||||||
|
beforeSendHeadersExtra
|
||||||
|
);
|
||||||
|
vAPI.net.addListener(
|
||||||
|
'onHeadersReceived',
|
||||||
|
onHeadersReceivedHandler,
|
||||||
|
{
|
||||||
|
types: [ 'main_frame', 'sub_frame' ],
|
||||||
|
urls: [ 'http://*/*', 'https://*/*' ],
|
||||||
|
},
|
||||||
|
[ 'blocking', 'responseHeaders' ]
|
||||||
|
);
|
||||||
|
vAPI.net.unsuspend(true);
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,6 @@
|
||||||
Home: https://github.com/gorhill/uMatrix
|
Home: https://github.com/gorhill/uMatrix
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global publicSuffixList, punycode */
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
@ -44,13 +42,11 @@ Naming convention from https://en.wikipedia.org/wiki/URI_scheme#Examples
|
||||||
// <http://jsperf.com/old-uritools-vs-new-uritools>
|
// <http://jsperf.com/old-uritools-vs-new-uritools>
|
||||||
// Performance improvements welcomed.
|
// Performance improvements welcomed.
|
||||||
// jsperf: <http://jsperf.com/old-uritools-vs-new-uritools>
|
// jsperf: <http://jsperf.com/old-uritools-vs-new-uritools>
|
||||||
var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
|
const reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
|
||||||
|
|
||||||
// Derived
|
// Derived
|
||||||
var reSchemeFromURI = /^[^:\/?#]+:/;
|
const reSchemeFromURI = /^[a-z][0-9a-z+.-]+:/;
|
||||||
var reAuthorityFromURI = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/;
|
const reOriginFromURI = /^(?:[^:\/?#]+:)\/\/[^\/?#]+/;
|
||||||
var reCommonHostnameFromURL = /^https?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//;
|
|
||||||
var reMustNormalizeHostname = /[^0-9a-z._-]/;
|
|
||||||
|
|
||||||
// These are to parse authority field, not parsed by above official regex
|
// These are to parse authority field, not parsed by above official regex
|
||||||
// IPv6 is seen as an exception: a non-compatible IPv6 is first tried, and
|
// IPv6 is seen as an exception: a non-compatible IPv6 is first tried, and
|
||||||
|
@ -60,15 +56,10 @@ var reMustNormalizeHostname = /[^0-9a-z._-]/;
|
||||||
// https://github.com/gorhill/httpswitchboard/issues/211
|
// https://github.com/gorhill/httpswitchboard/issues/211
|
||||||
// "While a hostname may not contain other characters, such as the
|
// "While a hostname may not contain other characters, such as the
|
||||||
// "underscore character (_), other DNS names may contain the underscore"
|
// "underscore character (_), other DNS names may contain the underscore"
|
||||||
var reHostPortFromAuthority = /^(?:[^@]*@)?([^:]*)(:\d*)?$/;
|
const reHostPortFromAuthority = /^(?:[^@]*@)?([^:]*)(:\d*)?$/;
|
||||||
var reIPv6PortFromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]*\])(:\d*)?$/i;
|
const reIPv6PortFromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]*\])(:\d*)?$/i;
|
||||||
|
|
||||||
var reHostFromNakedAuthority = /^[0-9a-z._-]+[0-9a-z]$/i;
|
const reHostFromNakedAuthority = /^[0-9a-z._-]+[0-9a-z]$/i;
|
||||||
var reHostFromAuthority = /^(?:[^@]*@)?([^:]+)(?::\d*)?$/;
|
|
||||||
var reIPv6FromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]+\])(?::\d*)?$/i;
|
|
||||||
|
|
||||||
// Coarse (but fast) tests
|
|
||||||
var reIPAddressNaive = /^\d+\.\d+\.\d+\.\d+$|^\[[\da-zA-Z:]+\]$/;
|
|
||||||
|
|
||||||
// Accurate tests
|
// Accurate tests
|
||||||
// Source.: http://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp/5284410#5284410
|
// Source.: http://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp/5284410#5284410
|
||||||
|
@ -103,7 +94,7 @@ var resetAuthority = function(o) {
|
||||||
|
|
||||||
// This will be exported
|
// This will be exported
|
||||||
|
|
||||||
var URI = {
|
const URI = {
|
||||||
scheme: '',
|
scheme: '',
|
||||||
authority: '',
|
authority: '',
|
||||||
hostname: '',
|
hostname: '',
|
||||||
|
@ -226,11 +217,16 @@ URI.assemble = function(bits) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
URI.originFromURI = function(uri) {
|
||||||
|
const matches = reOriginFromURI.exec(uri);
|
||||||
|
return matches !== null ? matches[0].toLowerCase() : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
URI.schemeFromURI = function(uri) {
|
URI.schemeFromURI = function(uri) {
|
||||||
var matches = reSchemeFromURI.exec(uri);
|
const matches = reSchemeFromURI.exec(uri);
|
||||||
if ( matches === null ) {
|
if ( matches === null ) { return ''; }
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return matches[0].slice(0, -1).toLowerCase();
|
return matches[0].slice(0, -1).toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -252,138 +248,9 @@ URI.reSecureScheme = /^(?:https|wss|ftps)\b/;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// The most used function, so it better be fast.
|
URI.hostnameFromURI = vAPI.hostnameFromURI;
|
||||||
|
URI.domainFromHostname = vAPI.domainFromHostname;
|
||||||
// https://github.com/gorhill/uBlock/issues/1559
|
URI.domainFromURI = vAPI.domainFromURI;
|
||||||
// See http://en.wikipedia.org/wiki/FQDN
|
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1360285
|
|
||||||
// Revisit punycode dependency when above issue is fixed in Firefox.
|
|
||||||
|
|
||||||
URI.hostnameFromURI = function(uri) {
|
|
||||||
var matches = reCommonHostnameFromURL.exec(uri);
|
|
||||||
if ( matches !== null ) { return matches[1]; }
|
|
||||||
matches = reAuthorityFromURI.exec(uri);
|
|
||||||
if ( matches === null ) { return ''; }
|
|
||||||
var authority = matches[1].slice(2);
|
|
||||||
// Assume very simple authority (most common case for µBlock)
|
|
||||||
if ( reHostFromNakedAuthority.test(authority) ) {
|
|
||||||
return authority.toLowerCase();
|
|
||||||
}
|
|
||||||
matches = reHostFromAuthority.exec(authority);
|
|
||||||
if ( matches === null ) {
|
|
||||||
matches = reIPv6FromAuthority.exec(authority);
|
|
||||||
if ( matches === null ) { return ''; }
|
|
||||||
}
|
|
||||||
var hostname = matches[1];
|
|
||||||
while ( hostname.endsWith('.') ) {
|
|
||||||
hostname = hostname.slice(0, -1);
|
|
||||||
}
|
|
||||||
if ( reMustNormalizeHostname.test(hostname) ) {
|
|
||||||
hostname = punycode.toASCII(hostname.toLowerCase());
|
|
||||||
}
|
|
||||||
return hostname;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
URI.domainFromHostname = function(hostname) {
|
|
||||||
// Try to skip looking up the PSL database
|
|
||||||
var entry = domainCache.get(hostname);
|
|
||||||
if ( entry !== undefined ) {
|
|
||||||
entry.tstamp = Date.now();
|
|
||||||
return entry.domain;
|
|
||||||
}
|
|
||||||
// Meh.. will have to search it
|
|
||||||
if ( reIPAddressNaive.test(hostname) === false ) {
|
|
||||||
return domainCacheAdd(hostname, psl.getDomain(hostname));
|
|
||||||
}
|
|
||||||
return domainCacheAdd(hostname, hostname);
|
|
||||||
};
|
|
||||||
|
|
||||||
// It is expected that there is higher-scoped `publicSuffixList` lingering
|
|
||||||
// somewhere. Cache it. See <https://github.com/gorhill/publicsuffixlist.js>.
|
|
||||||
var psl = publicSuffixList;
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Trying to alleviate the worries of looking up too often the domain name from
|
|
||||||
// a hostname. With a cache, uBlock benefits given that it deals with a
|
|
||||||
// specific set of hostnames within a narrow time span -- in other words, I
|
|
||||||
// believe probability of cache hit are high in uBlock.
|
|
||||||
|
|
||||||
var domainCache = new Map();
|
|
||||||
var domainCacheCountLowWaterMark = 75;
|
|
||||||
var domainCacheCountHighWaterMark = 100;
|
|
||||||
var domainCacheEntryJunkyard = [];
|
|
||||||
var domainCacheEntryJunkyardMax = domainCacheCountHighWaterMark - domainCacheCountLowWaterMark;
|
|
||||||
|
|
||||||
var DomainCacheEntry = function(domain) {
|
|
||||||
this.init(domain);
|
|
||||||
};
|
|
||||||
|
|
||||||
DomainCacheEntry.prototype.init = function(domain) {
|
|
||||||
this.domain = domain;
|
|
||||||
this.tstamp = Date.now();
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
DomainCacheEntry.prototype.dispose = function() {
|
|
||||||
this.domain = '';
|
|
||||||
if ( domainCacheEntryJunkyard.length < domainCacheEntryJunkyardMax ) {
|
|
||||||
domainCacheEntryJunkyard.push(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var domainCacheEntryFactory = function(domain) {
|
|
||||||
var entry = domainCacheEntryJunkyard.pop();
|
|
||||||
if ( entry ) {
|
|
||||||
return entry.init(domain);
|
|
||||||
}
|
|
||||||
return new DomainCacheEntry(domain);
|
|
||||||
};
|
|
||||||
|
|
||||||
var domainCacheAdd = function(hostname, domain) {
|
|
||||||
var entry = domainCache.get(hostname);
|
|
||||||
if ( entry !== undefined ) {
|
|
||||||
entry.tstamp = Date.now();
|
|
||||||
} else {
|
|
||||||
domainCache.set(hostname, domainCacheEntryFactory(domain));
|
|
||||||
if ( domainCache.size === domainCacheCountHighWaterMark ) {
|
|
||||||
domainCachePrune();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return domain;
|
|
||||||
};
|
|
||||||
|
|
||||||
var domainCacheEntrySort = function(a, b) {
|
|
||||||
return domainCache.get(b).tstamp - domainCache.get(a).tstamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
var domainCachePrune = function() {
|
|
||||||
var hostnames = Array.from(domainCache.keys())
|
|
||||||
.sort(domainCacheEntrySort)
|
|
||||||
.slice(domainCacheCountLowWaterMark);
|
|
||||||
var i = hostnames.length;
|
|
||||||
var hostname;
|
|
||||||
while ( i-- ) {
|
|
||||||
hostname = hostnames[i];
|
|
||||||
domainCache.get(hostname).dispose();
|
|
||||||
domainCache.delete(hostname);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('publicSuffixList', function() {
|
|
||||||
domainCache.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
URI.domainFromURI = function(uri) {
|
|
||||||
if ( !uri ) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return this.domainFromHostname(this.hostnameFromURI(uri));
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
|
|
@ -25,21 +25,22 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
|
// >>>>> start of local scope
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Move to dashboard-common.js if needed
|
// Move to dashboard-common.js if needed
|
||||||
|
|
||||||
(function() {
|
{
|
||||||
let timer;
|
let timer;
|
||||||
let resize = function() {
|
const resize = ( ) => {
|
||||||
timer = undefined;
|
timer = undefined;
|
||||||
let child = document.querySelector('.vfill-available');
|
const child = document.querySelector('.vfill-available');
|
||||||
if ( child === null ) { return; }
|
if ( child === null ) { return; }
|
||||||
let prect = document.documentElement.getBoundingClientRect();
|
const prect = document.documentElement.getBoundingClientRect();
|
||||||
let crect = child.getBoundingClientRect();
|
const crect = child.getBoundingClientRect();
|
||||||
let cssHeight = Math.max(prect.bottom - crect.top, 80) + 'px';
|
const cssHeight = Math.max(prect.bottom - crect.top, 80) + 'px';
|
||||||
if ( child.style.height !== cssHeight ) {
|
if ( child.style.height !== cssHeight ) {
|
||||||
child.style.height = cssHeight;
|
child.style.height = cssHeight;
|
||||||
if ( typeof mergeView !== 'undefined' ) {
|
if ( typeof mergeView !== 'undefined' ) {
|
||||||
|
@ -48,7 +49,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let resizeAsync = function(delay) {
|
const resizeAsync = function(delay) {
|
||||||
if ( timer === undefined ) {
|
if ( timer === undefined ) {
|
||||||
timer = vAPI.setTimeout(
|
timer = vAPI.setTimeout(
|
||||||
resize,
|
resize,
|
||||||
|
@ -57,17 +58,17 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('resize', resizeAsync);
|
window.addEventListener('resize', resizeAsync);
|
||||||
var observer = new MutationObserver(resizeAsync);
|
const observer = new MutationObserver(resizeAsync);
|
||||||
observer.observe(document.querySelector('.body'), {
|
observer.observe(document.querySelector('.body'), {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true
|
subtree: true
|
||||||
});
|
});
|
||||||
resizeAsync(1);
|
resizeAsync(1);
|
||||||
})();
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var mergeView = new CodeMirror.MergeView(
|
const mergeView = new CodeMirror.MergeView(
|
||||||
document.querySelector('.codeMirrorMergeContainer'),
|
document.querySelector('.codeMirrorMergeContainer'),
|
||||||
{
|
{
|
||||||
allowEditingOriginals: true,
|
allowEditingOriginals: true,
|
||||||
|
@ -84,15 +85,15 @@ mergeView.editor().setOption('styleActiveLine', true);
|
||||||
mergeView.editor().setOption('lineNumbers', false);
|
mergeView.editor().setOption('lineNumbers', false);
|
||||||
mergeView.leftOriginal().setOption('readOnly', 'nocursor');
|
mergeView.leftOriginal().setOption('readOnly', 'nocursor');
|
||||||
|
|
||||||
var unfilteredRules = {
|
const unfilteredRules = {
|
||||||
orig: { doc: mergeView.leftOriginal(), rules: [] },
|
orig: { doc: mergeView.leftOriginal(), rules: [] },
|
||||||
edit: { doc: mergeView.editor(), rules: [] }
|
edit: { doc: mergeView.editor(), rules: [] }
|
||||||
};
|
};
|
||||||
|
|
||||||
var cleanEditToken = 0;
|
let cleanEditToken = 0;
|
||||||
var cleanEditText = '';
|
let cleanEditText = '';
|
||||||
|
|
||||||
var differ;
|
let differ;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
@ -100,13 +101,13 @@ var differ;
|
||||||
// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js#L22
|
// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js#L22
|
||||||
// ... and modified as needed.
|
// ... and modified as needed.
|
||||||
|
|
||||||
var updateOverlay = (function() {
|
const updateOverlay = (function() {
|
||||||
var reFilter;
|
let reFilter;
|
||||||
var mode = {
|
const mode = {
|
||||||
token: function(stream) {
|
token: function(stream) {
|
||||||
if ( reFilter !== undefined ) {
|
if ( reFilter !== undefined ) {
|
||||||
reFilter.lastIndex = stream.pos;
|
reFilter.lastIndex = stream.pos;
|
||||||
var match = reFilter.exec(stream.string);
|
const match = reFilter.exec(stream.string);
|
||||||
if ( match !== null ) {
|
if ( match !== null ) {
|
||||||
if ( match.index === stream.pos ) {
|
if ( match.index === stream.pos ) {
|
||||||
stream.pos += match[0].length || 1;
|
stream.pos += match[0].length || 1;
|
||||||
|
@ -133,36 +134,36 @@ var updateOverlay = (function() {
|
||||||
// - Scroll position preserved
|
// - Scroll position preserved
|
||||||
// - Minimum amount of text updated
|
// - Minimum amount of text updated
|
||||||
|
|
||||||
var rulesToDoc = function(clearHistory) {
|
const rulesToDoc = function(clearHistory) {
|
||||||
for ( var key in unfilteredRules ) {
|
for ( const key in unfilteredRules ) {
|
||||||
if ( unfilteredRules.hasOwnProperty(key) === false ) { continue; }
|
if ( unfilteredRules.hasOwnProperty(key) === false ) { continue; }
|
||||||
var doc = unfilteredRules[key].doc;
|
const doc = unfilteredRules[key].doc;
|
||||||
var rules = filterRules(key);
|
const rules = filterRules(key);
|
||||||
if ( doc.lineCount() === 1 && doc.getValue() === '' || rules.length === 0 ) {
|
if ( doc.lineCount() === 1 && doc.getValue() === '' || rules.length === 0 ) {
|
||||||
doc.setValue(rules.length !== 0 ? rules.join('\n') : '');
|
doc.setValue(rules.length !== 0 ? rules.join('\n') : '');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ( differ === undefined ) { differ = new diff_match_patch(); }
|
if ( differ === undefined ) { differ = new diff_match_patch(); }
|
||||||
var beforeText = doc.getValue();
|
const beforeText = doc.getValue();
|
||||||
var afterText = rules.join('\n');
|
const afterText = rules.join('\n');
|
||||||
var diffs = differ.diff_main(beforeText, afterText);
|
const diffs = differ.diff_main(beforeText, afterText);
|
||||||
doc.startOperation();
|
doc.startOperation();
|
||||||
var i = diffs.length,
|
let i = diffs.length;
|
||||||
iedit = beforeText.length;
|
let iedit = beforeText.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
var diff = diffs[i];
|
const diff = diffs[i];
|
||||||
if ( diff[0] === 0 ) {
|
if ( diff[0] === 0 ) {
|
||||||
iedit -= diff[1].length;
|
iedit -= diff[1].length;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var end = doc.posFromIndex(iedit);
|
const end = doc.posFromIndex(iedit);
|
||||||
if ( diff[0] === 1 ) {
|
if ( diff[0] === 1 ) {
|
||||||
doc.replaceRange(diff[1], end, end);
|
doc.replaceRange(diff[1], end, end);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* diff[0] === -1 */
|
/* diff[0] === -1 */
|
||||||
iedit -= diff[1].length;
|
iedit -= diff[1].length;
|
||||||
var beg = doc.posFromIndex(iedit);
|
const beg = doc.posFromIndex(iedit);
|
||||||
doc.replaceRange('', beg, end);
|
doc.replaceRange('', beg, end);
|
||||||
}
|
}
|
||||||
doc.endOperation();
|
doc.endOperation();
|
||||||
|
@ -176,12 +177,12 @@ var rulesToDoc = function(clearHistory) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var filterRules = function(key) {
|
const filterRules = function(key) {
|
||||||
var rules = unfilteredRules[key].rules;
|
const filter = uDom('#ruleFilter input').val();
|
||||||
var filter = uDom('#ruleFilter input').val();
|
let rules = unfilteredRules[key].rules;
|
||||||
if ( filter !== '' ) {
|
if ( filter !== '' ) {
|
||||||
rules = rules.slice();
|
rules = rules.slice();
|
||||||
var i = rules.length;
|
let i = rules.length;
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
if ( rules[i].indexOf(filter) === -1 ) {
|
if ( rules[i].indexOf(filter) === -1 ) {
|
||||||
rules.splice(i, 1);
|
rules.splice(i, 1);
|
||||||
|
@ -193,25 +194,20 @@ var filterRules = function(key) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderRules = (function() {
|
const renderRules = function(details, firstVisit = false) {
|
||||||
var firstVisit = true;
|
unfilteredRules.orig.rules = details.permanentRules.sort(directiveSort);
|
||||||
|
unfilteredRules.edit.rules = details.temporaryRules.sort(directiveSort);
|
||||||
return function(details) {
|
rulesToDoc(firstVisit);
|
||||||
unfilteredRules.orig.rules = details.permanentRules.sort(directiveSort);
|
if ( firstVisit ) {
|
||||||
unfilteredRules.edit.rules = details.temporaryRules.sort(directiveSort);
|
mergeView.editor().execCommand('goNextDiff');
|
||||||
rulesToDoc(firstVisit);
|
}
|
||||||
if ( firstVisit ) {
|
onTextChanged(true);
|
||||||
firstVisit = false;
|
};
|
||||||
mergeView.editor().execCommand('goNextDiff');
|
|
||||||
}
|
|
||||||
onTextChanged(true);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Switches before, rules after
|
// Switches before, rules after
|
||||||
var directiveSort = function(a, b) {
|
const directiveSort = function(a, b) {
|
||||||
var aIsSwitch = a.indexOf(': ') !== -1;
|
const aIsSwitch = a.indexOf(': ') !== -1;
|
||||||
var bIsSwitch = b.indexOf(': ') !== -1;
|
const bIsSwitch = b.indexOf(': ') !== -1;
|
||||||
if ( aIsSwitch === bIsSwitch ) {
|
if ( aIsSwitch === bIsSwitch ) {
|
||||||
return a.localeCompare(b);
|
return a.localeCompare(b);
|
||||||
}
|
}
|
||||||
|
@ -220,17 +216,15 @@ var directiveSort = function(a, b) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var applyDiff = function(permanent, toAdd, toRemove) {
|
const applyDiff = function(permanent, toAdd, toRemove) {
|
||||||
vAPI.messaging.send(
|
vAPI.messaging.send('dashboard', {
|
||||||
'user-rules.js',
|
what: 'modifyRuleset',
|
||||||
{
|
permanent,
|
||||||
what: 'modifyRuleset',
|
toAdd,
|
||||||
permanent: permanent,
|
toRemove,
|
||||||
toAdd: toAdd,
|
}).then(response => {
|
||||||
toRemove: toRemove
|
renderRules(response);
|
||||||
},
|
});
|
||||||
renderRules
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -245,20 +239,20 @@ mergeView.options.revertChunk = function(
|
||||||
) {
|
) {
|
||||||
// https://github.com/gorhill/uBlock/issues/3611
|
// https://github.com/gorhill/uBlock/issues/3611
|
||||||
if ( document.body.getAttribute('dir') === 'rtl' ) {
|
if ( document.body.getAttribute('dir') === 'rtl' ) {
|
||||||
var tmp;
|
let tmp;
|
||||||
tmp = from; from = to; to = tmp;
|
tmp = from; from = to; to = tmp;
|
||||||
tmp = fromStart; fromStart = toStart; toStart = tmp;
|
tmp = fromStart; fromStart = toStart; toStart = tmp;
|
||||||
tmp = fromEnd; fromEnd = toEnd; toEnd = tmp;
|
tmp = fromEnd; fromEnd = toEnd; toEnd = tmp;
|
||||||
}
|
}
|
||||||
if ( typeof fromStart.ch !== 'number' ) { fromStart.ch = 0; }
|
if ( typeof fromStart.ch !== 'number' ) { fromStart.ch = 0; }
|
||||||
if ( fromEnd.ch !== 0 ) { fromEnd.line += 1; }
|
if ( fromEnd.ch !== 0 ) { fromEnd.line += 1; }
|
||||||
var toAdd = from.getRange(
|
const toAdd = from.getRange(
|
||||||
{ line: fromStart.line, ch: 0 },
|
{ line: fromStart.line, ch: 0 },
|
||||||
{ line: fromEnd.line, ch: 0 }
|
{ line: fromEnd.line, ch: 0 }
|
||||||
);
|
);
|
||||||
if ( typeof toStart.ch !== 'number' ) { toStart.ch = 0; }
|
if ( typeof toStart.ch !== 'number' ) { toStart.ch = 0; }
|
||||||
if ( toEnd.ch !== 0 ) { toEnd.line += 1; }
|
if ( toEnd.ch !== 0 ) { toEnd.line += 1; }
|
||||||
var toRemove = to.getRange(
|
const toRemove = to.getRange(
|
||||||
{ line: toStart.line, ch: 0 },
|
{ line: toStart.line, ch: 0 },
|
||||||
{ line: toEnd.line, ch: 0 }
|
{ line: toEnd.line, ch: 0 }
|
||||||
);
|
);
|
||||||
|
@ -270,8 +264,8 @@ mergeView.options.revertChunk = function(
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/757
|
// https://github.com/chrisaljoudi/uBlock/issues/757
|
||||||
// Support RequestPolicy rule syntax
|
// Support RequestPolicy rule syntax
|
||||||
|
|
||||||
var fromRequestPolicy = function(content) {
|
const fromRequestPolicy = function(content) {
|
||||||
var matches = /\[origins-to-destinations\]([^\[]+)/.exec(content);
|
const matches = /\[origins-to-destinations\]([^\[]+)/.exec(content);
|
||||||
if ( matches === null || matches.length !== 2 ) { return; }
|
if ( matches === null || matches.length !== 2 ) { return; }
|
||||||
return matches[1].trim()
|
return matches[1].trim()
|
||||||
.replace(/\|/g, ' ')
|
.replace(/\|/g, ' ')
|
||||||
|
@ -282,8 +276,8 @@ var fromRequestPolicy = function(content) {
|
||||||
|
|
||||||
// https://github.com/gorhill/uMatrix/issues/270
|
// https://github.com/gorhill/uMatrix/issues/270
|
||||||
|
|
||||||
var fromNoScript = function(content) {
|
const fromNoScript = function(content) {
|
||||||
var noscript = null;
|
let noscript = null;
|
||||||
try {
|
try {
|
||||||
noscript = JSON.parse(content);
|
noscript = JSON.parse(content);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -298,17 +292,16 @@ var fromNoScript = function(content) {
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var out = new Set();
|
const out = new Set();
|
||||||
var reBad = /[a-z]+:\w*$/;
|
const reBad = /[a-z]+:\w*$/;
|
||||||
var reURL = /[a-z]+:\/\/([0-9a-z.-]+)/;
|
const reURL = /[a-z]+:\/\/([0-9a-z.-]+)/;
|
||||||
var directives = noscript.whitelist.split(/\s+/);
|
const directives = noscript.whitelist.split(/\s+/);
|
||||||
var i = directives.length;
|
let i = directives.length;
|
||||||
var directive, matches;
|
|
||||||
while ( i-- ) {
|
while ( i-- ) {
|
||||||
directive = directives[i].trim();
|
let directive = directives[i].trim();
|
||||||
if ( directive === '' ) { continue; }
|
if ( directive === '' ) { continue; }
|
||||||
if ( reBad.test(directive) ) { continue; }
|
if ( reBad.test(directive) ) { continue; }
|
||||||
matches = reURL.exec(directive);
|
const matches = reURL.exec(directive);
|
||||||
if ( matches !== null ) {
|
if ( matches !== null ) {
|
||||||
directive = matches[1];
|
directive = matches[1];
|
||||||
}
|
}
|
||||||
|
@ -321,12 +314,12 @@ var fromNoScript = function(content) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var handleImportFilePicker = function() {
|
const handleImportFilePicker = function() {
|
||||||
var fileReaderOnLoadHandler = function() {
|
const fileReaderOnLoadHandler = function() {
|
||||||
if ( typeof this.result !== 'string' || this.result === '' ) {
|
if ( typeof this.result !== 'string' || this.result === '' ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var result = fromRequestPolicy(this.result);
|
let result = fromRequestPolicy(this.result);
|
||||||
if ( result === undefined ) {
|
if ( result === undefined ) {
|
||||||
result = fromNoScript(this.result);
|
result = fromNoScript(this.result);
|
||||||
if ( result === undefined ) {
|
if ( result === undefined ) {
|
||||||
|
@ -336,20 +329,20 @@ var handleImportFilePicker = function() {
|
||||||
if ( this.result === '' ) { return; }
|
if ( this.result === '' ) { return; }
|
||||||
applyDiff(false, result, '');
|
applyDiff(false, result, '');
|
||||||
};
|
};
|
||||||
var file = this.files[0];
|
const file = this.files[0];
|
||||||
if ( file === undefined || file.name === '' ) { return; }
|
if ( file === undefined || file.name === '' ) { return; }
|
||||||
if ( file.type.indexOf('text') !== 0 && file.type !== 'application/json') {
|
if ( file.type.indexOf('text') !== 0 && file.type !== 'application/json') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var fr = new FileReader();
|
const fr = new FileReader();
|
||||||
fr.onload = fileReaderOnLoadHandler;
|
fr.onload = fileReaderOnLoadHandler;
|
||||||
fr.readAsText(file);
|
fr.readAsText(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var startImportFilePicker = function() {
|
const startImportFilePicker = function() {
|
||||||
var input = document.getElementById('importFilePicker');
|
const input = document.getElementById('importFilePicker');
|
||||||
// Reset to empty string, this will ensure an change event is properly
|
// Reset to empty string, this will ensure an change event is properly
|
||||||
// triggered if the user pick a file, even if it is the same as the last
|
// triggered if the user pick a file, even if it is the same as the last
|
||||||
// one picked.
|
// one picked.
|
||||||
|
@ -359,26 +352,26 @@ var startImportFilePicker = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
function exportUserRulesToFile() {
|
const exportUserRulesToFile = function() {
|
||||||
vAPI.download({
|
vAPI.download({
|
||||||
url: 'data:text/plain,' + encodeURIComponent(
|
url: 'data:text/plain,' + encodeURIComponent(
|
||||||
mergeView.leftOriginal().getValue().trim() + '\n'
|
mergeView.leftOriginal().getValue().trim() + '\n'
|
||||||
),
|
),
|
||||||
filename: uDom('[data-i18n="userRulesDefaultFileName"]').text()
|
filename: uDom('[data-i18n="userRulesDefaultFileName"]').text()
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onFilterChanged = (function() {
|
const onFilterChanged = (function() {
|
||||||
var timer,
|
let timer;
|
||||||
overlay = null,
|
let overlay = null;
|
||||||
last = '';
|
let last = '';
|
||||||
|
|
||||||
var process = function() {
|
const process = function() {
|
||||||
timer = undefined;
|
timer = undefined;
|
||||||
if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
|
if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
|
||||||
var filter = uDom('#ruleFilter input').val();
|
const filter = uDom('#ruleFilter input').val();
|
||||||
if ( filter === last ) { return; }
|
if ( filter === last ) { return; }
|
||||||
last = filter;
|
last = filter;
|
||||||
if ( overlay !== null ) {
|
if ( overlay !== null ) {
|
||||||
|
@ -402,13 +395,13 @@ var onFilterChanged = (function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onTextChanged = (function() {
|
const onTextChanged = (function() {
|
||||||
var timer;
|
let timer;
|
||||||
|
|
||||||
var process = function(now) {
|
const process = function(now) {
|
||||||
timer = undefined;
|
timer = undefined;
|
||||||
var isClean = mergeView.editor().isClean(cleanEditToken);
|
const diff = document.getElementById('diff');
|
||||||
var diff = document.getElementById('diff');
|
let isClean = mergeView.editor().isClean(cleanEditToken);
|
||||||
if (
|
if (
|
||||||
now &&
|
now &&
|
||||||
isClean === false &&
|
isClean === false &&
|
||||||
|
@ -419,7 +412,7 @@ var onTextChanged = (function() {
|
||||||
}
|
}
|
||||||
diff.classList.toggle('editing', isClean === false);
|
diff.classList.toggle('editing', isClean === false);
|
||||||
diff.classList.toggle('dirty', mergeView.leftChunks().length !== 0);
|
diff.classList.toggle('dirty', mergeView.leftChunks().length !== 0);
|
||||||
var input = document.querySelector('#ruleFilter input');
|
const input = document.querySelector('#ruleFilter input');
|
||||||
if ( isClean ) {
|
if ( isClean ) {
|
||||||
input.removeAttribute('disabled');
|
input.removeAttribute('disabled');
|
||||||
CodeMirror.commands.save = undefined;
|
CodeMirror.commands.save = undefined;
|
||||||
|
@ -437,16 +430,16 @@ var onTextChanged = (function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var revertAllHandler = function() {
|
const revertAllHandler = function() {
|
||||||
var toAdd = [], toRemove = [];
|
const toAdd = [], toRemove = [];
|
||||||
var left = mergeView.leftOriginal(),
|
const left = mergeView.leftOriginal();
|
||||||
edit = mergeView.editor();
|
const edit = mergeView.editor();
|
||||||
for ( var chunk of mergeView.leftChunks() ) {
|
for ( const chunk of mergeView.leftChunks() ) {
|
||||||
var addedLines = left.getRange(
|
const addedLines = left.getRange(
|
||||||
{ line: chunk.origFrom, ch: 0 },
|
{ line: chunk.origFrom, ch: 0 },
|
||||||
{ line: chunk.origTo, ch: 0 }
|
{ line: chunk.origTo, ch: 0 }
|
||||||
);
|
);
|
||||||
var removedLines = edit.getRange(
|
const removedLines = edit.getRange(
|
||||||
{ line: chunk.editFrom, ch: 0 },
|
{ line: chunk.editFrom, ch: 0 },
|
||||||
{ line: chunk.editTo, ch: 0 }
|
{ line: chunk.editTo, ch: 0 }
|
||||||
);
|
);
|
||||||
|
@ -458,16 +451,16 @@ var revertAllHandler = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var commitAllHandler = function() {
|
const commitAllHandler = function() {
|
||||||
var toAdd = [], toRemove = [];
|
const toAdd = [], toRemove = [];
|
||||||
var left = mergeView.leftOriginal(),
|
const left = mergeView.leftOriginal();
|
||||||
edit = mergeView.editor();
|
const edit = mergeView.editor();
|
||||||
for ( var chunk of mergeView.leftChunks() ) {
|
for ( const chunk of mergeView.leftChunks() ) {
|
||||||
var addedLines = edit.getRange(
|
const addedLines = edit.getRange(
|
||||||
{ line: chunk.editFrom, ch: 0 },
|
{ line: chunk.editFrom, ch: 0 },
|
||||||
{ line: chunk.editTo, ch: 0 }
|
{ line: chunk.editTo, ch: 0 }
|
||||||
);
|
);
|
||||||
var removedLines = left.getRange(
|
const removedLines = left.getRange(
|
||||||
{ line: chunk.origFrom, ch: 0 },
|
{ line: chunk.origFrom, ch: 0 },
|
||||||
{ line: chunk.origTo, ch: 0 }
|
{ line: chunk.origTo, ch: 0 }
|
||||||
);
|
);
|
||||||
|
@ -479,17 +472,17 @@ var commitAllHandler = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var editSaveHandler = function() {
|
const editSaveHandler = function() {
|
||||||
var editor = mergeView.editor();
|
const editor = mergeView.editor();
|
||||||
var editText = editor.getValue().trim();
|
const editText = editor.getValue().trim();
|
||||||
if ( editText === cleanEditText ) {
|
if ( editText === cleanEditText ) {
|
||||||
onTextChanged(true);
|
onTextChanged(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( differ === undefined ) { differ = new diff_match_patch(); }
|
if ( differ === undefined ) { differ = new diff_match_patch(); }
|
||||||
var toAdd = [], toRemove = [];
|
const toAdd = [], toRemove = [];
|
||||||
var diffs = differ.diff_main(cleanEditText, editText);
|
const diffs = differ.diff_main(cleanEditText, editText);
|
||||||
for ( var diff of diffs ) {
|
for ( const diff of diffs ) {
|
||||||
if ( diff[0] === 1 ) {
|
if ( diff[0] === 1 ) {
|
||||||
toAdd.push(diff[1]);
|
toAdd.push(diff[1]);
|
||||||
} else if ( diff[0] === -1 ) {
|
} else if ( diff[0] === -1 ) {
|
||||||
|
@ -528,9 +521,14 @@ uDom('#ruleFilter input').on('input', onFilterChanged);
|
||||||
// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs
|
// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs
|
||||||
mergeView.editor().on('updateDiff', function() { onTextChanged(); });
|
mergeView.editor().on('updateDiff', function() { onTextChanged(); });
|
||||||
|
|
||||||
vAPI.messaging.send('user-rules.js', { what: 'getRuleset' }, renderRules);
|
vAPI.messaging.send('dashboard', {
|
||||||
|
what: 'getRuleset',
|
||||||
|
}).then(response => {
|
||||||
|
renderRules(response, true);
|
||||||
|
});
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
// <<<<< end of local scope
|
||||||
|
}
|
||||||
|
|
||||||
|
|
128
src/js/utils.js
128
src/js/utils.js
|
@ -28,13 +28,26 @@
|
||||||
if ( details.shiftKey ) {
|
if ( details.shiftKey ) {
|
||||||
this.changeUserSettings(
|
this.changeUserSettings(
|
||||||
'alwaysDetachLogger',
|
'alwaysDetachLogger',
|
||||||
!this.userSettings.alwaysDetachLogger
|
this.userSettings.alwaysDetachLogger === false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
details.popup = this.userSettings.alwaysDetachLogger;
|
if ( this.userSettings.alwaysDetachLogger ) {
|
||||||
|
details.popup = this.rawSettings.loggerPopupType;
|
||||||
|
const url = new URL(vAPI.getURL(details.url));
|
||||||
|
url.searchParams.set('popup', '1');
|
||||||
|
details.url = url.href;
|
||||||
|
let popupLoggerBox;
|
||||||
|
try {
|
||||||
|
popupLoggerBox = JSON.parse(
|
||||||
|
vAPI.localStorage.getItem('popupLoggerBox')
|
||||||
|
);
|
||||||
|
} catch(ex) {
|
||||||
|
}
|
||||||
|
if ( popupLoggerBox !== undefined ) {
|
||||||
|
details.box = popupLoggerBox;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
details.index = -1;
|
|
||||||
details.select = true;
|
|
||||||
vAPI.tabs.open(details);
|
vAPI.tabs.open(details);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,3 +120,110 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Custom base64 encoder/decoder
|
||||||
|
//
|
||||||
|
// TODO:
|
||||||
|
// Could expand the LZ4 codec API to be able to return UTF8-safe string
|
||||||
|
// representation of a compressed buffer, and thus the code below could be
|
||||||
|
// moved LZ4 codec-side.
|
||||||
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/461
|
||||||
|
// Provide a fallback encoding for Chromium 59 and less by issuing a plain
|
||||||
|
// JSON string. The fallback can be removed once min supported version is
|
||||||
|
// above 59.
|
||||||
|
|
||||||
|
µMatrix.base64 = new (class {
|
||||||
|
constructor() {
|
||||||
|
this.valToDigit = new Uint8Array(64);
|
||||||
|
this.digitToVal = new Uint8Array(128);
|
||||||
|
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@%";
|
||||||
|
for ( let i = 0, n = chars.length; i < n; i++ ) {
|
||||||
|
const c = chars.charCodeAt(i);
|
||||||
|
this.valToDigit[i] = c;
|
||||||
|
this.digitToVal[c] = i;
|
||||||
|
}
|
||||||
|
this.magic = 'Base64_1';
|
||||||
|
}
|
||||||
|
|
||||||
|
encode(arrbuf, arrlen) {
|
||||||
|
const inputLength = (arrlen + 3) >>> 2;
|
||||||
|
const inbuf = new Uint32Array(arrbuf, 0, inputLength);
|
||||||
|
const outputLength = this.magic.length + 7 + inputLength * 7;
|
||||||
|
const outbuf = new Uint8Array(outputLength);
|
||||||
|
let j = 0;
|
||||||
|
for ( let i = 0; i < this.magic.length; i++ ) {
|
||||||
|
outbuf[j++] = this.magic.charCodeAt(i);
|
||||||
|
}
|
||||||
|
let v = inputLength;
|
||||||
|
do {
|
||||||
|
outbuf[j++] = this.valToDigit[v & 0b111111];
|
||||||
|
v >>>= 6;
|
||||||
|
} while ( v !== 0 );
|
||||||
|
outbuf[j++] = 0x20 /* ' ' */;
|
||||||
|
for ( let i = 0; i < inputLength; i++ ) {
|
||||||
|
v = inbuf[i];
|
||||||
|
do {
|
||||||
|
outbuf[j++] = this.valToDigit[v & 0b111111];
|
||||||
|
v >>>= 6;
|
||||||
|
} while ( v !== 0 );
|
||||||
|
outbuf[j++] = 0x20 /* ' ' */;
|
||||||
|
}
|
||||||
|
if ( typeof TextDecoder === 'undefined' ) {
|
||||||
|
return JSON.stringify(
|
||||||
|
Array.from(new Uint32Array(outbuf.buffer, 0, j >>> 2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const textDecoder = new TextDecoder();
|
||||||
|
return textDecoder.decode(new Uint8Array(outbuf.buffer, 0, j));
|
||||||
|
}
|
||||||
|
|
||||||
|
decode(instr, arrbuf) {
|
||||||
|
if ( instr.charCodeAt(0) === 0x5B /* '[' */ ) {
|
||||||
|
const inbuf = JSON.parse(instr);
|
||||||
|
if ( arrbuf instanceof ArrayBuffer === false ) {
|
||||||
|
return new Uint32Array(inbuf);
|
||||||
|
}
|
||||||
|
const outbuf = new Uint32Array(arrbuf);
|
||||||
|
outbuf.set(inbuf);
|
||||||
|
return outbuf;
|
||||||
|
}
|
||||||
|
if ( instr.startsWith(this.magic) === false ) {
|
||||||
|
throw new Error('Invalid µBlock.base64 encoding');
|
||||||
|
}
|
||||||
|
const inputLength = instr.length;
|
||||||
|
const outbuf = arrbuf instanceof ArrayBuffer === false
|
||||||
|
? new Uint32Array(this.decodeSize(instr) >> 2)
|
||||||
|
: new Uint32Array(arrbuf);
|
||||||
|
let i = instr.indexOf(' ', this.magic.length) + 1;
|
||||||
|
if ( i === -1 ) {
|
||||||
|
throw new Error('Invalid µBlock.base64 encoding');
|
||||||
|
}
|
||||||
|
let j = 0;
|
||||||
|
for (;;) {
|
||||||
|
if ( i === inputLength ) { break; }
|
||||||
|
let v = 0, l = 0;
|
||||||
|
for (;;) {
|
||||||
|
const c = instr.charCodeAt(i++);
|
||||||
|
if ( c === 0x20 /* ' ' */ ) { break; }
|
||||||
|
v += this.digitToVal[c] << l;
|
||||||
|
l += 6;
|
||||||
|
}
|
||||||
|
outbuf[j++] = v;
|
||||||
|
}
|
||||||
|
return outbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeSize(instr) {
|
||||||
|
if ( instr.startsWith(this.magic) === false ) { return 0; }
|
||||||
|
let v = 0, l = 0, i = this.magic.length;
|
||||||
|
for (;;) {
|
||||||
|
const c = instr.charCodeAt(i++);
|
||||||
|
if ( c === 0x20 /* ' ' */ ) { break; }
|
||||||
|
v += this.digitToVal[c] << l;
|
||||||
|
l += 6;
|
||||||
|
}
|
||||||
|
return v << 2;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
24
src/js/wasm/README.md
Normal file
24
src/js/wasm/README.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
### For code reviewers
|
||||||
|
|
||||||
|
All `wasm` files in that directory where created by compiling the
|
||||||
|
corresponding `wat` file using the command (using `hntrie.wat`/`hntrie.wasm`
|
||||||
|
as example):
|
||||||
|
|
||||||
|
wat2wasm hntrie.wat -o hntrie.wasm
|
||||||
|
|
||||||
|
Assuming:
|
||||||
|
|
||||||
|
- The command is executed from within the present directory.
|
||||||
|
|
||||||
|
### `wat2wasm` tool
|
||||||
|
|
||||||
|
The `wat2wasm` tool can be downloaded from an official WebAssembly project:
|
||||||
|
<https://github.com/WebAssembly/wabt/releases>.
|
||||||
|
|
||||||
|
### `wat2wasm` tool online
|
||||||
|
|
||||||
|
You can also use the following online `wat2wasm` tool:
|
||||||
|
<https://webassembly.github.io/wabt/demo/wat2wasm/>.
|
||||||
|
|
||||||
|
Just paste the whole content of the `wat` file to compile into the WAT pane.
|
||||||
|
Click "Download" button to retrieve the resulting `wasm` file.
|
BIN
src/js/wasm/hntrie.wasm
Normal file
BIN
src/js/wasm/hntrie.wasm
Normal file
Binary file not shown.
710
src/js/wasm/hntrie.wat
Normal file
710
src/js/wasm/hntrie.wat
Normal file
|
@ -0,0 +1,710 @@
|
||||||
|
;;
|
||||||
|
;; uBlock Origin - a browser extension to block requests.
|
||||||
|
;; Copyright (C) 2018-present Raymond Hill
|
||||||
|
;;
|
||||||
|
;; This program is free software: you can redistribute it and/or modify
|
||||||
|
;; it under the terms of the GNU General Public License as published by
|
||||||
|
;; the Free Software Foundation, either version 3 of the License, or
|
||||||
|
;; (at your option) any later version.
|
||||||
|
;;
|
||||||
|
;; This program is distributed in the hope that it will be useful,
|
||||||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
;; GNU General Public License for more details.
|
||||||
|
;;
|
||||||
|
;; You should have received a copy of the GNU General Public License
|
||||||
|
;; along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||||
|
;;
|
||||||
|
;; Home: https://github.com/gorhill/uBlock
|
||||||
|
;; File: hntrie.wat
|
||||||
|
;; Description: WebAssembly code used by src/js/hntrie.js
|
||||||
|
;; How to compile: See README.md in this directory.
|
||||||
|
|
||||||
|
(module
|
||||||
|
;;
|
||||||
|
;; module start
|
||||||
|
;;
|
||||||
|
|
||||||
|
(func $growBuf (import "imports" "growBuf"))
|
||||||
|
(memory (import "imports" "memory") 1)
|
||||||
|
|
||||||
|
;; Trie container
|
||||||
|
;;
|
||||||
|
;; Memory layout, byte offset:
|
||||||
|
;; 0-254: needle being processed
|
||||||
|
;; 255: length of needle
|
||||||
|
;; 256-259: offset to start of trie data section (=> trie0)
|
||||||
|
;; 260-263: offset to end of trie data section (=> trie1)
|
||||||
|
;; 264-267: offset to start of character data section (=> char0)
|
||||||
|
;; 268-271: offset to end of character data section (=> char1)
|
||||||
|
;; 272: start of trie data section
|
||||||
|
;;
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Public functions
|
||||||
|
;;
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; unsigned int matches(icell)
|
||||||
|
;;
|
||||||
|
;; Test whether the currently set needle matches the trie at specified trie
|
||||||
|
;; offset.
|
||||||
|
;;
|
||||||
|
(func (export "matches")
|
||||||
|
(param $iroot i32) ;; offset to root cell of the trie
|
||||||
|
(result i32) ;; result = match index, -1 = miss
|
||||||
|
(local $icell i32) ;; offset to the current cell
|
||||||
|
(local $char0 i32) ;; offset to first character data
|
||||||
|
(local $ineedle i32) ;; current needle offset
|
||||||
|
(local $c i32)
|
||||||
|
(local $v i32)
|
||||||
|
(local $n i32)
|
||||||
|
(local $i0 i32)
|
||||||
|
(local $i1 i32)
|
||||||
|
;;
|
||||||
|
i32.const 264 ;; start of char section is stored at addr 264
|
||||||
|
i32.load
|
||||||
|
set_local $char0
|
||||||
|
;; let ineedle = this.buf[255];
|
||||||
|
i32.const 255 ;; addr of needle is stored at addr 255
|
||||||
|
i32.load8_u
|
||||||
|
set_local $ineedle
|
||||||
|
;; let icell = this.buf32[iroot+0];
|
||||||
|
get_local $iroot
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
i32.load
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
tee_local $icell
|
||||||
|
;; if ( icell === 0 ) { return -1; }
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const -1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; for (;;) {
|
||||||
|
block $noSegment loop $nextSegment
|
||||||
|
;; if ( ineedle === 0 ) { return -1; }
|
||||||
|
get_local $ineedle
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const -1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; ineedle -= 1;
|
||||||
|
get_local $ineedle
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
tee_local $ineedle
|
||||||
|
;; let c = this.buf[ineedle];
|
||||||
|
i32.load8_u
|
||||||
|
set_local $c
|
||||||
|
;; for (;;) {
|
||||||
|
block $foundSegment loop $findSegment
|
||||||
|
;; v = this.buf32[icell+2];
|
||||||
|
get_local $icell
|
||||||
|
i32.load offset=8
|
||||||
|
tee_local $v
|
||||||
|
;; i0 = this.char0 + (v & 0x00FFFFFF);
|
||||||
|
i32.const 0x00FFFFFF
|
||||||
|
i32.and
|
||||||
|
get_local $char0
|
||||||
|
i32.add
|
||||||
|
tee_local $i0
|
||||||
|
;; if ( this.buf[i0] === c ) { break; }
|
||||||
|
i32.load8_u
|
||||||
|
get_local $c
|
||||||
|
i32.eq
|
||||||
|
br_if $foundSegment
|
||||||
|
;; icell = this.buf32[icell+0];
|
||||||
|
get_local $icell
|
||||||
|
i32.load
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
tee_local $icell
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const -1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
br 0
|
||||||
|
end end
|
||||||
|
;; let n = v >>> 24;
|
||||||
|
get_local $v
|
||||||
|
i32.const 24
|
||||||
|
i32.shr_u
|
||||||
|
tee_local $n
|
||||||
|
;; if ( n > 1 ) {
|
||||||
|
i32.const 1
|
||||||
|
i32.gt_u
|
||||||
|
if
|
||||||
|
;; n -= 1;
|
||||||
|
get_local $n
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
tee_local $n
|
||||||
|
;; if ( n > ineedle ) { return -1; }
|
||||||
|
get_local $ineedle
|
||||||
|
i32.gt_u
|
||||||
|
if
|
||||||
|
i32.const -1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
get_local $i0
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
tee_local $i0
|
||||||
|
;; const i1 = i0 + n;
|
||||||
|
get_local $n
|
||||||
|
i32.add
|
||||||
|
set_local $i1
|
||||||
|
;; do {
|
||||||
|
loop
|
||||||
|
;; ineedle -= 1;
|
||||||
|
get_local $ineedle
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
tee_local $ineedle
|
||||||
|
;; if ( this.buf[i0] !== this.buf[ineedle] ) { return -1; }
|
||||||
|
i32.load8_u
|
||||||
|
get_local $i0
|
||||||
|
i32.load8_u
|
||||||
|
i32.ne
|
||||||
|
if
|
||||||
|
i32.const -1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; i0 += 1;
|
||||||
|
get_local $i0
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
tee_local $i0
|
||||||
|
;; } while ( i0 < i1 );
|
||||||
|
get_local $i1
|
||||||
|
i32.lt_u
|
||||||
|
br_if 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
;; icell = this.buf32[icell+1];
|
||||||
|
get_local $icell
|
||||||
|
i32.load offset=4
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
tee_local $icell
|
||||||
|
;; if ( icell === 0 ) { break; }
|
||||||
|
i32.eqz
|
||||||
|
br_if $noSegment
|
||||||
|
;; if ( this.buf32[icell+2] === 0 ) {
|
||||||
|
get_local $icell
|
||||||
|
i32.load
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
;; if ( ineedle === 0 || this.buf[ineedle-1] === 0x2E ) {
|
||||||
|
;; return ineedle;
|
||||||
|
;; }
|
||||||
|
get_local $ineedle
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
get_local $ineedle
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
i32.load8_u
|
||||||
|
i32.const 0x2E
|
||||||
|
i32.eq
|
||||||
|
if
|
||||||
|
get_local $ineedle
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; icell = this.buf32[icell+1];
|
||||||
|
get_local $icell
|
||||||
|
i32.load offset=4
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
set_local $icell
|
||||||
|
end
|
||||||
|
br 0
|
||||||
|
end end
|
||||||
|
;; return ineedle === 0 || this.buf[ineedle-1] === 0x2E ? ineedle : -1;
|
||||||
|
get_local $ineedle
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
get_local $ineedle
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
i32.load8_u
|
||||||
|
i32.const 0x2E
|
||||||
|
i32.eq
|
||||||
|
if
|
||||||
|
get_local $ineedle
|
||||||
|
return
|
||||||
|
end
|
||||||
|
i32.const -1
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; unsigned int add(icell)
|
||||||
|
;;
|
||||||
|
;; Add a new hostname to a trie which root cell is passed as argument.
|
||||||
|
;;
|
||||||
|
(func (export "add")
|
||||||
|
(param $iroot i32) ;; index of root cell of the trie
|
||||||
|
(result i32) ;; result: 0 not added, 1 = added
|
||||||
|
(local $icell i32) ;; index of current cell in the trie
|
||||||
|
(local $lhnchar i32) ;; number of characters left to process in hostname
|
||||||
|
(local $char0 i32) ;; offset to start of character data section
|
||||||
|
(local $vseg i32) ;; integer value describing a segment
|
||||||
|
(local $isegchar0 i32) ;; offset to start of current segment's character data
|
||||||
|
(local $isegchar i32)
|
||||||
|
(local $lsegchar i32) ;; number of character in current segment
|
||||||
|
(local $inext i32) ;; index of next cell to process
|
||||||
|
;;
|
||||||
|
;; let lhnchar = this.buf[255];
|
||||||
|
i32.const 255
|
||||||
|
i32.load8_u
|
||||||
|
tee_local $lhnchar
|
||||||
|
;; if ( lhnchar === 0 ) { return 0; }
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; if (
|
||||||
|
;; (this.buf32[HNBIGTRIE_CHAR0_SLOT] - this.buf32[HNBIGTRIE_TRIE1_SLOT]) < 24 ||
|
||||||
|
;; (this.buf.length - this.buf32[HNBIGTRIE_CHAR1_SLOT]) < 256
|
||||||
|
;; ) {
|
||||||
|
;; this.growBuf();
|
||||||
|
;; }
|
||||||
|
i32.const 264
|
||||||
|
i32.load
|
||||||
|
i32.const 260
|
||||||
|
i32.load
|
||||||
|
i32.sub
|
||||||
|
i32.const 24
|
||||||
|
i32.lt_u
|
||||||
|
if
|
||||||
|
call $growBuf
|
||||||
|
else
|
||||||
|
memory.size
|
||||||
|
i32.const 16
|
||||||
|
i32.shl
|
||||||
|
i32.const 268
|
||||||
|
i32.load
|
||||||
|
i32.sub
|
||||||
|
i32.const 256
|
||||||
|
i32.lt_u
|
||||||
|
if
|
||||||
|
call $growBuf
|
||||||
|
end
|
||||||
|
end
|
||||||
|
;; let icell = this.buf32[iroot+0];
|
||||||
|
get_local $iroot
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
tee_local $iroot
|
||||||
|
i32.load
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
tee_local $icell
|
||||||
|
;; if ( this.buf32[icell+2] === 0 ) {
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
;; this.buf32[iroot+0] = this.addCell(0, 0, this.addSegment(lhnchar));
|
||||||
|
;; return 1;
|
||||||
|
get_local $iroot
|
||||||
|
i32.const 0
|
||||||
|
i32.const 0
|
||||||
|
get_local $lhnchar
|
||||||
|
call $addSegment
|
||||||
|
call $addCell
|
||||||
|
i32.store
|
||||||
|
i32.const 1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; const char0 = this.buf32[HNBIGTRIE_CHAR0_SLOT];
|
||||||
|
i32.const 264
|
||||||
|
i32.load
|
||||||
|
set_local $char0
|
||||||
|
;; for (;;) {
|
||||||
|
loop $nextSegment
|
||||||
|
;; const v = this.buf32[icell+2];
|
||||||
|
get_local $icell
|
||||||
|
i32.load offset=8
|
||||||
|
tee_local $vseg
|
||||||
|
;; if ( vseg === 0 ) {
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
;; if ( this.buf[lhnchar-1] === 0x2E /* '.' */ ) { return -1; }
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
i32.load8_u
|
||||||
|
i32.const 0x2E
|
||||||
|
i32.eq
|
||||||
|
if
|
||||||
|
i32.const -1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; icell = this.buf32[icell+1];
|
||||||
|
;; continue;
|
||||||
|
get_local $icell
|
||||||
|
i32.load offset=4
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
set_local $icell
|
||||||
|
br $nextSegment
|
||||||
|
end
|
||||||
|
;; let isegchar0 = char0 + (vseg & 0x00FFFFFF);
|
||||||
|
get_local $char0
|
||||||
|
get_local $vseg
|
||||||
|
i32.const 0x00FFFFFF
|
||||||
|
i32.and
|
||||||
|
i32.add
|
||||||
|
tee_local $isegchar0
|
||||||
|
;; if ( this.buf[isegchar0] !== this.buf[lhnchar-1] ) {
|
||||||
|
i32.load8_u
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
i32.load8_u
|
||||||
|
i32.ne
|
||||||
|
if
|
||||||
|
;; inext = this.buf32[icell+0];
|
||||||
|
get_local $icell
|
||||||
|
i32.load
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
tee_local $inext
|
||||||
|
;; if ( inext === 0 ) {
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
;; this.buf32[icell+0] = this.addCell(0, 0, this.addSegment(lhnchar));
|
||||||
|
get_local $icell
|
||||||
|
i32.const 0
|
||||||
|
i32.const 0
|
||||||
|
get_local $lhnchar
|
||||||
|
call $addSegment
|
||||||
|
call $addCell
|
||||||
|
i32.store
|
||||||
|
;; return 1;
|
||||||
|
i32.const 1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; icell = inext;
|
||||||
|
get_local $inext
|
||||||
|
set_local $icell
|
||||||
|
br $nextSegment
|
||||||
|
end
|
||||||
|
;; let isegchar = 1;
|
||||||
|
i32.const 1
|
||||||
|
set_local $isegchar
|
||||||
|
;; lhnchar -= 1;
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
set_local $lhnchar
|
||||||
|
;; const lsegchar = vseg >>> 24;
|
||||||
|
get_local $vseg
|
||||||
|
i32.const 24
|
||||||
|
i32.shr_u
|
||||||
|
tee_local $lsegchar
|
||||||
|
;; if ( lsegchar !== 1 ) {
|
||||||
|
i32.const 1
|
||||||
|
i32.ne
|
||||||
|
if
|
||||||
|
;; for (;;) {
|
||||||
|
block $mismatch loop
|
||||||
|
;; if ( isegchar === lsegchar ) { break; }
|
||||||
|
get_local $isegchar
|
||||||
|
get_local $lsegchar
|
||||||
|
i32.eq
|
||||||
|
br_if $mismatch
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.eqz
|
||||||
|
br_if $mismatch
|
||||||
|
;; if ( this.buf[isegchar0+isegchar] !== this.buf[lhnchar-1] ) { break; }
|
||||||
|
get_local $isegchar0
|
||||||
|
get_local $isegchar
|
||||||
|
i32.add
|
||||||
|
i32.load8_u
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
i32.load8_u
|
||||||
|
i32.ne
|
||||||
|
br_if $mismatch
|
||||||
|
;; isegchar += 1;
|
||||||
|
get_local $isegchar
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $isegchar
|
||||||
|
;; lhnchar -= 1;
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
set_local $lhnchar
|
||||||
|
br 0
|
||||||
|
end end
|
||||||
|
end
|
||||||
|
;; if ( isegchar === lsegchar ) {
|
||||||
|
get_local $isegchar
|
||||||
|
get_local $lsegchar
|
||||||
|
i32.eq
|
||||||
|
if
|
||||||
|
;; inext = this.buf32[icell+1];
|
||||||
|
get_local $icell
|
||||||
|
i32.load offset=4
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
set_local $inext
|
||||||
|
;; if ( lhnchar === 0 ) {
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
;; if ( inext === 0 || this.buf32[inext+2] === 0 ) { return 0; }
|
||||||
|
get_local $inext
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
get_local $inext
|
||||||
|
i32.load offset=8
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; this.buf32[icell+1] = this.addCell(0, inext, 0);
|
||||||
|
get_local $icell
|
||||||
|
i32.const 0
|
||||||
|
get_local $inext
|
||||||
|
i32.const 2
|
||||||
|
i32.shr_u
|
||||||
|
i32.const 0
|
||||||
|
call $addCell
|
||||||
|
i32.store offset=4
|
||||||
|
else
|
||||||
|
;; if ( inext !== 0 ) {
|
||||||
|
get_local $inext
|
||||||
|
if
|
||||||
|
;; icell = inext;
|
||||||
|
get_local $inext
|
||||||
|
set_local $icell
|
||||||
|
br $nextSegment
|
||||||
|
end
|
||||||
|
;; if ( this.buf[lhnchar-1] === 0x2E /* '.' */ ) { return -1; }
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
i32.load8_u
|
||||||
|
i32.const 0x2E
|
||||||
|
i32.eq
|
||||||
|
if
|
||||||
|
i32.const -1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; inext = this.addCell(0, 0, 0);
|
||||||
|
;; this.buf32[icell+1] = inext;
|
||||||
|
get_local $icell
|
||||||
|
i32.const 0
|
||||||
|
i32.const 0
|
||||||
|
i32.const 0
|
||||||
|
call $addCell
|
||||||
|
tee_local $inext
|
||||||
|
i32.store offset=4
|
||||||
|
;; this.buf32[inext+1] = this.addCell(0, 0, this.addSegment(lhnchar));
|
||||||
|
get_local $inext
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
i32.const 0
|
||||||
|
i32.const 0
|
||||||
|
get_local $lhnchar
|
||||||
|
call $addSegment
|
||||||
|
call $addCell
|
||||||
|
i32.store offset=4
|
||||||
|
end
|
||||||
|
else
|
||||||
|
;; isegchar0 -= char0;
|
||||||
|
get_local $icell
|
||||||
|
get_local $isegchar0
|
||||||
|
get_local $char0
|
||||||
|
i32.sub
|
||||||
|
tee_local $isegchar0
|
||||||
|
;; this.buf32[icell+2] = isegchar << 24 | isegchar0;
|
||||||
|
get_local $isegchar
|
||||||
|
i32.const 24
|
||||||
|
i32.shl
|
||||||
|
i32.or
|
||||||
|
i32.store offset=8
|
||||||
|
;; inext = this.addCell(
|
||||||
|
;; 0,
|
||||||
|
;; this.buf32[icell+1],
|
||||||
|
;; lsegchar - isegchar << 24 | isegchar0 + isegchar
|
||||||
|
;; );
|
||||||
|
;; this.buf32[icell+1] = inext;
|
||||||
|
get_local $icell
|
||||||
|
i32.const 0
|
||||||
|
get_local $icell
|
||||||
|
i32.load offset=4
|
||||||
|
get_local $lsegchar
|
||||||
|
get_local $isegchar
|
||||||
|
i32.sub
|
||||||
|
i32.const 24
|
||||||
|
i32.shl
|
||||||
|
get_local $isegchar0
|
||||||
|
get_local $isegchar
|
||||||
|
i32.add
|
||||||
|
i32.or
|
||||||
|
call $addCell
|
||||||
|
tee_local $inext
|
||||||
|
i32.store offset=4
|
||||||
|
;; if ( lhnchar === 0 ) {
|
||||||
|
get_local $lhnchar
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
;; this.buf32[icell+1] = this.addCell(0, inext, 0);
|
||||||
|
get_local $icell
|
||||||
|
i32.const 0
|
||||||
|
get_local $inext
|
||||||
|
i32.const 0
|
||||||
|
call $addCell
|
||||||
|
i32.store offset=4
|
||||||
|
else
|
||||||
|
;; this.buf32[inext+0] = this.addCell(0, 0, this.addSegment(lhnchar));
|
||||||
|
get_local $inext
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
i32.const 0
|
||||||
|
i32.const 0
|
||||||
|
get_local $lhnchar
|
||||||
|
call $addSegment
|
||||||
|
call $addCell
|
||||||
|
i32.store
|
||||||
|
end
|
||||||
|
end
|
||||||
|
;; return 1;
|
||||||
|
i32.const 1
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;;
|
||||||
|
i32.const 1
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Private functions
|
||||||
|
;;
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; unsigned int addCell(idown, iright, vseg)
|
||||||
|
;;
|
||||||
|
;; Add a new cell, return cell index.
|
||||||
|
;;
|
||||||
|
(func $addCell
|
||||||
|
(param $idown i32)
|
||||||
|
(param $iright i32)
|
||||||
|
(param $vseg i32)
|
||||||
|
(result i32) ;; result: index of added cell
|
||||||
|
(local $icell i32)
|
||||||
|
;;
|
||||||
|
;; let icell = this.buf32[HNBIGTRIE_TRIE1_SLOT];
|
||||||
|
;; this.buf32[HNBIGTRIE_TRIE1_SLOT] = icell + 12;
|
||||||
|
i32.const 260
|
||||||
|
i32.const 260
|
||||||
|
i32.load
|
||||||
|
tee_local $icell
|
||||||
|
i32.const 12
|
||||||
|
i32.add
|
||||||
|
i32.store
|
||||||
|
;; this.buf32[icell+0] = idown;
|
||||||
|
get_local $icell
|
||||||
|
get_local $idown
|
||||||
|
i32.store
|
||||||
|
;; this.buf32[icell+1] = iright;
|
||||||
|
get_local $icell
|
||||||
|
get_local $iright
|
||||||
|
i32.store offset=4
|
||||||
|
;; this.buf32[icell+2] = v;
|
||||||
|
get_local $icell
|
||||||
|
get_local $vseg
|
||||||
|
i32.store offset=8
|
||||||
|
;; return icell;
|
||||||
|
get_local $icell
|
||||||
|
i32.const 2
|
||||||
|
i32.shr_u
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; unsigned int addSegment(lsegchar)
|
||||||
|
;;
|
||||||
|
;; Store a segment of characters and return a segment descriptor. The segment
|
||||||
|
;; is created from the character data in the needle buffer.
|
||||||
|
;;
|
||||||
|
(func $addSegment
|
||||||
|
(param $lsegchar i32)
|
||||||
|
(result i32) ;; result: segment descriptor
|
||||||
|
(local $char1 i32) ;; offset to end of character data section
|
||||||
|
(local $isegchar i32) ;; relative offset to first character of segment
|
||||||
|
(local $i i32) ;; iterator
|
||||||
|
;;
|
||||||
|
;; if ( lsegchar === 0 ) { return 0; }
|
||||||
|
get_local $lsegchar
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
;; let char1 = this.buf32[HNBIGTRIE_CHAR1_SLOT];
|
||||||
|
i32.const 268
|
||||||
|
i32.load
|
||||||
|
tee_local $char1
|
||||||
|
;; const isegchar = char1 - this.buf32[HNBIGTRIE_CHAR0_SLOT];
|
||||||
|
i32.const 264
|
||||||
|
i32.load
|
||||||
|
i32.sub
|
||||||
|
set_local $isegchar
|
||||||
|
;; let i = lsegchar;
|
||||||
|
get_local $lsegchar
|
||||||
|
set_local $i
|
||||||
|
;; do {
|
||||||
|
block $endOfSegment loop
|
||||||
|
;; this.buf[char1++] = this.buf[--i];
|
||||||
|
get_local $char1
|
||||||
|
get_local $i
|
||||||
|
i32.const -1
|
||||||
|
i32.add
|
||||||
|
tee_local $i
|
||||||
|
i32.load8_u
|
||||||
|
i32.store8
|
||||||
|
get_local $char1
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $char1
|
||||||
|
;; } while ( i !== 0 );
|
||||||
|
get_local $i
|
||||||
|
i32.eqz
|
||||||
|
br_if $endOfSegment
|
||||||
|
br 0
|
||||||
|
end end
|
||||||
|
;; this.buf32[HNBIGTRIE_CHAR1_SLOT] = char1;
|
||||||
|
i32.const 268
|
||||||
|
get_local $char1
|
||||||
|
i32.store
|
||||||
|
;; return (lsegchar << 24) | isegchar;
|
||||||
|
get_local $lsegchar
|
||||||
|
i32.const 24
|
||||||
|
i32.shl
|
||||||
|
get_local $isegchar
|
||||||
|
i32.or
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; module end
|
||||||
|
;;
|
||||||
|
)
|
127
src/lib/codemirror/addon/display/panel.js
vendored
Normal file
127
src/lib/codemirror/addon/display/panel.js
vendored
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
CodeMirror.defineExtension("addPanel", function(node, options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if (!this.state.panels) initPanels(this);
|
||||||
|
|
||||||
|
var info = this.state.panels;
|
||||||
|
var wrapper = info.wrapper;
|
||||||
|
var cmWrapper = this.getWrapperElement();
|
||||||
|
var replace = options.replace instanceof Panel && !options.replace.cleared;
|
||||||
|
|
||||||
|
if (options.after instanceof Panel && !options.after.cleared) {
|
||||||
|
wrapper.insertBefore(node, options.before.node.nextSibling);
|
||||||
|
} else if (options.before instanceof Panel && !options.before.cleared) {
|
||||||
|
wrapper.insertBefore(node, options.before.node);
|
||||||
|
} else if (replace) {
|
||||||
|
wrapper.insertBefore(node, options.replace.node);
|
||||||
|
info.panels++;
|
||||||
|
options.replace.clear();
|
||||||
|
} else if (options.position == "bottom") {
|
||||||
|
wrapper.appendChild(node);
|
||||||
|
} else if (options.position == "before-bottom") {
|
||||||
|
wrapper.insertBefore(node, cmWrapper.nextSibling);
|
||||||
|
} else if (options.position == "after-top") {
|
||||||
|
wrapper.insertBefore(node, cmWrapper);
|
||||||
|
} else {
|
||||||
|
wrapper.insertBefore(node, wrapper.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
var height = (options && options.height) || node.offsetHeight;
|
||||||
|
this._setSize(null, info.heightLeft -= height);
|
||||||
|
if (!replace) {
|
||||||
|
info.panels++;
|
||||||
|
}
|
||||||
|
if (options.stable && isAtTop(this, node))
|
||||||
|
this.scrollTo(null, this.getScrollInfo().top + height)
|
||||||
|
|
||||||
|
return new Panel(this, node, options, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
function Panel(cm, node, options, height) {
|
||||||
|
this.cm = cm;
|
||||||
|
this.node = node;
|
||||||
|
this.options = options;
|
||||||
|
this.height = height;
|
||||||
|
this.cleared = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel.prototype.clear = function() {
|
||||||
|
if (this.cleared) return;
|
||||||
|
this.cleared = true;
|
||||||
|
var info = this.cm.state.panels;
|
||||||
|
this.cm._setSize(null, info.heightLeft += this.height);
|
||||||
|
if (this.options.stable && isAtTop(this.cm, this.node))
|
||||||
|
this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height)
|
||||||
|
info.wrapper.removeChild(this.node);
|
||||||
|
if (--info.panels == 0) removePanels(this.cm);
|
||||||
|
};
|
||||||
|
|
||||||
|
Panel.prototype.changed = function(height) {
|
||||||
|
var newHeight = height == null ? this.node.offsetHeight : height;
|
||||||
|
var info = this.cm.state.panels;
|
||||||
|
this.cm._setSize(null, info.heightLeft -= (newHeight - this.height));
|
||||||
|
this.height = newHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
function initPanels(cm) {
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
|
||||||
|
var height = parseInt(style.height);
|
||||||
|
var info = cm.state.panels = {
|
||||||
|
setHeight: wrap.style.height,
|
||||||
|
heightLeft: height,
|
||||||
|
panels: 0,
|
||||||
|
wrapper: document.createElement("div")
|
||||||
|
};
|
||||||
|
wrap.parentNode.insertBefore(info.wrapper, wrap);
|
||||||
|
var hasFocus = cm.hasFocus();
|
||||||
|
info.wrapper.appendChild(wrap);
|
||||||
|
if (hasFocus) cm.focus();
|
||||||
|
|
||||||
|
cm._setSize = cm.setSize;
|
||||||
|
if (height != null) cm.setSize = function(width, newHeight) {
|
||||||
|
if (newHeight == null) return this._setSize(width, newHeight);
|
||||||
|
info.setHeight = newHeight;
|
||||||
|
if (typeof newHeight != "number") {
|
||||||
|
var px = /^(\d+\.?\d*)px$/.exec(newHeight);
|
||||||
|
if (px) {
|
||||||
|
newHeight = Number(px[1]);
|
||||||
|
} else {
|
||||||
|
info.wrapper.style.height = newHeight;
|
||||||
|
newHeight = info.wrapper.offsetHeight;
|
||||||
|
info.wrapper.style.height = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cm._setSize(width, info.heightLeft += (newHeight - height));
|
||||||
|
height = newHeight;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePanels(cm) {
|
||||||
|
var info = cm.state.panels;
|
||||||
|
cm.state.panels = null;
|
||||||
|
|
||||||
|
var wrap = cm.getWrapperElement();
|
||||||
|
info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
|
||||||
|
wrap.style.height = info.setHeight;
|
||||||
|
cm.setSize = cm._setSize;
|
||||||
|
cm.setSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAtTop(cm, dom) {
|
||||||
|
for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling)
|
||||||
|
if (sibling == cm.getWrapperElement()) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
});
|
122
src/lib/codemirror/addon/scroll/annotatescrollbar.js
vendored
Normal file
122
src/lib/codemirror/addon/scroll/annotatescrollbar.js
vendored
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("annotateScrollbar", function(options) {
|
||||||
|
if (typeof options == "string") options = {className: options};
|
||||||
|
return new Annotation(this, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineOption("scrollButtonHeight", 0);
|
||||||
|
|
||||||
|
function Annotation(cm, options) {
|
||||||
|
this.cm = cm;
|
||||||
|
this.options = options;
|
||||||
|
this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
|
||||||
|
this.annotations = [];
|
||||||
|
this.doRedraw = this.doUpdate = null;
|
||||||
|
this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
|
||||||
|
this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
|
||||||
|
this.computeScale();
|
||||||
|
|
||||||
|
function scheduleRedraw(delay) {
|
||||||
|
clearTimeout(self.doRedraw);
|
||||||
|
self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
cm.on("refresh", this.resizeHandler = function() {
|
||||||
|
clearTimeout(self.doUpdate);
|
||||||
|
self.doUpdate = setTimeout(function() {
|
||||||
|
if (self.computeScale()) scheduleRedraw(20);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
cm.on("markerAdded", this.resizeHandler);
|
||||||
|
cm.on("markerCleared", this.resizeHandler);
|
||||||
|
if (options.listenForChanges !== false)
|
||||||
|
cm.on("change", this.changeHandler = function() {
|
||||||
|
scheduleRedraw(250);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Annotation.prototype.computeScale = function() {
|
||||||
|
var cm = this.cm;
|
||||||
|
var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
|
||||||
|
cm.getScrollerElement().scrollHeight
|
||||||
|
if (hScale != this.hScale) {
|
||||||
|
this.hScale = hScale;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Annotation.prototype.update = function(annotations) {
|
||||||
|
this.annotations = annotations;
|
||||||
|
this.redraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
Annotation.prototype.redraw = function(compute) {
|
||||||
|
if (compute !== false) this.computeScale();
|
||||||
|
var cm = this.cm, hScale = this.hScale;
|
||||||
|
|
||||||
|
var frag = document.createDocumentFragment(), anns = this.annotations;
|
||||||
|
|
||||||
|
var wrapping = cm.getOption("lineWrapping");
|
||||||
|
var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
|
||||||
|
var curLine = null, curLineObj = null;
|
||||||
|
function getY(pos, top) {
|
||||||
|
if (curLine != pos.line) {
|
||||||
|
curLine = pos.line;
|
||||||
|
curLineObj = cm.getLineHandle(curLine);
|
||||||
|
}
|
||||||
|
if ((curLineObj.widgets && curLineObj.widgets.length) ||
|
||||||
|
(wrapping && curLineObj.height > singleLineH))
|
||||||
|
return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
|
||||||
|
var topY = cm.heightAtLine(curLineObj, "local");
|
||||||
|
return topY + (top ? 0 : curLineObj.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastLine = cm.lastLine()
|
||||||
|
if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
|
||||||
|
var ann = anns[i];
|
||||||
|
if (ann.to.line > lastLine) continue;
|
||||||
|
var top = nextTop || getY(ann.from, true) * hScale;
|
||||||
|
var bottom = getY(ann.to, false) * hScale;
|
||||||
|
while (i < anns.length - 1) {
|
||||||
|
if (anns[i + 1].to.line > lastLine) break;
|
||||||
|
nextTop = getY(anns[i + 1].from, true) * hScale;
|
||||||
|
if (nextTop > bottom + .9) break;
|
||||||
|
ann = anns[++i];
|
||||||
|
bottom = getY(ann.to, false) * hScale;
|
||||||
|
}
|
||||||
|
if (bottom == top) continue;
|
||||||
|
var height = Math.max(bottom - top, 3);
|
||||||
|
|
||||||
|
var elt = frag.appendChild(document.createElement("div"));
|
||||||
|
elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
|
||||||
|
+ (top + this.buttonHeight) + "px; height: " + height + "px";
|
||||||
|
elt.className = this.options.className;
|
||||||
|
if (ann.id) {
|
||||||
|
elt.setAttribute("annotation-id", ann.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.div.textContent = "";
|
||||||
|
this.div.appendChild(frag);
|
||||||
|
};
|
||||||
|
|
||||||
|
Annotation.prototype.clear = function() {
|
||||||
|
this.cm.off("refresh", this.resizeHandler);
|
||||||
|
this.cm.off("markerAdded", this.resizeHandler);
|
||||||
|
this.cm.off("markerCleared", this.resizeHandler);
|
||||||
|
if (this.changeHandler) this.cm.off("change", this.changeHandler);
|
||||||
|
this.div.parentNode.removeChild(this.div);
|
||||||
|
};
|
||||||
|
});
|
8
src/lib/codemirror/addon/search/matchesonscrollbar.css
vendored
Normal file
8
src/lib/codemirror/addon/search/matchesonscrollbar.css
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.CodeMirror-search-match {
|
||||||
|
background: gold;
|
||||||
|
border-top: 1px solid orange;
|
||||||
|
border-bottom: 1px solid orange;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
97
src/lib/codemirror/addon/search/matchesonscrollbar.js
vendored
Normal file
97
src/lib/codemirror/addon/search/matchesonscrollbar.js
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) {
|
||||||
|
if (typeof options == "string") options = {className: options};
|
||||||
|
if (!options) options = {};
|
||||||
|
return new SearchAnnotation(this, query, caseFold, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
function SearchAnnotation(cm, query, caseFold, options) {
|
||||||
|
this.cm = cm;
|
||||||
|
this.options = options;
|
||||||
|
var annotateOptions = {listenForChanges: false};
|
||||||
|
for (var prop in options) annotateOptions[prop] = options[prop];
|
||||||
|
if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
|
||||||
|
this.annotation = cm.annotateScrollbar(annotateOptions);
|
||||||
|
this.query = query;
|
||||||
|
this.caseFold = caseFold;
|
||||||
|
this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};
|
||||||
|
this.matches = [];
|
||||||
|
this.update = null;
|
||||||
|
|
||||||
|
this.findMatches();
|
||||||
|
this.annotation.update(this.matches);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); });
|
||||||
|
}
|
||||||
|
|
||||||
|
var MAX_MATCHES = 1000;
|
||||||
|
|
||||||
|
SearchAnnotation.prototype.findMatches = function() {
|
||||||
|
if (!this.gap) return;
|
||||||
|
for (var i = 0; i < this.matches.length; i++) {
|
||||||
|
var match = this.matches[i];
|
||||||
|
if (match.from.line >= this.gap.to) break;
|
||||||
|
if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
|
||||||
|
}
|
||||||
|
var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), {caseFold: this.caseFold, multiline: this.options.multiline});
|
||||||
|
var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES;
|
||||||
|
while (cursor.findNext()) {
|
||||||
|
var match = {from: cursor.from(), to: cursor.to()};
|
||||||
|
if (match.from.line >= this.gap.to) break;
|
||||||
|
this.matches.splice(i++, 0, match);
|
||||||
|
if (this.matches.length > maxMatches) break;
|
||||||
|
}
|
||||||
|
this.gap = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function offsetLine(line, changeStart, sizeChange) {
|
||||||
|
if (line <= changeStart) return line;
|
||||||
|
return Math.max(changeStart, line + sizeChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchAnnotation.prototype.onChange = function(change) {
|
||||||
|
var startLine = change.from.line;
|
||||||
|
var endLine = CodeMirror.changeEnd(change).line;
|
||||||
|
var sizeChange = endLine - change.to.line;
|
||||||
|
if (this.gap) {
|
||||||
|
this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);
|
||||||
|
this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);
|
||||||
|
} else {
|
||||||
|
this.gap = {from: change.from.line, to: endLine + 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeChange) for (var i = 0; i < this.matches.length; i++) {
|
||||||
|
var match = this.matches[i];
|
||||||
|
var newFrom = offsetLine(match.from.line, startLine, sizeChange);
|
||||||
|
if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);
|
||||||
|
var newTo = offsetLine(match.to.line, startLine, sizeChange);
|
||||||
|
if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);
|
||||||
|
}
|
||||||
|
clearTimeout(this.update);
|
||||||
|
var self = this;
|
||||||
|
this.update = setTimeout(function() { self.updateAfterChange(); }, 250);
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchAnnotation.prototype.updateAfterChange = function() {
|
||||||
|
this.findMatches();
|
||||||
|
this.annotation.update(this.matches);
|
||||||
|
};
|
||||||
|
|
||||||
|
SearchAnnotation.prototype.clear = function() {
|
||||||
|
this.cm.off("change", this.changeHandler);
|
||||||
|
this.annotation.clear();
|
||||||
|
};
|
||||||
|
});
|
293
src/lib/codemirror/addon/search/searchcursor.js
vendored
Normal file
293
src/lib/codemirror/addon/search/searchcursor.js
vendored
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"))
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod)
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror)
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict"
|
||||||
|
var Pos = CodeMirror.Pos
|
||||||
|
|
||||||
|
function regexpFlags(regexp) {
|
||||||
|
var flags = regexp.flags
|
||||||
|
return flags != null ? flags : (regexp.ignoreCase ? "i" : "")
|
||||||
|
+ (regexp.global ? "g" : "")
|
||||||
|
+ (regexp.multiline ? "m" : "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureFlags(regexp, flags) {
|
||||||
|
var current = regexpFlags(regexp), target = current
|
||||||
|
for (var i = 0; i < flags.length; i++) if (target.indexOf(flags.charAt(i)) == -1)
|
||||||
|
target += flags.charAt(i)
|
||||||
|
return current == target ? regexp : new RegExp(regexp.source, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeMultiline(regexp) {
|
||||||
|
return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRegexpForward(doc, regexp, start) {
|
||||||
|
regexp = ensureFlags(regexp, "g")
|
||||||
|
for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) {
|
||||||
|
regexp.lastIndex = ch
|
||||||
|
var string = doc.getLine(line), match = regexp.exec(string)
|
||||||
|
if (match)
|
||||||
|
return {from: Pos(line, match.index),
|
||||||
|
to: Pos(line, match.index + match[0].length),
|
||||||
|
match: match}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRegexpForwardMultiline(doc, regexp, start) {
|
||||||
|
if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start)
|
||||||
|
|
||||||
|
regexp = ensureFlags(regexp, "gm")
|
||||||
|
var string, chunk = 1
|
||||||
|
for (var line = start.line, last = doc.lastLine(); line <= last;) {
|
||||||
|
// This grows the search buffer in exponentially-sized chunks
|
||||||
|
// between matches, so that nearby matches are fast and don't
|
||||||
|
// require concatenating the whole document (in case we're
|
||||||
|
// searching for something that has tons of matches), but at the
|
||||||
|
// same time, the amount of retries is limited.
|
||||||
|
for (var i = 0; i < chunk; i++) {
|
||||||
|
if (line > last) break
|
||||||
|
var curLine = doc.getLine(line++)
|
||||||
|
string = string == null ? curLine : string + "\n" + curLine
|
||||||
|
}
|
||||||
|
chunk = chunk * 2
|
||||||
|
regexp.lastIndex = start.ch
|
||||||
|
var match = regexp.exec(string)
|
||||||
|
if (match) {
|
||||||
|
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
|
||||||
|
var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length
|
||||||
|
return {from: Pos(startLine, startCh),
|
||||||
|
to: Pos(startLine + inside.length - 1,
|
||||||
|
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
|
||||||
|
match: match}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function lastMatchIn(string, regexp) {
|
||||||
|
var cutOff = 0, match
|
||||||
|
for (;;) {
|
||||||
|
regexp.lastIndex = cutOff
|
||||||
|
var newMatch = regexp.exec(string)
|
||||||
|
if (!newMatch) return match
|
||||||
|
match = newMatch
|
||||||
|
cutOff = match.index + (match[0].length || 1)
|
||||||
|
if (cutOff == string.length) return match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRegexpBackward(doc, regexp, start) {
|
||||||
|
regexp = ensureFlags(regexp, "g")
|
||||||
|
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
|
||||||
|
var string = doc.getLine(line)
|
||||||
|
if (ch > -1) string = string.slice(0, ch)
|
||||||
|
var match = lastMatchIn(string, regexp)
|
||||||
|
if (match)
|
||||||
|
return {from: Pos(line, match.index),
|
||||||
|
to: Pos(line, match.index + match[0].length),
|
||||||
|
match: match}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchRegexpBackwardMultiline(doc, regexp, start) {
|
||||||
|
regexp = ensureFlags(regexp, "gm")
|
||||||
|
var string, chunk = 1
|
||||||
|
for (var line = start.line, first = doc.firstLine(); line >= first;) {
|
||||||
|
for (var i = 0; i < chunk; i++) {
|
||||||
|
var curLine = doc.getLine(line--)
|
||||||
|
string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
|
||||||
|
}
|
||||||
|
chunk *= 2
|
||||||
|
|
||||||
|
var match = lastMatchIn(string, regexp)
|
||||||
|
if (match) {
|
||||||
|
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
|
||||||
|
var startLine = line + before.length, startCh = before[before.length - 1].length
|
||||||
|
return {from: Pos(startLine, startCh),
|
||||||
|
to: Pos(startLine + inside.length - 1,
|
||||||
|
inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length),
|
||||||
|
match: match}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var doFold, noFold
|
||||||
|
if (String.prototype.normalize) {
|
||||||
|
doFold = function(str) { return str.normalize("NFD").toLowerCase() }
|
||||||
|
noFold = function(str) { return str.normalize("NFD") }
|
||||||
|
} else {
|
||||||
|
doFold = function(str) { return str.toLowerCase() }
|
||||||
|
noFold = function(str) { return str }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps a position in a case-folded line back to a position in the original line
|
||||||
|
// (compensating for codepoints increasing in number during folding)
|
||||||
|
function adjustPos(orig, folded, pos, foldFunc) {
|
||||||
|
if (orig.length == folded.length) return pos
|
||||||
|
for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) {
|
||||||
|
if (min == max) return min
|
||||||
|
var mid = (min + max) >> 1
|
||||||
|
var len = foldFunc(orig.slice(0, mid)).length
|
||||||
|
if (len == pos) return mid
|
||||||
|
else if (len > pos) max = mid
|
||||||
|
else min = mid + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchStringForward(doc, query, start, caseFold) {
|
||||||
|
// Empty string would match anything and never progress, so we
|
||||||
|
// define it to match nothing instead.
|
||||||
|
if (!query.length) return null
|
||||||
|
var fold = caseFold ? doFold : noFold
|
||||||
|
var lines = fold(query).split(/\r|\n\r?/)
|
||||||
|
|
||||||
|
search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) {
|
||||||
|
var orig = doc.getLine(line).slice(ch), string = fold(orig)
|
||||||
|
if (lines.length == 1) {
|
||||||
|
var found = string.indexOf(lines[0])
|
||||||
|
if (found == -1) continue search
|
||||||
|
var start = adjustPos(orig, string, found, fold) + ch
|
||||||
|
return {from: Pos(line, adjustPos(orig, string, found, fold) + ch),
|
||||||
|
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)}
|
||||||
|
} else {
|
||||||
|
var cutFrom = string.length - lines[0].length
|
||||||
|
if (string.slice(cutFrom) != lines[0]) continue search
|
||||||
|
for (var i = 1; i < lines.length - 1; i++)
|
||||||
|
if (fold(doc.getLine(line + i)) != lines[i]) continue search
|
||||||
|
var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1]
|
||||||
|
if (endString.slice(0, lastLine.length) != lastLine) continue search
|
||||||
|
return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch),
|
||||||
|
to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchStringBackward(doc, query, start, caseFold) {
|
||||||
|
if (!query.length) return null
|
||||||
|
var fold = caseFold ? doFold : noFold
|
||||||
|
var lines = fold(query).split(/\r|\n\r?/)
|
||||||
|
|
||||||
|
search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) {
|
||||||
|
var orig = doc.getLine(line)
|
||||||
|
if (ch > -1) orig = orig.slice(0, ch)
|
||||||
|
var string = fold(orig)
|
||||||
|
if (lines.length == 1) {
|
||||||
|
var found = string.lastIndexOf(lines[0])
|
||||||
|
if (found == -1) continue search
|
||||||
|
return {from: Pos(line, adjustPos(orig, string, found, fold)),
|
||||||
|
to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))}
|
||||||
|
} else {
|
||||||
|
var lastLine = lines[lines.length - 1]
|
||||||
|
if (string.slice(0, lastLine.length) != lastLine) continue search
|
||||||
|
for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++)
|
||||||
|
if (fold(doc.getLine(start + i)) != lines[i]) continue search
|
||||||
|
var top = doc.getLine(line + 1 - lines.length), topString = fold(top)
|
||||||
|
if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search
|
||||||
|
return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)),
|
||||||
|
to: Pos(line, adjustPos(orig, string, lastLine.length, fold))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function SearchCursor(doc, query, pos, options) {
|
||||||
|
this.atOccurrence = false
|
||||||
|
this.doc = doc
|
||||||
|
pos = pos ? doc.clipPos(pos) : Pos(0, 0)
|
||||||
|
this.pos = {from: pos, to: pos}
|
||||||
|
|
||||||
|
var caseFold
|
||||||
|
if (typeof options == "object") {
|
||||||
|
caseFold = options.caseFold
|
||||||
|
} else { // Backwards compat for when caseFold was the 4th argument
|
||||||
|
caseFold = options
|
||||||
|
options = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof query == "string") {
|
||||||
|
if (caseFold == null) caseFold = false
|
||||||
|
this.matches = function(reverse, pos) {
|
||||||
|
return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query = ensureFlags(query, "gm")
|
||||||
|
if (!options || options.multiline !== false)
|
||||||
|
this.matches = function(reverse, pos) {
|
||||||
|
return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.matches = function(reverse, pos) {
|
||||||
|
return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchCursor.prototype = {
|
||||||
|
findNext: function() {return this.find(false)},
|
||||||
|
findPrevious: function() {return this.find(true)},
|
||||||
|
|
||||||
|
find: function(reverse) {
|
||||||
|
var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to))
|
||||||
|
|
||||||
|
// Implements weird auto-growing behavior on null-matches for
|
||||||
|
// backwards-compatiblity with the vim code (unfortunately)
|
||||||
|
while (result && CodeMirror.cmpPos(result.from, result.to) == 0) {
|
||||||
|
if (reverse) {
|
||||||
|
if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1)
|
||||||
|
else if (result.from.line == this.doc.firstLine()) result = null
|
||||||
|
else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1)))
|
||||||
|
} else {
|
||||||
|
if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1)
|
||||||
|
else if (result.to.line == this.doc.lastLine()) result = null
|
||||||
|
else result = this.matches(reverse, Pos(result.to.line + 1, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
this.pos = result
|
||||||
|
this.atOccurrence = true
|
||||||
|
return this.pos.match || true
|
||||||
|
} else {
|
||||||
|
var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0)
|
||||||
|
this.pos = {from: end, to: end}
|
||||||
|
return this.atOccurrence = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
from: function() {if (this.atOccurrence) return this.pos.from},
|
||||||
|
to: function() {if (this.atOccurrence) return this.pos.to},
|
||||||
|
|
||||||
|
replace: function(newText, origin) {
|
||||||
|
if (!this.atOccurrence) return
|
||||||
|
var lines = CodeMirror.splitLines(newText)
|
||||||
|
this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin)
|
||||||
|
this.pos.to = Pos(this.pos.from.line + lines.length - 1,
|
||||||
|
lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
|
||||||
|
return new SearchCursor(this.doc, query, pos, caseFold)
|
||||||
|
})
|
||||||
|
CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
|
||||||
|
return new SearchCursor(this, query, pos, caseFold)
|
||||||
|
})
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
|
||||||
|
var ranges = []
|
||||||
|
var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold)
|
||||||
|
while (cur.findNext()) {
|
||||||
|
if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break
|
||||||
|
ranges.push({anchor: cur.from(), head: cur.to()})
|
||||||
|
}
|
||||||
|
if (ranges.length)
|
||||||
|
this.setSelections(ranges, 0)
|
||||||
|
})
|
||||||
|
});
|
18912
src/lib/codemirror/lib/codemirror.js
vendored
18912
src/lib/codemirror/lib/codemirror.js
vendored
File diff suppressed because it is too large
Load diff
52
src/lib/lz4/README.md
Normal file
52
src/lib/lz4/README.md
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
The purpose of this library is to implement LZ4 compression/decompression,
|
||||||
|
as documented at the official LZ4 repository:
|
||||||
|
|
||||||
|
https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
|
||||||
|
|
||||||
|
The files in this directory are developed as a separate project at:
|
||||||
|
|
||||||
|
https://github.com/gorhill/lz4-wasm
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
### `lz4-block-codec-any.js`
|
||||||
|
|
||||||
|
The purpose is to instanciate a WebAssembly- or pure javascript-based
|
||||||
|
LZ4 block codec.
|
||||||
|
|
||||||
|
If the choosen implementation is not specified, there will be an attempt to
|
||||||
|
create a WebAssembly-based instance. If for whatever reason this fails, a
|
||||||
|
pure javascript-based instance will be created.
|
||||||
|
|
||||||
|
The script for either instance are dynamically loaded and only when needed,
|
||||||
|
such that no resources are wasted by keeping in memory code which won't be
|
||||||
|
used.
|
||||||
|
|
||||||
|
### `lz4-block-codec-wasm.js`
|
||||||
|
|
||||||
|
This contains the code to instanciate WebAssembly-based LZ4 block codec. Note
|
||||||
|
that the WebAssembly module is loaded using a `same-origin` fetch, hence
|
||||||
|
ensuring that no code outside the package is loaded.
|
||||||
|
|
||||||
|
### `lz4-block-codec-js.js`
|
||||||
|
|
||||||
|
This contains the code to instanciate pure javascript-based LZ4 block codec.
|
||||||
|
|
||||||
|
This is used as a fallback implementation should WebAssembly not be available
|
||||||
|
for whatever reason.
|
||||||
|
|
||||||
|
### `lz4-block-codec.wasm`
|
||||||
|
|
||||||
|
This is the WebAssembly module, loaded by `lz4-block-codec-wasm.js` using a
|
||||||
|
`same-origin` fetch.
|
||||||
|
|
||||||
|
### `lz4-block-codec.wat`
|
||||||
|
|
||||||
|
The WebAssembly source code used to generate the WebAssembly module `lz4-block-codec.wasm`.
|
||||||
|
|
||||||
|
wat2wasm ./lz4-block-codec.wat -o ./lz4-block-codec.wasm
|
||||||
|
wasm-opt ./lz4-block-codec.wasm -O4 -o ./lz4-block-codec.wasm
|
||||||
|
|
||||||
|
You can get `wat2wasm` at <https://github.com/WebAssembly/wabt>, and `wasm-opt` at <https://github.com/WebAssembly/binaryen>.
|
151
src/lib/lz4/lz4-block-codec-any.js
Normal file
151
src/lib/lz4/lz4-block-codec-any.js
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
lz4-block-codec-any.js
|
||||||
|
A wrapper to instanciate a wasm- and/or js-based LZ4 block
|
||||||
|
encoder/decoder.
|
||||||
|
Copyright (C) 2018 Raymond Hill
|
||||||
|
|
||||||
|
BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/lz4-wasm
|
||||||
|
|
||||||
|
I used the same license as the one picked by creator of LZ4 out of respect
|
||||||
|
for his creation, see https://lz4.github.io/lz4/
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
(function(context) { // >>>> Start of private namespace
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const wd = (function() {
|
||||||
|
let url = document.currentScript.src;
|
||||||
|
let match = /[^\/]+$/.exec(url);
|
||||||
|
return match !== null ?
|
||||||
|
url.slice(0, match.index) :
|
||||||
|
'';
|
||||||
|
})();
|
||||||
|
|
||||||
|
const removeScript = function(script) {
|
||||||
|
if ( !script ) { return; }
|
||||||
|
if ( script.parentNode === null ) { return; }
|
||||||
|
script.parentNode.removeChild(script);
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInstanceWASM = function() {
|
||||||
|
if ( context.LZ4BlockWASM instanceof Function ) {
|
||||||
|
const instance = new context.LZ4BlockWASM();
|
||||||
|
return instance.init().then(ok => ok ? instance : null);
|
||||||
|
}
|
||||||
|
if ( context.LZ4BlockWASM === null ) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = wd + 'lz4-block-codec-wasm.js';
|
||||||
|
script.addEventListener('load', ( ) => {
|
||||||
|
if ( context.LZ4BlockWASM instanceof Function === false ) {
|
||||||
|
context.LZ4BlockWASM = null;
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const instance = new context.LZ4BlockWASM();
|
||||||
|
instance.init().then(ok => { resolve(ok ? instance : null); });
|
||||||
|
});
|
||||||
|
script.addEventListener('error', ( ) => {
|
||||||
|
context.LZ4BlockWASM = null;
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
document.head.appendChild(script);
|
||||||
|
removeScript(script);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createInstanceJS = function() {
|
||||||
|
if ( context.LZ4BlockJS instanceof Function ) {
|
||||||
|
const instance = new context.LZ4BlockJS();
|
||||||
|
return instance.init().then(ok => ok ? instance : null);
|
||||||
|
}
|
||||||
|
if ( context.LZ4BlockJS === null ) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = wd + 'lz4-block-codec-js.js';
|
||||||
|
script.addEventListener('load', ( ) => {
|
||||||
|
if ( context.LZ4BlockJS instanceof Function === false ) {
|
||||||
|
context.LZ4BlockJS = null;
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const instance = new context.LZ4BlockJS();
|
||||||
|
instance.init().then(ok => { resolve(ok ? instance : null); });
|
||||||
|
});
|
||||||
|
script.addEventListener('error', ( ) => {
|
||||||
|
context.LZ4BlockJS = null;
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
document.head.appendChild(script);
|
||||||
|
removeScript(script);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
context.lz4BlockCodec = {
|
||||||
|
createInstance: function(flavor) {
|
||||||
|
let instantiator;
|
||||||
|
if ( flavor === 'wasm' ) {
|
||||||
|
instantiator = createInstanceWASM;
|
||||||
|
} else if ( flavor === 'js' ) {
|
||||||
|
instantiator = createInstanceJS;
|
||||||
|
} else {
|
||||||
|
instantiator = createInstanceWASM || createInstanceJS;
|
||||||
|
}
|
||||||
|
return (instantiator)().then(instance => {
|
||||||
|
if ( instance ) { return instance; }
|
||||||
|
if ( flavor === undefined ) {
|
||||||
|
return createInstanceJS();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
reset: function() {
|
||||||
|
context.LZ4BlockWASM = undefined;
|
||||||
|
context.LZ4BlockJS = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
})(this || self); // <<<< End of private namespace
|
||||||
|
|
||||||
|
/******************************************************************************/
|
297
src/lib/lz4/lz4-block-codec-js.js
Normal file
297
src/lib/lz4/lz4-block-codec-js.js
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
lz4-block-codec-js.js
|
||||||
|
A javascript wrapper around a pure javascript implementation of
|
||||||
|
LZ4 block format codec.
|
||||||
|
Copyright (C) 2018 Raymond Hill
|
||||||
|
|
||||||
|
BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/lz4-wasm
|
||||||
|
|
||||||
|
I used the same license as the one picked by creator of LZ4 out of respect
|
||||||
|
for his creation, see https://lz4.github.io/lz4/
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
(function(context) { // >>>> Start of private namespace
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const growOutputBuffer = function(instance, size) {
|
||||||
|
if (
|
||||||
|
instance.outputBuffer === undefined ||
|
||||||
|
instance.outputBuffer.byteLength < size
|
||||||
|
) {
|
||||||
|
instance.outputBuffer = new ArrayBuffer(size + 0xFFFF & 0x7FFF0000);
|
||||||
|
}
|
||||||
|
return instance.outputBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodeBound = function(size) {
|
||||||
|
return size > 0x7E000000 ?
|
||||||
|
0 :
|
||||||
|
size + (size / 255 | 0) + 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodeBlock = function(instance, iBuf, oOffset) {
|
||||||
|
let iLen = iBuf.byteLength;
|
||||||
|
if ( iLen >= 0x7E000000 ) { throw new RangeError(); }
|
||||||
|
|
||||||
|
// "The last match must start at least 12 bytes before end of block"
|
||||||
|
let lastMatchPos = iLen - 12;
|
||||||
|
|
||||||
|
// "The last 5 bytes are always literals"
|
||||||
|
let lastLiteralPos = iLen - 5;
|
||||||
|
|
||||||
|
if ( instance.hashTable === undefined ) {
|
||||||
|
instance.hashTable = new Int32Array(65536);
|
||||||
|
}
|
||||||
|
instance.hashTable.fill(-65536);
|
||||||
|
|
||||||
|
if ( iBuf instanceof ArrayBuffer ) {
|
||||||
|
iBuf = new Uint8Array(iBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
let oLen = oOffset + encodeBound(iLen);
|
||||||
|
let oBuf = new Uint8Array(growOutputBuffer(instance, oLen), 0, oLen);
|
||||||
|
let iPos = 0;
|
||||||
|
let oPos = oOffset;
|
||||||
|
let anchorPos = 0;
|
||||||
|
|
||||||
|
// sequence-finding loop
|
||||||
|
for (;;) {
|
||||||
|
let refPos;
|
||||||
|
let mOffset;
|
||||||
|
let sequence = iBuf[iPos] << 8 | iBuf[iPos+1] << 16 | iBuf[iPos+2] << 24;
|
||||||
|
|
||||||
|
// match-finding loop
|
||||||
|
while ( iPos <= lastMatchPos ) {
|
||||||
|
sequence = sequence >>> 8 | iBuf[iPos+3] << 24;
|
||||||
|
let hash = (sequence * 0x9E37 & 0xFFFF) + (sequence * 0x79B1 >>> 16) & 0xFFFF;
|
||||||
|
refPos = instance.hashTable[hash];
|
||||||
|
instance.hashTable[hash] = iPos;
|
||||||
|
mOffset = iPos - refPos;
|
||||||
|
if (
|
||||||
|
mOffset < 65536 &&
|
||||||
|
iBuf[refPos+0] === ((sequence ) & 0xFF) &&
|
||||||
|
iBuf[refPos+1] === ((sequence >>> 8) & 0xFF) &&
|
||||||
|
iBuf[refPos+2] === ((sequence >>> 16) & 0xFF) &&
|
||||||
|
iBuf[refPos+3] === ((sequence >>> 24) & 0xFF)
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
iPos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no match found
|
||||||
|
if ( iPos > lastMatchPos ) { break; }
|
||||||
|
|
||||||
|
// match found
|
||||||
|
let lLen = iPos - anchorPos;
|
||||||
|
let mLen = iPos;
|
||||||
|
iPos += 4; refPos += 4;
|
||||||
|
while ( iPos < lastLiteralPos && iBuf[iPos] === iBuf[refPos] ) {
|
||||||
|
iPos += 1; refPos += 1;
|
||||||
|
}
|
||||||
|
mLen = iPos - mLen;
|
||||||
|
let token = mLen < 19 ? mLen - 4 : 15;
|
||||||
|
|
||||||
|
// write token, length of literals if needed
|
||||||
|
if ( lLen >= 15 ) {
|
||||||
|
oBuf[oPos++] = 0xF0 | token;
|
||||||
|
let l = lLen - 15;
|
||||||
|
while ( l >= 255 ) {
|
||||||
|
oBuf[oPos++] = 255;
|
||||||
|
l -= 255;
|
||||||
|
}
|
||||||
|
oBuf[oPos++] = l;
|
||||||
|
} else {
|
||||||
|
oBuf[oPos++] = (lLen << 4) | token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write literals
|
||||||
|
while ( lLen-- ) {
|
||||||
|
oBuf[oPos++] = iBuf[anchorPos++];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mLen === 0 ) { break; }
|
||||||
|
|
||||||
|
// write offset of match
|
||||||
|
oBuf[oPos+0] = mOffset;
|
||||||
|
oBuf[oPos+1] = mOffset >>> 8;
|
||||||
|
oPos += 2;
|
||||||
|
|
||||||
|
// write length of match if needed
|
||||||
|
if ( mLen >= 19 ) {
|
||||||
|
let l = mLen - 19;
|
||||||
|
while ( l >= 255 ) {
|
||||||
|
oBuf[oPos++] = 255;
|
||||||
|
l -= 255;
|
||||||
|
}
|
||||||
|
oBuf[oPos++] = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorPos = iPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// last sequence is literals only
|
||||||
|
let lLen = iLen - anchorPos;
|
||||||
|
if ( lLen >= 15 ) {
|
||||||
|
oBuf[oPos++] = 0xF0;
|
||||||
|
let l = lLen - 15;
|
||||||
|
while ( l >= 255 ) {
|
||||||
|
oBuf[oPos++] = 255;
|
||||||
|
l -= 255;
|
||||||
|
}
|
||||||
|
oBuf[oPos++] = l;
|
||||||
|
} else {
|
||||||
|
oBuf[oPos++] = lLen << 4;
|
||||||
|
}
|
||||||
|
while ( lLen-- ) {
|
||||||
|
oBuf[oPos++] = iBuf[anchorPos++];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(oBuf.buffer, 0, oPos);
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodeBlock = function(instance, iBuf, iOffset, oLen) {
|
||||||
|
let iLen = iBuf.byteLength;
|
||||||
|
let oBuf = new Uint8Array(growOutputBuffer(instance, oLen), 0, oLen);
|
||||||
|
let iPos = iOffset, oPos = 0;
|
||||||
|
|
||||||
|
while ( iPos < iLen ) {
|
||||||
|
let token = iBuf[iPos++];
|
||||||
|
|
||||||
|
// literals
|
||||||
|
let clen = token >>> 4;
|
||||||
|
|
||||||
|
// length of literals
|
||||||
|
if ( clen !== 0 ) {
|
||||||
|
if ( clen === 15 ) {
|
||||||
|
let l;
|
||||||
|
for (;;) {
|
||||||
|
l = iBuf[iPos++];
|
||||||
|
if ( l !== 255 ) { break; }
|
||||||
|
clen += 255;
|
||||||
|
}
|
||||||
|
clen += l;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy literals
|
||||||
|
let end = iPos + clen;
|
||||||
|
while ( iPos < end ) {
|
||||||
|
oBuf[oPos++] = iBuf[iPos++];
|
||||||
|
}
|
||||||
|
if ( iPos === iLen ) { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// match
|
||||||
|
let mOffset = iBuf[iPos+0] | (iBuf[iPos+1] << 8);
|
||||||
|
if ( mOffset === 0 || mOffset > oPos ) { return; }
|
||||||
|
iPos += 2;
|
||||||
|
|
||||||
|
// length of match
|
||||||
|
clen = (token & 0x0F) + 4;
|
||||||
|
if ( clen === 19 ) {
|
||||||
|
let l;
|
||||||
|
for (;;) {
|
||||||
|
l = iBuf[iPos++];
|
||||||
|
if ( l !== 255 ) { break; }
|
||||||
|
clen += 255;
|
||||||
|
}
|
||||||
|
clen += l;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy match
|
||||||
|
let mPos = oPos - mOffset;
|
||||||
|
let end = oPos + clen;
|
||||||
|
while ( oPos < end ) {
|
||||||
|
oBuf[oPos++] = oBuf[mPos++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return oBuf;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
context.LZ4BlockJS = function() {
|
||||||
|
this.hashTable = undefined;
|
||||||
|
this.outputBuffer = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
context.LZ4BlockJS.prototype = {
|
||||||
|
flavor: 'js',
|
||||||
|
init: function() {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
this.hashTable = undefined;
|
||||||
|
this.outputBuffer = undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
bytesInUse: function() {
|
||||||
|
let bytesInUse = 0;
|
||||||
|
if ( this.hashTable !== undefined ) {
|
||||||
|
bytesInUse += this.hashTable.byteLength;
|
||||||
|
}
|
||||||
|
if ( this.outputBuffer !== undefined ) {
|
||||||
|
bytesInUse += this.outputBuffer.byteLength;
|
||||||
|
}
|
||||||
|
return bytesInUse;
|
||||||
|
},
|
||||||
|
|
||||||
|
encodeBlock: function(input, outputOffset) {
|
||||||
|
if ( input instanceof ArrayBuffer ) {
|
||||||
|
input = new Uint8Array(input);
|
||||||
|
} else if ( input instanceof Uint8Array === false ) {
|
||||||
|
throw new TypeError();
|
||||||
|
}
|
||||||
|
return encodeBlock(this, input, outputOffset);
|
||||||
|
},
|
||||||
|
|
||||||
|
decodeBlock: function(input, inputOffset, outputSize) {
|
||||||
|
if ( input instanceof ArrayBuffer ) {
|
||||||
|
input = new Uint8Array(input);
|
||||||
|
} else if ( input instanceof Uint8Array === false ) {
|
||||||
|
throw new TypeError();
|
||||||
|
}
|
||||||
|
return decodeBlock(this, input, inputOffset, outputSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
})(this || self); // <<<< End of private namespace
|
||||||
|
|
||||||
|
/******************************************************************************/
|
195
src/lib/lz4/lz4-block-codec-wasm.js
Normal file
195
src/lib/lz4/lz4-block-codec-wasm.js
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
lz4-block-codec-wasm.js
|
||||||
|
A javascript wrapper around a WebAssembly implementation of
|
||||||
|
LZ4 block format codec.
|
||||||
|
Copyright (C) 2018 Raymond Hill
|
||||||
|
|
||||||
|
BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Home: https://github.com/gorhill/lz4-wasm
|
||||||
|
|
||||||
|
I used the same license as the one picked by creator of LZ4 out of respect
|
||||||
|
for his creation, see https://lz4.github.io/lz4/
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* global WebAssembly */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
(function(context) { // >>>> Start of private namespace
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const wd = (function() {
|
||||||
|
let url = document.currentScript.src;
|
||||||
|
let match = /[^\/]+$/.exec(url);
|
||||||
|
return match !== null ?
|
||||||
|
url.slice(0, match.index) :
|
||||||
|
'';
|
||||||
|
})();
|
||||||
|
|
||||||
|
const growMemoryTo = function(wasmInstance, byteLength) {
|
||||||
|
let lz4api = wasmInstance.exports;
|
||||||
|
let neededByteLength = lz4api.getLinearMemoryOffset() + byteLength;
|
||||||
|
let pageCountBefore = lz4api.memory.buffer.byteLength >>> 16;
|
||||||
|
let pageCountAfter = (neededByteLength + 65535) >>> 16;
|
||||||
|
if ( pageCountAfter > pageCountBefore ) {
|
||||||
|
lz4api.memory.grow(pageCountAfter - pageCountBefore);
|
||||||
|
}
|
||||||
|
return lz4api.memory.buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodeBlock = function(wasmInstance, inputArray, outputOffset) {
|
||||||
|
let lz4api = wasmInstance.exports;
|
||||||
|
let mem0 = lz4api.getLinearMemoryOffset();
|
||||||
|
let hashTableSize = 65536 * 4;
|
||||||
|
let inputSize = inputArray.byteLength;
|
||||||
|
if ( inputSize >= 0x7E000000 ) { throw new RangeError(); }
|
||||||
|
let memSize =
|
||||||
|
hashTableSize +
|
||||||
|
inputSize +
|
||||||
|
outputOffset + lz4api.lz4BlockEncodeBound(inputSize);
|
||||||
|
let memBuffer = growMemoryTo(wasmInstance, memSize);
|
||||||
|
let hashTable = new Int32Array(memBuffer, mem0, 65536);
|
||||||
|
hashTable.fill(-65536, 0, 65536);
|
||||||
|
let inputMem = new Uint8Array(memBuffer, mem0 + hashTableSize, inputSize);
|
||||||
|
inputMem.set(inputArray);
|
||||||
|
let outputSize = lz4api.lz4BlockEncode(
|
||||||
|
mem0 + hashTableSize,
|
||||||
|
inputSize,
|
||||||
|
mem0 + hashTableSize + inputSize + outputOffset
|
||||||
|
);
|
||||||
|
if ( outputSize === 0 ) { return; }
|
||||||
|
let outputArray = new Uint8Array(
|
||||||
|
memBuffer,
|
||||||
|
mem0 + hashTableSize + inputSize,
|
||||||
|
outputOffset + outputSize
|
||||||
|
);
|
||||||
|
return outputArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodeBlock = function(wasmInstance, inputArray, inputOffset, outputSize) {
|
||||||
|
let inputSize = inputArray.byteLength;
|
||||||
|
let lz4api = wasmInstance.exports;
|
||||||
|
let mem0 = lz4api.getLinearMemoryOffset();
|
||||||
|
let memSize = inputSize + outputSize;
|
||||||
|
let memBuffer = growMemoryTo(wasmInstance, memSize);
|
||||||
|
let inputArea = new Uint8Array(memBuffer, mem0, inputSize);
|
||||||
|
inputArea.set(inputArray);
|
||||||
|
outputSize = lz4api.lz4BlockDecode(
|
||||||
|
mem0 + inputOffset,
|
||||||
|
inputSize - inputOffset,
|
||||||
|
mem0 + inputSize
|
||||||
|
);
|
||||||
|
if ( outputSize === 0 ) { return; }
|
||||||
|
return new Uint8Array(memBuffer, mem0 + inputSize, outputSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
context.LZ4BlockWASM = function() {
|
||||||
|
this.lz4wasmInstance = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
context.LZ4BlockWASM.prototype = {
|
||||||
|
flavor: 'wasm',
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
if (
|
||||||
|
typeof WebAssembly !== 'object' ||
|
||||||
|
typeof WebAssembly.instantiateStreaming !== 'function'
|
||||||
|
) {
|
||||||
|
this.lz4wasmInstance = null;
|
||||||
|
}
|
||||||
|
if ( this.lz4wasmInstance === null ) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
if ( this.lz4wasmInstance instanceof WebAssembly.Instance ) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
if ( this.lz4wasmInstance === undefined ) {
|
||||||
|
this.lz4wasmInstance = fetch(
|
||||||
|
wd + 'lz4-block-codec.wasm',
|
||||||
|
{ mode: 'same-origin' }
|
||||||
|
).then(
|
||||||
|
WebAssembly.instantiateStreaming
|
||||||
|
).then(result => {
|
||||||
|
this.lz4wasmInstance = result && result.instance || null;
|
||||||
|
}).catch(reason => {
|
||||||
|
this.lz4wasmInstance = null;
|
||||||
|
console.info(reason);
|
||||||
|
}).then(( ) =>
|
||||||
|
this.lz4wasmInstance !== null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.lz4wasmInstance;
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
this.lz4wasmInstance = undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
bytesInUse: function() {
|
||||||
|
return this.lz4wasmInstance instanceof WebAssembly.Instance ?
|
||||||
|
this.lz4wasmInstance.exports.memory.buffer.byteLength :
|
||||||
|
0;
|
||||||
|
},
|
||||||
|
|
||||||
|
encodeBlock: function(input, outputOffset) {
|
||||||
|
if ( this.lz4wasmInstance instanceof WebAssembly.Instance === false ) {
|
||||||
|
throw new Error('LZ4BlockWASM: not initialized');
|
||||||
|
}
|
||||||
|
if ( input instanceof ArrayBuffer ) {
|
||||||
|
input = new Uint8Array(input);
|
||||||
|
} else if ( input instanceof Uint8Array === false ) {
|
||||||
|
throw new TypeError();
|
||||||
|
}
|
||||||
|
return encodeBlock(this.lz4wasmInstance, input, outputOffset);
|
||||||
|
},
|
||||||
|
|
||||||
|
decodeBlock: function(input, inputOffset, outputSize) {
|
||||||
|
if ( this.lz4wasmInstance instanceof WebAssembly.Instance === false ) {
|
||||||
|
throw new Error('LZ4BlockWASM: not initialized');
|
||||||
|
}
|
||||||
|
if ( input instanceof ArrayBuffer ) {
|
||||||
|
input = new Uint8Array(input);
|
||||||
|
} else if ( input instanceof Uint8Array === false ) {
|
||||||
|
throw new TypeError();
|
||||||
|
}
|
||||||
|
return decodeBlock(this.lz4wasmInstance, input, inputOffset, outputSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
})(this || self); // <<<< End of private namespace
|
||||||
|
|
||||||
|
/******************************************************************************/
|
BIN
src/lib/lz4/lz4-block-codec.wasm
Normal file
BIN
src/lib/lz4/lz4-block-codec.wasm
Normal file
Binary file not shown.
745
src/lib/lz4/lz4-block-codec.wat
Normal file
745
src/lib/lz4/lz4-block-codec.wat
Normal file
|
@ -0,0 +1,745 @@
|
||||||
|
;;
|
||||||
|
;; lz4-block-codec.wat: a WebAssembly implementation of LZ4 block format codec
|
||||||
|
;; Copyright (C) 2018 Raymond Hill
|
||||||
|
;;
|
||||||
|
;; BSD-2-Clause License (http://www.opensource.org/licenses/bsd-license.php)
|
||||||
|
;;
|
||||||
|
;; Redistribution and use in source and binary forms, with or without
|
||||||
|
;; modification, are permitted provided that the following conditions are
|
||||||
|
;; met:
|
||||||
|
;;
|
||||||
|
;; 1. Redistributions of source code must retain the above copyright
|
||||||
|
;; notice, this list of conditions and the following disclaimer.
|
||||||
|
;;
|
||||||
|
;; 2. Redistributions in binary form must reproduce the above
|
||||||
|
;; copyright notice, this list of conditions and the following disclaimer
|
||||||
|
;; in the documentation and/or other materials provided with the
|
||||||
|
;; distribution.
|
||||||
|
;;
|
||||||
|
;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
;;
|
||||||
|
;; Home: https://github.com/gorhill/lz4-wasm
|
||||||
|
;;
|
||||||
|
;; I used the same license as the one picked by creator of LZ4 out of respect
|
||||||
|
;; for his creation, see https://lz4.github.io/lz4/
|
||||||
|
;;
|
||||||
|
|
||||||
|
(module
|
||||||
|
;;
|
||||||
|
;; module start
|
||||||
|
;;
|
||||||
|
|
||||||
|
;; (func $log (import "imports" "log") (param i32 i32 i32))
|
||||||
|
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Public functions
|
||||||
|
;;
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Return an offset to the first byte of usable linear memory.
|
||||||
|
;; Might be useful in the future to reserve memory space for whatever purpose,
|
||||||
|
;; like config variables, etc.
|
||||||
|
;;
|
||||||
|
(func $getLinearMemoryOffset (export "getLinearMemoryOffset")
|
||||||
|
(result i32)
|
||||||
|
i32.const 0
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; unsigned int lz4BlockEncodeBound()
|
||||||
|
;;
|
||||||
|
;; Return the maximum size of the output buffer holding the compressed data.
|
||||||
|
;;
|
||||||
|
;; Reference implementation:
|
||||||
|
;; https://github.com/lz4/lz4/blob/dev/lib/lz4.h#L156
|
||||||
|
;;
|
||||||
|
(func (export "lz4BlockEncodeBound")
|
||||||
|
(param $ilen i32)
|
||||||
|
(result i32)
|
||||||
|
get_local $ilen
|
||||||
|
i32.const 0x7E000000
|
||||||
|
i32.gt_u
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
get_local $ilen
|
||||||
|
get_local $ilen
|
||||||
|
i32.const 255
|
||||||
|
i32.div_u
|
||||||
|
i32.add
|
||||||
|
i32.const 16
|
||||||
|
i32.add
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; unsigned int lz4BlockEncode(
|
||||||
|
;; unsigned int inPtr,
|
||||||
|
;; unsigned int ilen,
|
||||||
|
;; unsigned int outPtr
|
||||||
|
;; )
|
||||||
|
;;
|
||||||
|
;; https://github.com/lz4/lz4/blob/dev/lib/lz4.c#L651
|
||||||
|
;;
|
||||||
|
;; The implementation below is modified from the reference one.
|
||||||
|
;;
|
||||||
|
;; - There is no skip adjustement for repeated failure to find a match.
|
||||||
|
;;
|
||||||
|
;; - All configurable values are hard-coded to match the generic version
|
||||||
|
;; of the compressor.
|
||||||
|
;;
|
||||||
|
;; Note the size of the input block is NOT encoded in the output buffer, it
|
||||||
|
;; is for the caller to figure how they will save that information on
|
||||||
|
;; their side. At this point it is probably a trivial amount of work to
|
||||||
|
;; implement the LZ4 frame format, which encode the content size, but this
|
||||||
|
;; is for another day.
|
||||||
|
;;
|
||||||
|
(func $lz4BlockEncode (export "lz4BlockEncode")
|
||||||
|
(param $inPtr i32) ;; pointer to start of input buffer
|
||||||
|
(param $ilen i32) ;; size of input buffer
|
||||||
|
(param $outPtr i32) ;; pointer to start of output buffer
|
||||||
|
(result i32)
|
||||||
|
(local $hashPtrBeg i32) ;; start of hash buffer
|
||||||
|
(local $hashPtr i32) ;; current hash entry
|
||||||
|
(local $anchorPtr i32) ;; anchor position in input
|
||||||
|
(local $inPtrEnd1 i32) ;; point in input at which match-finding must cease
|
||||||
|
(local $inPtrEnd2 i32) ;; point in input at which match-length finding must cease
|
||||||
|
(local $inPtrEnd i32) ;; point to end of input
|
||||||
|
(local $outPtrBeg i32) ;; start of output buffer
|
||||||
|
(local $refPtr i32) ;; start of match in input
|
||||||
|
(local $seq32 i32) ;; 4-byte value from current input position
|
||||||
|
(local $llen i32) ;; length of found literals
|
||||||
|
(local $moffset i32) ;; offset to found match from current input position
|
||||||
|
(local $mlen i32) ;; length of found match
|
||||||
|
get_local $ilen ;; empty input = empty output
|
||||||
|
i32.const 0x7E000000 ;; max input size: 0x7E000000
|
||||||
|
i32.gt_u
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
get_local $ilen ;; "blocks < 13 bytes cannot be compressed"
|
||||||
|
i32.const 13
|
||||||
|
i32.lt_u
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
call $getLinearMemoryOffset ;; hash table is at start of usable memory
|
||||||
|
set_local $hashPtrBeg
|
||||||
|
get_local $inPtr
|
||||||
|
tee_local $anchorPtr
|
||||||
|
get_local $ilen
|
||||||
|
i32.add
|
||||||
|
tee_local $inPtrEnd
|
||||||
|
i32.const -5 ;; "The last 5 bytes are always literals."
|
||||||
|
i32.add
|
||||||
|
tee_local $inPtrEnd2
|
||||||
|
i32.const -7 ;; "The last match must start at least 12 bytes before end of block"
|
||||||
|
i32.add
|
||||||
|
set_local $inPtrEnd1
|
||||||
|
get_local $outPtr
|
||||||
|
set_local $outPtrBeg
|
||||||
|
;;
|
||||||
|
;; sequence processing loop
|
||||||
|
;;
|
||||||
|
block $noMoreSequence loop $nextSequence
|
||||||
|
get_local $inPtr
|
||||||
|
get_local $inPtrEnd1
|
||||||
|
i32.ge_u ;; 5 or less bytes left?
|
||||||
|
br_if $noMoreSequence
|
||||||
|
get_local $inPtr ;; first sequence of 3 bytes before match-finding loop
|
||||||
|
i32.load8_u
|
||||||
|
i32.const 8
|
||||||
|
i32.shl
|
||||||
|
get_local $inPtr
|
||||||
|
i32.load8_u offset=1
|
||||||
|
i32.const 16
|
||||||
|
i32.shl
|
||||||
|
i32.or
|
||||||
|
get_local $inPtr
|
||||||
|
i32.load8_u offset=2
|
||||||
|
i32.const 24
|
||||||
|
i32.shl
|
||||||
|
i32.or
|
||||||
|
set_local $seq32
|
||||||
|
;;
|
||||||
|
;; match-finding loop
|
||||||
|
;;
|
||||||
|
loop $findMatch block $noMatchFound
|
||||||
|
get_local $inPtr
|
||||||
|
get_local $inPtrEnd2
|
||||||
|
i32.gt_u ;; less than 12 bytes left?
|
||||||
|
br_if $noMoreSequence
|
||||||
|
get_local $seq32 ;; update last byte of current sequence
|
||||||
|
i32.const 8
|
||||||
|
i32.shr_u
|
||||||
|
get_local $inPtr
|
||||||
|
i32.load8_u offset=3
|
||||||
|
i32.const 24
|
||||||
|
i32.shl
|
||||||
|
i32.or
|
||||||
|
tee_local $seq32
|
||||||
|
i32.const 0x9E3779B1 ;; compute 16-bit hash
|
||||||
|
i32.mul
|
||||||
|
i32.const 16
|
||||||
|
i32.shr_u ;; hash value is at top of stack
|
||||||
|
i32.const 2 ;; lookup refPtr at hash entry
|
||||||
|
i32.shl
|
||||||
|
get_local $hashPtrBeg
|
||||||
|
i32.add
|
||||||
|
tee_local $hashPtr
|
||||||
|
i32.load
|
||||||
|
set_local $refPtr
|
||||||
|
get_local $hashPtr ;; update hash entry with inPtr
|
||||||
|
get_local $inPtr
|
||||||
|
i32.store
|
||||||
|
get_local $inPtr
|
||||||
|
get_local $refPtr
|
||||||
|
i32.sub
|
||||||
|
tee_local $moffset ;; remember match offset, we will need it in case of match
|
||||||
|
i32.const 0xFFFF
|
||||||
|
i32.gt_s ;; match offset > 65535 = unusable match
|
||||||
|
br_if $noMatchFound
|
||||||
|
;;
|
||||||
|
;; confirm match: different sequences can yield same hash
|
||||||
|
;; compare-branch each byte to potentially save memory read ops
|
||||||
|
;;
|
||||||
|
get_local $seq32 ;; byte 0
|
||||||
|
i32.const 0xFF
|
||||||
|
i32.and
|
||||||
|
get_local $refPtr
|
||||||
|
i32.load8_u
|
||||||
|
i32.ne ;; refPtr[0] !== inPtr[0]
|
||||||
|
br_if $noMatchFound
|
||||||
|
get_local $seq32 ;; byte 1
|
||||||
|
i32.const 8
|
||||||
|
i32.shr_u
|
||||||
|
i32.const 0xFF
|
||||||
|
i32.and
|
||||||
|
get_local $refPtr
|
||||||
|
i32.load8_u offset=1
|
||||||
|
i32.ne
|
||||||
|
br_if $noMatchFound ;; refPtr[1] !== inPtr[1]
|
||||||
|
get_local $seq32 ;; byte 2
|
||||||
|
i32.const 16
|
||||||
|
i32.shr_u
|
||||||
|
i32.const 0xFF
|
||||||
|
i32.and
|
||||||
|
get_local $refPtr
|
||||||
|
i32.load8_u offset=2
|
||||||
|
i32.ne ;; refPtr[2] !== inPtr[2]
|
||||||
|
br_if $noMatchFound
|
||||||
|
get_local $seq32 ;; byte 3
|
||||||
|
i32.const 24
|
||||||
|
i32.shr_u
|
||||||
|
i32.const 0xFF
|
||||||
|
i32.and
|
||||||
|
get_local $refPtr
|
||||||
|
i32.load8_u offset=3
|
||||||
|
i32.ne ;; refPtr[3] !== inPtr[3]
|
||||||
|
br_if $noMatchFound
|
||||||
|
;;
|
||||||
|
;; a valid match has been found at this point
|
||||||
|
;;
|
||||||
|
get_local $inPtr ;; compute length of literals
|
||||||
|
get_local $anchorPtr
|
||||||
|
i32.sub
|
||||||
|
set_local $llen
|
||||||
|
get_local $inPtr ;; find match length
|
||||||
|
i32.const 4 ;; skip over confirmed 4-byte match
|
||||||
|
i32.add
|
||||||
|
set_local $inPtr
|
||||||
|
get_local $refPtr
|
||||||
|
i32.const 4
|
||||||
|
i32.add
|
||||||
|
tee_local $mlen ;; remember refPtr to later compute match length
|
||||||
|
set_local $refPtr
|
||||||
|
block $endOfMatch loop ;; scan input buffer until match ends
|
||||||
|
get_local $inPtr
|
||||||
|
get_local $inPtrEnd2
|
||||||
|
i32.ge_u
|
||||||
|
br_if $endOfMatch
|
||||||
|
get_local $inPtr
|
||||||
|
i32.load8_u
|
||||||
|
get_local $refPtr
|
||||||
|
i32.load8_u
|
||||||
|
i32.ne
|
||||||
|
br_if $endOfMatch
|
||||||
|
get_local $inPtr
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $inPtr
|
||||||
|
get_local $refPtr
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $refPtr
|
||||||
|
br 0
|
||||||
|
end end $endOfMatch
|
||||||
|
;; encode token
|
||||||
|
get_local $outPtr ;; output token
|
||||||
|
get_local $llen
|
||||||
|
get_local $refPtr
|
||||||
|
get_local $mlen
|
||||||
|
i32.sub
|
||||||
|
tee_local $mlen
|
||||||
|
call $writeToken
|
||||||
|
get_local $outPtr
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $outPtr
|
||||||
|
get_local $llen ;; encode/write length of literals if needed
|
||||||
|
i32.const 15
|
||||||
|
i32.ge_s
|
||||||
|
if
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $llen
|
||||||
|
call $writeLength
|
||||||
|
set_local $outPtr
|
||||||
|
end
|
||||||
|
;; copy literals
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $anchorPtr
|
||||||
|
get_local $llen
|
||||||
|
call $copy
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $llen
|
||||||
|
i32.add
|
||||||
|
set_local $outPtr
|
||||||
|
;; encode match offset
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $moffset
|
||||||
|
i32.store8
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $moffset
|
||||||
|
i32.const 8
|
||||||
|
i32.shr_u
|
||||||
|
i32.store8 offset=1
|
||||||
|
get_local $outPtr
|
||||||
|
i32.const 2
|
||||||
|
i32.add
|
||||||
|
set_local $outPtr
|
||||||
|
get_local $mlen ;; encode/write length of match if needed
|
||||||
|
i32.const 15
|
||||||
|
i32.ge_s
|
||||||
|
if
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $mlen
|
||||||
|
call $writeLength
|
||||||
|
set_local $outPtr
|
||||||
|
end
|
||||||
|
get_local $inPtr ;; advance anchor to current position
|
||||||
|
set_local $anchorPtr
|
||||||
|
br $nextSequence
|
||||||
|
end $noMatchFound
|
||||||
|
get_local $inPtr ;; no match found: advance to next byte
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $inPtr
|
||||||
|
br $findMatch end ;; match offset > 65535 = unusable match
|
||||||
|
end end $noMoreSequence
|
||||||
|
;;
|
||||||
|
;; generate last (match-less) sequence if compression succeeded
|
||||||
|
;;
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $outPtrBeg
|
||||||
|
i32.eq
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $inPtrEnd
|
||||||
|
get_local $anchorPtr
|
||||||
|
i32.sub
|
||||||
|
tee_local $llen
|
||||||
|
i32.const 0
|
||||||
|
call $writeToken
|
||||||
|
get_local $outPtr
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $outPtr
|
||||||
|
get_local $llen
|
||||||
|
i32.const 15
|
||||||
|
i32.ge_u
|
||||||
|
if
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $llen
|
||||||
|
call $writeLength
|
||||||
|
set_local $outPtr
|
||||||
|
end
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $anchorPtr
|
||||||
|
get_local $llen
|
||||||
|
call $copy
|
||||||
|
get_local $outPtr ;; return number of written bytes
|
||||||
|
get_local $llen
|
||||||
|
i32.add
|
||||||
|
get_local $outPtrBeg
|
||||||
|
i32.sub
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; unsigned int lz4BlockDecode(
|
||||||
|
;; unsigned int inPtr,
|
||||||
|
;; unsigned int ilen
|
||||||
|
;; unsigned int outPtr
|
||||||
|
;; )
|
||||||
|
;;
|
||||||
|
;; Reference:
|
||||||
|
;; https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
|
||||||
|
;;
|
||||||
|
(func (export "lz4BlockDecode")
|
||||||
|
(param $inPtr0 i32) ;; start of input buffer
|
||||||
|
(param $ilen i32) ;; length of input buffer
|
||||||
|
(param $outPtr0 i32) ;; start of output buffer
|
||||||
|
(result i32)
|
||||||
|
(local $inPtr i32) ;; current position in input buffer
|
||||||
|
(local $inPtrEnd i32) ;; end of input buffer
|
||||||
|
(local $outPtr i32) ;; current position in output buffer
|
||||||
|
(local $matchPtr i32) ;; position of current match
|
||||||
|
(local $token i32) ;; sequence token
|
||||||
|
(local $clen i32) ;; number of bytes to copy
|
||||||
|
(local $_ i32) ;; general purpose variable
|
||||||
|
get_local $ilen ;; if ( ilen == 0 ) { return 0; }
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
i32.const 0
|
||||||
|
return
|
||||||
|
end
|
||||||
|
get_local $inPtr0
|
||||||
|
tee_local $inPtr ;; current position in input buffer
|
||||||
|
get_local $ilen
|
||||||
|
i32.add
|
||||||
|
set_local $inPtrEnd
|
||||||
|
get_local $outPtr0 ;; start of output buffer
|
||||||
|
set_local $outPtr ;; current position in output buffer
|
||||||
|
block $noMoreSequence loop ;; iterate through all sequences
|
||||||
|
get_local $inPtr
|
||||||
|
get_local $inPtrEnd
|
||||||
|
i32.ge_u
|
||||||
|
br_if $noMoreSequence ;; break when nothing left to read in input buffer
|
||||||
|
get_local $inPtr ;; read token -- consume one byte
|
||||||
|
i32.load8_u
|
||||||
|
get_local $inPtr
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $inPtr
|
||||||
|
tee_local $token ;; extract length of literals from token
|
||||||
|
i32.const 4
|
||||||
|
i32.shr_u
|
||||||
|
tee_local $clen ;; consume extra length bytes if present
|
||||||
|
i32.eqz
|
||||||
|
if else
|
||||||
|
get_local $clen
|
||||||
|
i32.const 15
|
||||||
|
i32.eq
|
||||||
|
if loop
|
||||||
|
get_local $inPtr
|
||||||
|
i32.load8_u
|
||||||
|
get_local $inPtr
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $inPtr
|
||||||
|
tee_local $_
|
||||||
|
get_local $clen
|
||||||
|
i32.add
|
||||||
|
set_local $clen
|
||||||
|
get_local $_
|
||||||
|
i32.const 255
|
||||||
|
i32.eq
|
||||||
|
br_if 0
|
||||||
|
end end
|
||||||
|
get_local $outPtr ;; copy literals to ouput buffer
|
||||||
|
get_local $inPtr
|
||||||
|
get_local $clen
|
||||||
|
call $copy
|
||||||
|
get_local $outPtr ;; advance output buffer pointer past copy
|
||||||
|
get_local $clen
|
||||||
|
i32.add
|
||||||
|
set_local $outPtr
|
||||||
|
get_local $clen ;; advance input buffer pointer past literals
|
||||||
|
get_local $inPtr
|
||||||
|
i32.add
|
||||||
|
tee_local $inPtr
|
||||||
|
get_local $inPtrEnd ;; exit if this is the last sequence
|
||||||
|
i32.eq
|
||||||
|
br_if $noMoreSequence
|
||||||
|
end
|
||||||
|
get_local $outPtr ;; read match offset
|
||||||
|
get_local $inPtr
|
||||||
|
i32.load8_u
|
||||||
|
get_local $inPtr
|
||||||
|
i32.load8_u offset=1
|
||||||
|
i32.const 8
|
||||||
|
i32.shl
|
||||||
|
i32.or
|
||||||
|
i32.sub
|
||||||
|
tee_local $matchPtr
|
||||||
|
get_local $outPtr ;; match position can't be outside input buffer bounds
|
||||||
|
i32.eq
|
||||||
|
br_if $noMoreSequence
|
||||||
|
get_local $matchPtr
|
||||||
|
get_local $inPtrEnd
|
||||||
|
i32.lt_u
|
||||||
|
br_if $noMoreSequence
|
||||||
|
get_local $inPtr ;; advance input pointer past match offset bytes
|
||||||
|
i32.const 2
|
||||||
|
i32.add
|
||||||
|
set_local $inPtr
|
||||||
|
get_local $token ;; extract length of match from token
|
||||||
|
i32.const 15
|
||||||
|
i32.and
|
||||||
|
i32.const 4
|
||||||
|
i32.add
|
||||||
|
tee_local $clen
|
||||||
|
i32.const 19 ;; consume extra length bytes if present
|
||||||
|
i32.eq
|
||||||
|
if loop
|
||||||
|
get_local $inPtr
|
||||||
|
i32.load8_u
|
||||||
|
get_local $inPtr
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $inPtr
|
||||||
|
tee_local $_
|
||||||
|
get_local $clen
|
||||||
|
i32.add
|
||||||
|
set_local $clen
|
||||||
|
get_local $_
|
||||||
|
i32.const 255
|
||||||
|
i32.eq
|
||||||
|
br_if 0
|
||||||
|
end end
|
||||||
|
get_local $outPtr ;; copy match to ouput buffer
|
||||||
|
get_local $matchPtr
|
||||||
|
get_local $clen
|
||||||
|
call $copy
|
||||||
|
get_local $clen ;; advance output buffer pointer past copy
|
||||||
|
get_local $outPtr
|
||||||
|
i32.add
|
||||||
|
set_local $outPtr
|
||||||
|
br 0
|
||||||
|
end end $noMoreSequence
|
||||||
|
get_local $outPtr ;; return number of written bytes
|
||||||
|
get_local $outPtr0
|
||||||
|
i32.sub
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Private functions
|
||||||
|
;;
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Encode a sequence token
|
||||||
|
;;
|
||||||
|
;; Reference documentation:
|
||||||
|
;; https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
|
||||||
|
;;
|
||||||
|
(func $writeToken
|
||||||
|
(param $outPtr i32)
|
||||||
|
(param $llen i32)
|
||||||
|
(param $mlen i32)
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $llen
|
||||||
|
i32.const 15
|
||||||
|
get_local $llen
|
||||||
|
i32.const 15
|
||||||
|
i32.lt_u
|
||||||
|
select
|
||||||
|
i32.const 4
|
||||||
|
i32.shl
|
||||||
|
get_local $mlen
|
||||||
|
i32.const 15
|
||||||
|
get_local $mlen
|
||||||
|
i32.const 15
|
||||||
|
i32.lt_u
|
||||||
|
select
|
||||||
|
i32.or
|
||||||
|
i32.store8
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Encode and output length bytes. The return value is the pointer following
|
||||||
|
;; the last byte written.
|
||||||
|
;;
|
||||||
|
;; Reference documentation:
|
||||||
|
;; https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md
|
||||||
|
;;
|
||||||
|
(func $writeLength
|
||||||
|
(param $outPtr i32)
|
||||||
|
(param $len i32)
|
||||||
|
(result i32)
|
||||||
|
get_local $len
|
||||||
|
i32.const 15
|
||||||
|
i32.sub
|
||||||
|
set_local $len
|
||||||
|
loop
|
||||||
|
get_local $outPtr
|
||||||
|
get_local $len
|
||||||
|
i32.const 255
|
||||||
|
get_local $len
|
||||||
|
i32.const 255
|
||||||
|
i32.lt_u
|
||||||
|
select
|
||||||
|
i32.store8
|
||||||
|
get_local $outPtr
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $outPtr
|
||||||
|
get_local $len
|
||||||
|
i32.const 255
|
||||||
|
i32.sub
|
||||||
|
tee_local $len
|
||||||
|
i32.const 0
|
||||||
|
i32.ge_s
|
||||||
|
br_if 0
|
||||||
|
end
|
||||||
|
get_local $outPtr
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Copy n bytes from source to destination.
|
||||||
|
;;
|
||||||
|
;; It is overlap-safe only from left-to-right -- which is only what is
|
||||||
|
;; required in the current module.
|
||||||
|
;;
|
||||||
|
(func $copy
|
||||||
|
(param $dst i32)
|
||||||
|
(param $src i32)
|
||||||
|
(param $len i32)
|
||||||
|
block $lessThan8 loop
|
||||||
|
get_local $len
|
||||||
|
i32.const 8
|
||||||
|
i32.lt_u
|
||||||
|
br_if $lessThan8
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u
|
||||||
|
i32.store8
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=1
|
||||||
|
i32.store8 offset=1
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=2
|
||||||
|
i32.store8 offset=2
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=3
|
||||||
|
i32.store8 offset=3
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=4
|
||||||
|
i32.store8 offset=4
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=5
|
||||||
|
i32.store8 offset=5
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=6
|
||||||
|
i32.store8 offset=6
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=7
|
||||||
|
i32.store8 offset=7
|
||||||
|
get_local $dst
|
||||||
|
i32.const 8
|
||||||
|
i32.add
|
||||||
|
set_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.const 8
|
||||||
|
i32.add
|
||||||
|
set_local $src
|
||||||
|
get_local $len
|
||||||
|
i32.const -8
|
||||||
|
i32.add
|
||||||
|
set_local $len
|
||||||
|
br 0
|
||||||
|
end end $lessThan8
|
||||||
|
get_local $len
|
||||||
|
i32.const 4
|
||||||
|
i32.ge_u
|
||||||
|
if
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u
|
||||||
|
i32.store8
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=1
|
||||||
|
i32.store8 offset=1
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=2
|
||||||
|
i32.store8 offset=2
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=3
|
||||||
|
i32.store8 offset=3
|
||||||
|
get_local $dst
|
||||||
|
i32.const 4
|
||||||
|
i32.add
|
||||||
|
set_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.const 4
|
||||||
|
i32.add
|
||||||
|
set_local $src
|
||||||
|
get_local $len
|
||||||
|
i32.const -4
|
||||||
|
i32.add
|
||||||
|
set_local $len
|
||||||
|
end
|
||||||
|
get_local $len
|
||||||
|
i32.const 2
|
||||||
|
i32.ge_u
|
||||||
|
if
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u
|
||||||
|
i32.store8
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u offset=1
|
||||||
|
i32.store8 offset=1
|
||||||
|
get_local $dst
|
||||||
|
i32.const 2
|
||||||
|
i32.add
|
||||||
|
set_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.const 2
|
||||||
|
i32.add
|
||||||
|
set_local $src
|
||||||
|
get_local $len
|
||||||
|
i32.const -2
|
||||||
|
i32.add
|
||||||
|
set_local $len
|
||||||
|
end
|
||||||
|
get_local $len
|
||||||
|
i32.eqz
|
||||||
|
if else
|
||||||
|
get_local $dst
|
||||||
|
get_local $src
|
||||||
|
i32.load8_u
|
||||||
|
i32.store8
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; module end
|
||||||
|
;;
|
||||||
|
)
|
|
@ -1,343 +0,0 @@
|
||||||
/*******************************************************************************
|
|
||||||
|
|
||||||
publicsuffixlist.js - an efficient javascript implementation to deal with
|
|
||||||
Mozilla Foundation's Public Suffix List <http://publicsuffix.org/list/>
|
|
||||||
Copyright (C) 2013 Raymond Hill
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*! Home: https://github.com/gorhill/publicsuffixlist.js */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*
|
|
||||||
This code is mostly dumb: I consider this to be lower-level code, thus
|
|
||||||
in order to ensure efficiency, the caller is responsible for sanitizing
|
|
||||||
the inputs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// A single instance of PublicSuffixList is enough.
|
|
||||||
|
|
||||||
;(function(root) {
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
var exceptions = new Map();
|
|
||||||
var rules = new Map();
|
|
||||||
|
|
||||||
// This value dictate how the search will be performed:
|
|
||||||
// < this.cutoffLength = indexOf()
|
|
||||||
// >= this.cutoffLength = binary search
|
|
||||||
var cutoffLength = 256;
|
|
||||||
var mustPunycode = /[^\w.*-]/;
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// In the context of this code, a domain is defined as:
|
|
||||||
// "{label}.{public suffix}".
|
|
||||||
// A single standalone label is a public suffix as per
|
|
||||||
// http://publicsuffix.org/list/:
|
|
||||||
// "If no rules match, the prevailing rule is '*' "
|
|
||||||
// This means 'localhost' is not deemed a domain by this
|
|
||||||
// code, since according to the definition above, it would be
|
|
||||||
// evaluated as a public suffix. The caller is therefore responsible to
|
|
||||||
// decide how to further interpret such public suffix.
|
|
||||||
//
|
|
||||||
// `hostname` must be a valid ascii-based hostname.
|
|
||||||
|
|
||||||
function getDomain(hostname) {
|
|
||||||
// A hostname starting with a dot is not a valid hostname.
|
|
||||||
if ( !hostname || hostname.charAt(0) === '.' ) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
hostname = hostname.toLowerCase();
|
|
||||||
var suffix = getPublicSuffix(hostname);
|
|
||||||
if ( suffix === hostname ) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
var pos = hostname.lastIndexOf('.', hostname.lastIndexOf('.', hostname.length - suffix.length) - 1);
|
|
||||||
if ( pos <= 0 ) {
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
return hostname.slice(pos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Return longest public suffix.
|
|
||||||
//
|
|
||||||
// `hostname` must be a valid ascii-based string which respect hostname naming.
|
|
||||||
|
|
||||||
function getPublicSuffix(hostname) {
|
|
||||||
if ( !hostname ) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
// Since we slice down the hostname with each pass, the first match
|
|
||||||
// is the longest, so no need to find all the matching rules.
|
|
||||||
while ( true ) {
|
|
||||||
let pos = hostname.indexOf('.');
|
|
||||||
if ( pos < 0 ) {
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
if ( search(exceptions, hostname) ) {
|
|
||||||
return hostname.slice(pos + 1);
|
|
||||||
}
|
|
||||||
if ( search(rules, hostname) ) {
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
if ( search(rules, '*' + hostname.slice(pos)) ) {
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
hostname = hostname.slice(pos + 1);
|
|
||||||
}
|
|
||||||
// unreachable
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Look up a specific hostname.
|
|
||||||
|
|
||||||
function search(store, hostname) {
|
|
||||||
// Extract TLD
|
|
||||||
let tld, remainder;
|
|
||||||
let pos = hostname.lastIndexOf('.');
|
|
||||||
if ( pos === -1 ) {
|
|
||||||
tld = hostname;
|
|
||||||
remainder = hostname;
|
|
||||||
} else {
|
|
||||||
tld = hostname.slice(pos + 1);
|
|
||||||
remainder = hostname.slice(0, pos);
|
|
||||||
}
|
|
||||||
let substore = store.get(tld);
|
|
||||||
if ( substore === undefined ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If substore is a string, use indexOf()
|
|
||||||
if ( typeof substore === 'string' ) {
|
|
||||||
return substore.indexOf(' ' + remainder + ' ') >= 0;
|
|
||||||
}
|
|
||||||
// It is an array: use binary search.
|
|
||||||
let l = remainder.length;
|
|
||||||
if ( l >= substore.length ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let haystack = substore[l];
|
|
||||||
if ( haystack === null ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let left = 0;
|
|
||||||
let right = Math.floor(haystack.length / l + 0.5);
|
|
||||||
while ( left < right ) {
|
|
||||||
let i = left + right >> 1;
|
|
||||||
let needle = haystack.substr(l*i, l);
|
|
||||||
if ( remainder < needle ) {
|
|
||||||
right = i;
|
|
||||||
} else if ( remainder > needle ) {
|
|
||||||
left = i + 1;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Parse and set a UTF-8 text-based suffix list. Format is same as found at:
|
|
||||||
// http://publicsuffix.org/list/
|
|
||||||
//
|
|
||||||
// `toAscii` is a converter from unicode to punycode. Required since the
|
|
||||||
// Public Suffix List contains unicode characters.
|
|
||||||
// Suggestion: use <https://github.com/bestiejs/punycode.js> it's quite good.
|
|
||||||
|
|
||||||
function parse(text, toAscii) {
|
|
||||||
exceptions = new Map();
|
|
||||||
rules = new Map();
|
|
||||||
|
|
||||||
let lineBeg = 0;
|
|
||||||
let textEnd = text.length;
|
|
||||||
|
|
||||||
while ( lineBeg < textEnd ) {
|
|
||||||
let lineEnd = text.indexOf('\n', lineBeg);
|
|
||||||
if ( lineEnd < 0 ) {
|
|
||||||
lineEnd = text.indexOf('\r', lineBeg);
|
|
||||||
if ( lineEnd < 0 ) {
|
|
||||||
lineEnd = textEnd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let line = text.slice(lineBeg, lineEnd).trim();
|
|
||||||
lineBeg = lineEnd + 1;
|
|
||||||
|
|
||||||
if ( line.length === 0 ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore comments
|
|
||||||
let pos = line.indexOf('//');
|
|
||||||
if ( pos !== -1 ) {
|
|
||||||
line = line.slice(0, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore surrounding whitespaces
|
|
||||||
line = line.trim();
|
|
||||||
if ( line.length === 0 ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this an exception rule?
|
|
||||||
let store;
|
|
||||||
if ( line.charAt(0) === '!' ) {
|
|
||||||
store = exceptions;
|
|
||||||
line = line.slice(1);
|
|
||||||
} else {
|
|
||||||
store = rules;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( mustPunycode.test(line) ) {
|
|
||||||
line = toAscii(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://publicsuffix.org/list/:
|
|
||||||
// "... all rules must be canonicalized in the normal way
|
|
||||||
// for hostnames - lower-case, Punycode ..."
|
|
||||||
line = line.toLowerCase();
|
|
||||||
|
|
||||||
// Extract TLD
|
|
||||||
let tld;
|
|
||||||
pos = line.lastIndexOf('.');
|
|
||||||
if ( pos === -1 ) {
|
|
||||||
tld = line;
|
|
||||||
} else {
|
|
||||||
tld = line.slice(pos + 1);
|
|
||||||
line = line.slice(0, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store suffix using tld as key
|
|
||||||
let substore = store.get(tld);
|
|
||||||
if ( substore === undefined ) {
|
|
||||||
store.set(tld, substore = []);
|
|
||||||
}
|
|
||||||
if ( line ) {
|
|
||||||
substore.push(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crystallize(exceptions);
|
|
||||||
crystallize(rules);
|
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('publicSuffixList'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Cristallize the storage of suffixes using optimal internal representation
|
|
||||||
// for future look up.
|
|
||||||
|
|
||||||
function crystallize(store) {
|
|
||||||
for ( let entry of store ) {
|
|
||||||
let tld = entry[0];
|
|
||||||
let suffixes = entry[1];
|
|
||||||
|
|
||||||
// No suffix
|
|
||||||
if ( suffixes.length === 0 ) {
|
|
||||||
store.set(tld, '');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenated list of suffixes less than cutoff length:
|
|
||||||
// Store as string, lookup using indexOf()
|
|
||||||
let s = suffixes.join(' ');
|
|
||||||
if ( s.length < cutoffLength ) {
|
|
||||||
store.set(tld, ' ' + s + ' ');
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenated list of suffixes greater or equal to cutoff length
|
|
||||||
// Store as array keyed on suffix length, lookup using binary search.
|
|
||||||
// I borrowed the idea to key on string length here:
|
|
||||||
// http://ejohn.org/blog/dictionary-lookups-in-javascript/#comment-392072
|
|
||||||
let buckets = [];
|
|
||||||
for ( let suffix of suffixes ) {
|
|
||||||
let l = suffix.length;
|
|
||||||
if ( buckets.length <= l ) {
|
|
||||||
extendArray(buckets, l);
|
|
||||||
}
|
|
||||||
if ( buckets[l] === null ) {
|
|
||||||
buckets[l] = [];
|
|
||||||
}
|
|
||||||
buckets[l].push(suffix);
|
|
||||||
}
|
|
||||||
for ( let i = 0; i < buckets.length; i++ ) {
|
|
||||||
let bucket = buckets[i];
|
|
||||||
if ( bucket !== null ) {
|
|
||||||
buckets[i] = bucket.sort().join('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
store.set(tld, buckets);
|
|
||||||
}
|
|
||||||
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
let extendArray = function(aa, rb) {
|
|
||||||
for ( let i = aa.length; i <= rb; i++ ) {
|
|
||||||
aa.push(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
let selfieMagic = 3;
|
|
||||||
|
|
||||||
let toSelfie = function() {
|
|
||||||
return {
|
|
||||||
magic: selfieMagic,
|
|
||||||
rules: Array.from(rules),
|
|
||||||
exceptions: Array.from(exceptions)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let fromSelfie = function(selfie) {
|
|
||||||
if ( selfie instanceof Object === false || selfie.magic !== selfieMagic ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
rules = new Map(selfie.rules);
|
|
||||||
exceptions = new Map(selfie.exceptions);
|
|
||||||
window.dispatchEvent(new CustomEvent('publicSuffixList'));
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// Public API
|
|
||||||
|
|
||||||
root = root || window;
|
|
||||||
|
|
||||||
root.publicSuffixList = {
|
|
||||||
'version': '1.0',
|
|
||||||
'parse': parse,
|
|
||||||
'getDomain': getDomain,
|
|
||||||
'getPublicSuffix': getPublicSuffix,
|
|
||||||
'toSelfie': toSelfie,
|
|
||||||
'fromSelfie': fromSelfie
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
})(this);
|
|
||||||
|
|
647
src/lib/publicsuffixlist/publicsuffixlist.js
Normal file
647
src/lib/publicsuffixlist/publicsuffixlist.js
Normal file
|
@ -0,0 +1,647 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
publicsuffixlist.js - an efficient javascript implementation to deal with
|
||||||
|
Mozilla Foundation's Public Suffix List <http://publicsuffix.org/list/>
|
||||||
|
|
||||||
|
Copyright (C) 2013-present Raymond Hill
|
||||||
|
|
||||||
|
License: pick the one which suits you:
|
||||||
|
GPL v3 see <https://www.gnu.org/licenses/gpl.html>
|
||||||
|
APL v2 see <http://www.apache.org/licenses/LICENSE-2.0>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! Home: https://github.com/gorhill/publicsuffixlist.js -- GPLv3 APLv2 */
|
||||||
|
|
||||||
|
/* jshint browser:true, esversion:6, laxbreak:true, undef:true, unused:true */
|
||||||
|
/* globals WebAssembly, console, exports:true, module */
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
Reference:
|
||||||
|
https://publicsuffix.org/list/
|
||||||
|
|
||||||
|
Excerpt:
|
||||||
|
|
||||||
|
> Algorithm
|
||||||
|
>
|
||||||
|
> 1. Match domain against all rules and take note of the matching ones.
|
||||||
|
> 2. If no rules match, the prevailing rule is "*".
|
||||||
|
> 3. If more than one rule matches, the prevailing rule is the one which
|
||||||
|
is an exception rule.
|
||||||
|
> 4. If there is no matching exception rule, the prevailing rule is the
|
||||||
|
one with the most labels.
|
||||||
|
> 5. If the prevailing rule is a exception rule, modify it by removing
|
||||||
|
the leftmost label.
|
||||||
|
> 6. The public suffix is the set of labels from the domain which match
|
||||||
|
the labels of the prevailing rule, using the matching algorithm above.
|
||||||
|
> 7. The registered or registrable domain is the public suffix plus one
|
||||||
|
additional label.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
(function(context) {
|
||||||
|
// >>>>>>>> start of anonymous namespace
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
|
||||||
|
Tree encoding in array buffer:
|
||||||
|
|
||||||
|
Node:
|
||||||
|
+ u8: length of char data
|
||||||
|
+ u8: flags => bit 0: is_publicsuffix, bit 1: is_exception
|
||||||
|
+ u16: length of array of children
|
||||||
|
+ u32: char data or offset to char data
|
||||||
|
+ u32: offset to array of children
|
||||||
|
= 12 bytes
|
||||||
|
|
||||||
|
More bits in flags could be used; for example:
|
||||||
|
- to distinguish private suffixes
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// i32 / i8
|
||||||
|
const HOSTNAME_SLOT = 0; // jshint ignore:line
|
||||||
|
const LABEL_INDICES_SLOT = 256; // -- / 256
|
||||||
|
const RULES_PTR_SLOT = 100; // 100 / 400
|
||||||
|
const CHARDATA_PTR_SLOT = 101; // 101 / 404
|
||||||
|
const EMPTY_STRING = '';
|
||||||
|
const SELFIE_MAGIC = 2;
|
||||||
|
|
||||||
|
let wasmMemory;
|
||||||
|
let pslBuffer32;
|
||||||
|
let pslBuffer8;
|
||||||
|
let pslByteLength = 0;
|
||||||
|
let hostnameArg = EMPTY_STRING;
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const fireChangedEvent = function() {
|
||||||
|
if (
|
||||||
|
window instanceof Object &&
|
||||||
|
window.dispatchEvent instanceof Function &&
|
||||||
|
window.CustomEvent instanceof Function
|
||||||
|
) {
|
||||||
|
window.dispatchEvent(new CustomEvent('publicSuffixListChanged'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const allocateBuffers = function(byteLength) {
|
||||||
|
pslByteLength = byteLength + 3 & ~3;
|
||||||
|
if (
|
||||||
|
pslBuffer32 !== undefined &&
|
||||||
|
pslBuffer32.byteLength >= pslByteLength
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( wasmMemory !== undefined ) {
|
||||||
|
const newPageCount = pslByteLength + 0xFFFF >>> 16;
|
||||||
|
const curPageCount = wasmMemory.buffer.byteLength >>> 16;
|
||||||
|
const delta = newPageCount - curPageCount;
|
||||||
|
if ( delta > 0 ) {
|
||||||
|
wasmMemory.grow(delta);
|
||||||
|
pslBuffer32 = new Uint32Array(wasmMemory.buffer);
|
||||||
|
pslBuffer8 = new Uint8Array(wasmMemory.buffer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pslBuffer8 = new Uint8Array(pslByteLength);
|
||||||
|
pslBuffer32 = new Uint32Array(pslBuffer8.buffer);
|
||||||
|
}
|
||||||
|
hostnameArg = '';
|
||||||
|
pslBuffer8[LABEL_INDICES_SLOT] = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Parse and set a UTF-8 text-based suffix list. Format is same as found at:
|
||||||
|
// http://publicsuffix.org/list/
|
||||||
|
//
|
||||||
|
// `toAscii` is a converter from unicode to punycode. Required since the
|
||||||
|
// Public Suffix List contains unicode characters.
|
||||||
|
// Suggestion: use <https://github.com/bestiejs/punycode.js>
|
||||||
|
|
||||||
|
const parse = function(text, toAscii) {
|
||||||
|
// Use short property names for better minifying results
|
||||||
|
const rootRule = {
|
||||||
|
l: EMPTY_STRING, // l => label
|
||||||
|
f: 0, // f => flags
|
||||||
|
c: undefined // c => children
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tree building
|
||||||
|
{
|
||||||
|
const compareLabels = function(a, b) {
|
||||||
|
let n = a.length;
|
||||||
|
let d = n - b.length;
|
||||||
|
if ( d !== 0 ) { return d; }
|
||||||
|
for ( let i = 0; i < n; i++ ) {
|
||||||
|
d = a.charCodeAt(i) - b.charCodeAt(i);
|
||||||
|
if ( d !== 0 ) { return d; }
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addToTree = function(rule, exception) {
|
||||||
|
let node = rootRule;
|
||||||
|
let end = rule.length;
|
||||||
|
while ( end > 0 ) {
|
||||||
|
const beg = rule.lastIndexOf('.', end - 1);
|
||||||
|
const label = rule.slice(beg + 1, end);
|
||||||
|
end = beg;
|
||||||
|
|
||||||
|
if ( Array.isArray(node.c) === false ) {
|
||||||
|
const child = { l: label, f: 0, c: undefined };
|
||||||
|
node.c = [ child ];
|
||||||
|
node = child;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = 0;
|
||||||
|
let right = node.c.length;
|
||||||
|
while ( left < right ) {
|
||||||
|
const i = left + right >>> 1;
|
||||||
|
const d = compareLabels(label, node.c[i].l);
|
||||||
|
if ( d < 0 ) {
|
||||||
|
right = i;
|
||||||
|
if ( right === left ) {
|
||||||
|
const child = {
|
||||||
|
l: label,
|
||||||
|
f: 0,
|
||||||
|
c: undefined
|
||||||
|
};
|
||||||
|
node.c.splice(left, 0, child);
|
||||||
|
node = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( d > 0 ) {
|
||||||
|
left = i + 1;
|
||||||
|
if ( left === right ) {
|
||||||
|
const child = {
|
||||||
|
l: label,
|
||||||
|
f: 0,
|
||||||
|
c: undefined
|
||||||
|
};
|
||||||
|
node.c.splice(right, 0, child);
|
||||||
|
node = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* d === 0 */
|
||||||
|
node = node.c[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.f |= 0b01;
|
||||||
|
if ( exception ) {
|
||||||
|
node.f |= 0b10;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. If no rules match, the prevailing rule is "*".
|
||||||
|
addToTree('*', false);
|
||||||
|
|
||||||
|
const mustPunycode = /[^a-z0-9.-]/;
|
||||||
|
const textEnd = text.length;
|
||||||
|
let lineBeg = 0;
|
||||||
|
|
||||||
|
while ( lineBeg < textEnd ) {
|
||||||
|
let lineEnd = text.indexOf('\n', lineBeg);
|
||||||
|
if ( lineEnd === -1 ) {
|
||||||
|
lineEnd = text.indexOf('\r', lineBeg);
|
||||||
|
if ( lineEnd === -1 ) {
|
||||||
|
lineEnd = textEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let line = text.slice(lineBeg, lineEnd).trim();
|
||||||
|
lineBeg = lineEnd + 1;
|
||||||
|
|
||||||
|
// Ignore comments
|
||||||
|
const pos = line.indexOf('//');
|
||||||
|
if ( pos !== -1 ) {
|
||||||
|
line = line.slice(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore surrounding whitespaces
|
||||||
|
line = line.trim();
|
||||||
|
if ( line.length === 0 ) { continue; }
|
||||||
|
|
||||||
|
const exception = line.charCodeAt(0) === 0x21 /* '!' */;
|
||||||
|
if ( exception ) {
|
||||||
|
line = line.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mustPunycode.test(line) ) {
|
||||||
|
line = toAscii(line.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
addToTree(line, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const labelToOffsetMap = new Map();
|
||||||
|
const treeData = [];
|
||||||
|
const charData = [];
|
||||||
|
|
||||||
|
const allocate = function(n) {
|
||||||
|
const ibuf = treeData.length;
|
||||||
|
for ( let i = 0; i < n; i++ ) {
|
||||||
|
treeData.push(0);
|
||||||
|
}
|
||||||
|
return ibuf;
|
||||||
|
};
|
||||||
|
|
||||||
|
const storeNode = function(ibuf, node) {
|
||||||
|
const nChars = node.l.length;
|
||||||
|
const nChildren = node.c !== undefined
|
||||||
|
? node.c.length
|
||||||
|
: 0;
|
||||||
|
treeData[ibuf+0] = nChildren << 16 | node.f << 8 | nChars;
|
||||||
|
// char data
|
||||||
|
if ( nChars <= 4 ) {
|
||||||
|
let v = 0;
|
||||||
|
if ( nChars > 0 ) {
|
||||||
|
v |= node.l.charCodeAt(0);
|
||||||
|
if ( nChars > 1 ) {
|
||||||
|
v |= node.l.charCodeAt(1) << 8;
|
||||||
|
if ( nChars > 2 ) {
|
||||||
|
v |= node.l.charCodeAt(2) << 16;
|
||||||
|
if ( nChars > 3 ) {
|
||||||
|
v |= node.l.charCodeAt(3) << 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
treeData[ibuf+1] = v;
|
||||||
|
} else {
|
||||||
|
let offset = labelToOffsetMap.get(node.l);
|
||||||
|
if ( offset === undefined ) {
|
||||||
|
offset = charData.length;
|
||||||
|
for ( let i = 0; i < nChars; i++ ) {
|
||||||
|
charData.push(node.l.charCodeAt(i));
|
||||||
|
}
|
||||||
|
labelToOffsetMap.set(node.l, offset);
|
||||||
|
}
|
||||||
|
treeData[ibuf+1] = offset;
|
||||||
|
}
|
||||||
|
// child nodes
|
||||||
|
if ( Array.isArray(node.c) === false ) {
|
||||||
|
treeData[ibuf+2] = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iarray = allocate(nChildren * 3);
|
||||||
|
treeData[ibuf+2] = iarray;
|
||||||
|
for ( let i = 0; i < nChildren; i++ ) {
|
||||||
|
storeNode(iarray + i * 3, node.c[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// First 512 bytes are reserved for internal use
|
||||||
|
allocate(512 >> 2);
|
||||||
|
|
||||||
|
const iRootRule = allocate(3);
|
||||||
|
storeNode(iRootRule, rootRule);
|
||||||
|
treeData[RULES_PTR_SLOT] = iRootRule;
|
||||||
|
|
||||||
|
const iCharData = treeData.length << 2;
|
||||||
|
treeData[CHARDATA_PTR_SLOT] = iCharData;
|
||||||
|
|
||||||
|
const byteLength = (treeData.length << 2) + (charData.length + 3 & ~3);
|
||||||
|
allocateBuffers(byteLength);
|
||||||
|
pslBuffer32.set(treeData);
|
||||||
|
pslBuffer8.set(charData, treeData.length << 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fireChangedEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const setHostnameArg = function(hostname) {
|
||||||
|
const buf = pslBuffer8;
|
||||||
|
if ( hostname === hostnameArg ) { return buf[LABEL_INDICES_SLOT]; }
|
||||||
|
if ( hostname === null || hostname.length === 0 ) {
|
||||||
|
hostnameArg = '';
|
||||||
|
return (buf[LABEL_INDICES_SLOT] = 0);
|
||||||
|
}
|
||||||
|
hostname = hostname.toLowerCase();
|
||||||
|
hostnameArg = hostname;
|
||||||
|
let n = hostname.length;
|
||||||
|
if ( n > 255 ) { n = 255; }
|
||||||
|
buf[LABEL_INDICES_SLOT] = n;
|
||||||
|
let i = n;
|
||||||
|
let j = LABEL_INDICES_SLOT + 1;
|
||||||
|
while ( i-- ) {
|
||||||
|
const c = hostname.charCodeAt(i);
|
||||||
|
if ( c === 0x2E /* '.' */ ) {
|
||||||
|
buf[j+0] = i + 1;
|
||||||
|
buf[j+1] = i;
|
||||||
|
j += 2;
|
||||||
|
}
|
||||||
|
buf[i] = c;
|
||||||
|
}
|
||||||
|
buf[j] = 0;
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// Returns an offset to the start of the public suffix.
|
||||||
|
//
|
||||||
|
// WASM-able, because no information outside the buffer content is required.
|
||||||
|
|
||||||
|
const getPublicSuffixPosJS = function() {
|
||||||
|
const buf8 = pslBuffer8;
|
||||||
|
const buf32 = pslBuffer32;
|
||||||
|
const iCharData = buf32[CHARDATA_PTR_SLOT];
|
||||||
|
|
||||||
|
let iNode = pslBuffer32[RULES_PTR_SLOT];
|
||||||
|
let cursorPos = -1;
|
||||||
|
let iLabel = LABEL_INDICES_SLOT;
|
||||||
|
|
||||||
|
// Label-lookup loop
|
||||||
|
for (;;) {
|
||||||
|
// Extract label indices
|
||||||
|
const labelBeg = buf8[iLabel+1];
|
||||||
|
const labelLen = buf8[iLabel+0] - labelBeg;
|
||||||
|
// Match-lookup loop: binary search
|
||||||
|
let r = buf32[iNode+0] >>> 16;
|
||||||
|
if ( r === 0 ) { break; }
|
||||||
|
const iCandidates = buf32[iNode+2];
|
||||||
|
let l = 0;
|
||||||
|
let iFound = 0;
|
||||||
|
while ( l < r ) {
|
||||||
|
const iCandidate = l + r >>> 1;
|
||||||
|
const iCandidateNode = iCandidates + iCandidate + (iCandidate << 1);
|
||||||
|
const candidateLen = buf32[iCandidateNode+0] & 0x000000FF;
|
||||||
|
let d = labelLen - candidateLen;
|
||||||
|
if ( d === 0 ) {
|
||||||
|
const iCandidateChar = candidateLen <= 4
|
||||||
|
? iCandidateNode + 1 << 2
|
||||||
|
: iCharData + buf32[iCandidateNode+1];
|
||||||
|
for ( let i = 0; i < labelLen; i++ ) {
|
||||||
|
d = buf8[labelBeg+i] - buf8[iCandidateChar+i];
|
||||||
|
if ( d !== 0 ) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( d < 0 ) {
|
||||||
|
r = iCandidate;
|
||||||
|
} else if ( d > 0 ) {
|
||||||
|
l = iCandidate + 1;
|
||||||
|
} else /* if ( d === 0 ) */ {
|
||||||
|
iFound = iCandidateNode;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. If no rules match, the prevailing rule is "*".
|
||||||
|
if ( iFound === 0 ) {
|
||||||
|
if ( buf8[iCandidates + 1 << 2] !== 0x2A /* '*' */ ) { break; }
|
||||||
|
iFound = iCandidates;
|
||||||
|
}
|
||||||
|
iNode = iFound;
|
||||||
|
// 5. If the prevailing rule is a exception rule, modify it by
|
||||||
|
// removing the leftmost label.
|
||||||
|
if ( (buf32[iNode+0] & 0x00000200) !== 0 ) {
|
||||||
|
if ( iLabel > LABEL_INDICES_SLOT ) {
|
||||||
|
return iLabel - 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ( (buf32[iNode+0] & 0x00000100) !== 0 ) {
|
||||||
|
cursorPos = iLabel;
|
||||||
|
}
|
||||||
|
if ( labelBeg === 0 ) { break; }
|
||||||
|
iLabel += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursorPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
let getPublicSuffixPosWASM;
|
||||||
|
let getPublicSuffixPos = getPublicSuffixPosJS;
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const getPublicSuffix = function(hostname) {
|
||||||
|
if ( pslBuffer32 === undefined ) { return EMPTY_STRING; }
|
||||||
|
|
||||||
|
const hostnameLen = setHostnameArg(hostname);
|
||||||
|
const buf8 = pslBuffer8;
|
||||||
|
if ( hostnameLen === 0 || buf8[0] === 0x2E /* '.' */ ) {
|
||||||
|
return EMPTY_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursorPos = getPublicSuffixPos();
|
||||||
|
if ( cursorPos === -1 ) {
|
||||||
|
return EMPTY_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
const beg = buf8[cursorPos + 1];
|
||||||
|
return beg === 0 ? hostnameArg : hostnameArg.slice(beg);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const getDomain = function(hostname) {
|
||||||
|
if ( pslBuffer32 === undefined ) { return EMPTY_STRING; }
|
||||||
|
|
||||||
|
const hostnameLen = setHostnameArg(hostname);
|
||||||
|
const buf8 = pslBuffer8;
|
||||||
|
if ( hostnameLen === 0 || buf8[0] === 0x2E /* '.' */ ) {
|
||||||
|
return EMPTY_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursorPos = getPublicSuffixPos();
|
||||||
|
if ( cursorPos === -1 || buf8[cursorPos + 1] === 0 ) {
|
||||||
|
return EMPTY_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. The registered or registrable domain is the public suffix plus one
|
||||||
|
// additional label.
|
||||||
|
const beg = buf8[cursorPos + 3];
|
||||||
|
return beg === 0 ? hostnameArg : hostnameArg.slice(beg);
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
const toSelfie = function(encoder) {
|
||||||
|
if ( pslBuffer8 === undefined ) { return ''; }
|
||||||
|
if ( encoder instanceof Object ) {
|
||||||
|
const bufferStr = encoder.encode(pslBuffer8.buffer, pslByteLength);
|
||||||
|
return `${SELFIE_MAGIC}\t${bufferStr}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
magic: SELFIE_MAGIC,
|
||||||
|
buf32: Array.from(
|
||||||
|
new Uint32Array(pslBuffer8.buffer, 0, pslByteLength >>> 2)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromSelfie = function(selfie, decoder) {
|
||||||
|
let byteLength = 0;
|
||||||
|
if (
|
||||||
|
typeof selfie === 'string' &&
|
||||||
|
selfie.length !== 0 &&
|
||||||
|
decoder instanceof Object
|
||||||
|
) {
|
||||||
|
const pos = selfie.indexOf('\t');
|
||||||
|
if ( pos === -1 || selfie.slice(0, pos) !== `${SELFIE_MAGIC}` ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bufferStr = selfie.slice(pos + 1);
|
||||||
|
byteLength = decoder.decodeSize(bufferStr);
|
||||||
|
if ( byteLength === 0 ) { return false; }
|
||||||
|
allocateBuffers(byteLength);
|
||||||
|
decoder.decode(bufferStr, pslBuffer8.buffer);
|
||||||
|
} else if (
|
||||||
|
selfie instanceof Object &&
|
||||||
|
selfie.magic === SELFIE_MAGIC &&
|
||||||
|
Array.isArray(selfie.buf32)
|
||||||
|
) {
|
||||||
|
byteLength = selfie.buf32.length << 2;
|
||||||
|
allocateBuffers(byteLength);
|
||||||
|
pslBuffer32.set(selfie.buf32);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Important!
|
||||||
|
hostnameArg = '';
|
||||||
|
pslBuffer8[LABEL_INDICES_SLOT] = 0;
|
||||||
|
|
||||||
|
fireChangedEvent();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// The WASM module is entirely optional, the JS implementation will be
|
||||||
|
// used should the WASM module be unavailable for whatever reason.
|
||||||
|
|
||||||
|
const enableWASM = (function() {
|
||||||
|
// The directory from which the current script was fetched should also
|
||||||
|
// contain the related WASM file. The script is fetched from a trusted
|
||||||
|
// location, and consequently so will be the related WASM file.
|
||||||
|
let workingDir;
|
||||||
|
{
|
||||||
|
const url = new URL(document.currentScript.src);
|
||||||
|
const match = /[^\/]+$/.exec(url.pathname);
|
||||||
|
if ( match !== null ) {
|
||||||
|
url.pathname = url.pathname.slice(0, match.index);
|
||||||
|
}
|
||||||
|
workingDir = url.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
let memory;
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
if ( getPublicSuffixPosWASM instanceof Function ) {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof WebAssembly !== 'object' ||
|
||||||
|
typeof WebAssembly.instantiateStreaming !== 'function'
|
||||||
|
) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The wasm code will work only if CPU is natively little-endian,
|
||||||
|
// as we use native uint32 array in our js code.
|
||||||
|
const uint32s = new Uint32Array(1);
|
||||||
|
const uint8s = new Uint8Array(uint32s.buffer);
|
||||||
|
uint32s[0] = 1;
|
||||||
|
if ( uint8s[0] !== 1 ) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(
|
||||||
|
workingDir + 'wasm/publicsuffixlist.wasm',
|
||||||
|
{ mode: 'same-origin' }
|
||||||
|
).then(response => {
|
||||||
|
const pageCount = pslBuffer8 !== undefined
|
||||||
|
? pslBuffer8.byteLength + 0xFFFF >>> 16
|
||||||
|
: 1;
|
||||||
|
memory = new WebAssembly.Memory({ initial: pageCount });
|
||||||
|
return WebAssembly.instantiateStreaming(
|
||||||
|
response,
|
||||||
|
{ imports: { memory: memory } }
|
||||||
|
);
|
||||||
|
}).then(({ instance }) => {
|
||||||
|
const curPageCount = memory.buffer.byteLength >>> 16;
|
||||||
|
const newPageCount = pslBuffer8 !== undefined
|
||||||
|
? pslBuffer8.byteLength + 0xFFFF >>> 16
|
||||||
|
: 0;
|
||||||
|
if ( newPageCount > curPageCount ) {
|
||||||
|
memory.grow(newPageCount - curPageCount);
|
||||||
|
}
|
||||||
|
if ( pslBuffer32 !== undefined ) {
|
||||||
|
const buf8 = new Uint8Array(memory.buffer);
|
||||||
|
const buf32 = new Uint32Array(memory.buffer);
|
||||||
|
buf32.set(pslBuffer32);
|
||||||
|
pslBuffer8 = buf8;
|
||||||
|
pslBuffer32 = buf32;
|
||||||
|
}
|
||||||
|
wasmMemory = memory;
|
||||||
|
getPublicSuffixPosWASM = instance.exports.getPublicSuffixPos;
|
||||||
|
getPublicSuffixPos = getPublicSuffixPosWASM;
|
||||||
|
memory = undefined;
|
||||||
|
return true;
|
||||||
|
}).catch(reason => {
|
||||||
|
console.info(reason);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
const disableWASM = function() {
|
||||||
|
if ( getPublicSuffixPosWASM instanceof Function ) {
|
||||||
|
getPublicSuffixPos = getPublicSuffixPosJS;
|
||||||
|
getPublicSuffixPosWASM = undefined;
|
||||||
|
}
|
||||||
|
if ( wasmMemory === undefined ) { return; }
|
||||||
|
if ( pslBuffer32 !== undefined ) {
|
||||||
|
const buf8 = new Uint8Array(pslByteLength);
|
||||||
|
const buf32 = new Uint32Array(buf8.buffer);
|
||||||
|
buf32.set(pslBuffer32);
|
||||||
|
pslBuffer8 = buf8;
|
||||||
|
pslBuffer32 = buf32;
|
||||||
|
}
|
||||||
|
wasmMemory = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
context = context || window;
|
||||||
|
|
||||||
|
context.publicSuffixList = {
|
||||||
|
version: '2.0',
|
||||||
|
parse,
|
||||||
|
getDomain,
|
||||||
|
getPublicSuffix,
|
||||||
|
toSelfie, fromSelfie,
|
||||||
|
disableWASM, enableWASM,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( typeof module !== 'undefined' ) {
|
||||||
|
module.exports = context.publicSuffixList;
|
||||||
|
} else if ( typeof exports !== 'undefined' ) {
|
||||||
|
exports = context.publicSuffixList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
// <<<<<<<< end of anonymous namespace
|
||||||
|
})(this);
|
29
src/lib/publicsuffixlist/wasm/README.md
Normal file
29
src/lib/publicsuffixlist/wasm/README.md
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
### For code reviewers
|
||||||
|
|
||||||
|
All `wasm` files in that directory where created by compiling the
|
||||||
|
corresponding `wat` file using the command (using
|
||||||
|
`publicsuffixlist.wat`/`publicsuffixlist.wasm` as example):
|
||||||
|
|
||||||
|
wat2wasm publicsuffixlist.wat -o publicsuffixlist.wasm
|
||||||
|
|
||||||
|
Assuming:
|
||||||
|
|
||||||
|
- The command is executed from within the present directory.
|
||||||
|
|
||||||
|
### `wat2wasm` tool
|
||||||
|
|
||||||
|
The `wat2wasm` tool can be downloaded from an official WebAssembly project:
|
||||||
|
<https://github.com/WebAssembly/wabt/releases>.
|
||||||
|
|
||||||
|
### `wat2wasm` tool online
|
||||||
|
|
||||||
|
You can also use the following online `wat2wasm` tool:
|
||||||
|
<https://webassembly.github.io/wabt/demo/wat2wasm/>.
|
||||||
|
|
||||||
|
Just paste the whole content of the `wat` file to compile into the WAT pane.
|
||||||
|
Click "Download" button to retrieve the resulting `wasm` file.
|
||||||
|
|
||||||
|
### See also
|
||||||
|
|
||||||
|
For the curious, the following online tool allows you to find out the machine
|
||||||
|
code as a result from the WASM code: https://mbebenita.github.io/WasmExplorer/
|
BIN
src/lib/publicsuffixlist/wasm/publicsuffixlist.wasm
Normal file
BIN
src/lib/publicsuffixlist/wasm/publicsuffixlist.wasm
Normal file
Binary file not shown.
317
src/lib/publicsuffixlist/wasm/publicsuffixlist.wat
Normal file
317
src/lib/publicsuffixlist/wasm/publicsuffixlist.wat
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
;;
|
||||||
|
;; uBlock Origin - a browser extension to block requests.
|
||||||
|
;; Copyright (C) 2019-present Raymond Hill
|
||||||
|
;;
|
||||||
|
;; License: pick the one which suits you:
|
||||||
|
;; GPL v3 see <https://www.gnu.org/licenses/gpl.html>
|
||||||
|
;; APL v2 see <http://www.apache.org/licenses/LICENSE-2.0>
|
||||||
|
;;
|
||||||
|
;; Home: https://github.com/gorhill/publicsuffixlist.js
|
||||||
|
;; File: publicsuffixlist.wat
|
||||||
|
;;
|
||||||
|
;; Description: WebAssembly implementation for core lookup method in
|
||||||
|
;; publicsuffixlist.js
|
||||||
|
;;
|
||||||
|
;; How to compile:
|
||||||
|
;;
|
||||||
|
;; wat2wasm publicsuffixlist.wat -o publicsuffixlist.wasm
|
||||||
|
;;
|
||||||
|
;; The `wat2wasm` tool can be downloaded from an official WebAssembly
|
||||||
|
;; project:
|
||||||
|
;; https://github.com/WebAssembly/wabt/releases
|
||||||
|
|
||||||
|
|
||||||
|
(module
|
||||||
|
;;
|
||||||
|
;; module start
|
||||||
|
;;
|
||||||
|
|
||||||
|
(memory (import "imports" "memory") 1)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Tree encoding in array buffer:
|
||||||
|
;;
|
||||||
|
;; Node:
|
||||||
|
;; + u8: length of char data
|
||||||
|
;; + u8: flags => bit 0: is_publicsuffix, bit 1: is_exception
|
||||||
|
;; + u16: length of array of children
|
||||||
|
;; + u32: char data or offset to char data
|
||||||
|
;; + u32: offset to array of children
|
||||||
|
;; = 12 bytes
|
||||||
|
;;
|
||||||
|
;; // i32 / i8
|
||||||
|
;; const HOSTNAME_SLOT = 0; // jshint ignore:line
|
||||||
|
;; const LABEL_INDICES_SLOT = 256; // -- / 256
|
||||||
|
;; const RULES_PTR_SLOT = 100; // 100 / 400
|
||||||
|
;; const CHARDATA_PTR_SLOT = 101; // 101 / 404
|
||||||
|
;; const EMPTY_STRING = '';
|
||||||
|
;; const SELFIE_MAGIC = 2;
|
||||||
|
;;
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; Public functions
|
||||||
|
;;
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; unsigned int getPublicSuffixPos()
|
||||||
|
;;
|
||||||
|
;; Returns an offset to the start of the public suffix.
|
||||||
|
;;
|
||||||
|
(func (export "getPublicSuffixPos")
|
||||||
|
(result i32) ;; result = match index, -1 = miss
|
||||||
|
(local $iCharData i32) ;; offset to start of character data
|
||||||
|
(local $iNode i32) ;; offset to current node
|
||||||
|
(local $iLabel i32) ;; offset to label indices
|
||||||
|
(local $cursorPos i32) ;; position of cursor within hostname argument
|
||||||
|
(local $labelBeg i32)
|
||||||
|
(local $labelLen i32)
|
||||||
|
(local $nCandidates i32)
|
||||||
|
(local $iCandidates i32)
|
||||||
|
(local $iFound i32)
|
||||||
|
(local $l i32)
|
||||||
|
(local $r i32)
|
||||||
|
(local $d i32)
|
||||||
|
(local $iCandidate i32)
|
||||||
|
(local $iCandidateNode i32)
|
||||||
|
(local $candidateLen i32)
|
||||||
|
(local $iCandidateChar i32)
|
||||||
|
(local $_1 i32)
|
||||||
|
(local $_2 i32)
|
||||||
|
(local $_3 i32)
|
||||||
|
;;
|
||||||
|
;; const iCharData = buf32[CHARDATA_PTR_SLOT];
|
||||||
|
i32.const 404
|
||||||
|
i32.load
|
||||||
|
set_local $iCharData
|
||||||
|
;; let iNode = pslBuffer32[RULES_PTR_SLOT];
|
||||||
|
i32.const 400
|
||||||
|
i32.load
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
set_local $iNode
|
||||||
|
;; let iLabel = LABEL_INDICES_SLOT;
|
||||||
|
i32.const 256
|
||||||
|
set_local $iLabel
|
||||||
|
;; let cursorPos = -1;
|
||||||
|
i32.const -1
|
||||||
|
set_local $cursorPos
|
||||||
|
;; label-lookup loop
|
||||||
|
;; for (;;) {
|
||||||
|
block $labelLookupDone loop $labelLookup
|
||||||
|
;; // Extract label indices
|
||||||
|
;; const labelBeg = buf8[iLabel+1];
|
||||||
|
;; const labelLen = buf8[iLabel+0] - labelBeg;
|
||||||
|
get_local $iLabel
|
||||||
|
i32.load8_u
|
||||||
|
get_local $iLabel
|
||||||
|
i32.load8_u offset=1
|
||||||
|
tee_local $labelBeg
|
||||||
|
i32.sub
|
||||||
|
set_local $labelLen
|
||||||
|
;; // Match-lookup loop: binary search
|
||||||
|
;; let r = buf32[iNode+0] >>> 16;
|
||||||
|
;; if ( r === 0 ) { break; }
|
||||||
|
get_local $iNode
|
||||||
|
i32.load16_u offset=2
|
||||||
|
tee_local $r
|
||||||
|
i32.eqz
|
||||||
|
br_if $labelLookupDone
|
||||||
|
;; const iCandidates = buf32[iNode+2];
|
||||||
|
get_local $iNode
|
||||||
|
i32.load offset=8
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
set_local $iCandidates
|
||||||
|
;; let l = 0;
|
||||||
|
;; let iFound = 0;
|
||||||
|
i32.const 0
|
||||||
|
tee_local $l
|
||||||
|
set_local $iFound
|
||||||
|
;; while ( l < r ) {
|
||||||
|
block $binarySearchDone loop $binarySearch
|
||||||
|
get_local $l
|
||||||
|
get_local $r
|
||||||
|
i32.ge_u
|
||||||
|
br_if $binarySearchDone
|
||||||
|
;; const iCandidate = l + r >>> 1;
|
||||||
|
get_local $l
|
||||||
|
get_local $r
|
||||||
|
i32.add
|
||||||
|
i32.const 1
|
||||||
|
i32.shr_u
|
||||||
|
tee_local $iCandidate
|
||||||
|
;; const iCandidateNode = iCandidates + iCandidate + (iCandidate << 1);
|
||||||
|
i32.const 2
|
||||||
|
i32.shl
|
||||||
|
tee_local $_1
|
||||||
|
get_local $_1
|
||||||
|
i32.const 1
|
||||||
|
i32.shl
|
||||||
|
i32.add
|
||||||
|
get_local $iCandidates
|
||||||
|
i32.add
|
||||||
|
tee_local $iCandidateNode
|
||||||
|
;; const candidateLen = buf32[iCandidateNode+0] & 0x000000FF;
|
||||||
|
i32.load8_u
|
||||||
|
set_local $candidateLen
|
||||||
|
;; let d = labelLen - candidateLen;
|
||||||
|
get_local $labelLen
|
||||||
|
get_local $candidateLen
|
||||||
|
i32.sub
|
||||||
|
tee_local $d
|
||||||
|
;; if ( d === 0 ) {
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
;; const iCandidateChar = candidateLen <= 4
|
||||||
|
get_local $candidateLen
|
||||||
|
i32.const 4
|
||||||
|
i32.le_u
|
||||||
|
if
|
||||||
|
;; ? iCandidateNode + 1 << 2
|
||||||
|
get_local $iCandidateNode
|
||||||
|
i32.const 4
|
||||||
|
i32.add
|
||||||
|
set_local $iCandidateChar
|
||||||
|
else
|
||||||
|
;; : buf32[CHARDATA_PTR_SLOT] + buf32[iCandidateNode+1];
|
||||||
|
get_local $iCharData
|
||||||
|
get_local $iCandidateNode
|
||||||
|
i32.load offset=4
|
||||||
|
i32.add
|
||||||
|
set_local $iCandidateChar
|
||||||
|
end
|
||||||
|
;; for ( let i = 0; i < labelLen; i++ ) {
|
||||||
|
get_local $labelBeg
|
||||||
|
tee_local $_1
|
||||||
|
get_local $labelLen
|
||||||
|
i32.add
|
||||||
|
set_local $_3
|
||||||
|
get_local $iCandidateChar
|
||||||
|
set_local $_2
|
||||||
|
block $findDiffDone loop $findDiff
|
||||||
|
;; d = buf8[labelBeg+i] - buf8[iCandidateChar+i];
|
||||||
|
;; if ( d !== 0 ) { break; }
|
||||||
|
get_local $_1
|
||||||
|
i32.load8_u
|
||||||
|
get_local $_2
|
||||||
|
i32.load8_u
|
||||||
|
i32.sub
|
||||||
|
tee_local $d
|
||||||
|
br_if $findDiffDone
|
||||||
|
get_local $_1
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
tee_local $_1
|
||||||
|
get_local $_3
|
||||||
|
i32.eq
|
||||||
|
br_if $findDiffDone
|
||||||
|
get_local $_2
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $_2
|
||||||
|
br $findDiff
|
||||||
|
;; }
|
||||||
|
end end
|
||||||
|
;; }
|
||||||
|
end
|
||||||
|
;; if ( d < 0 ) {
|
||||||
|
;; r = iCandidate;
|
||||||
|
get_local $d
|
||||||
|
i32.const 0
|
||||||
|
i32.lt_s
|
||||||
|
if
|
||||||
|
get_local $iCandidate
|
||||||
|
set_local $r
|
||||||
|
br $binarySearch
|
||||||
|
end
|
||||||
|
;; } else if ( d > 0 ) {
|
||||||
|
;; l = iCandidate + 1;
|
||||||
|
get_local $d
|
||||||
|
i32.const 0
|
||||||
|
i32.gt_s
|
||||||
|
if
|
||||||
|
get_local $iCandidate
|
||||||
|
i32.const 1
|
||||||
|
i32.add
|
||||||
|
set_local $l
|
||||||
|
br $binarySearch
|
||||||
|
end
|
||||||
|
;; } else /* if ( d === 0 ) */ {
|
||||||
|
;; iFound = iCandidateNode;
|
||||||
|
;; break;
|
||||||
|
;; }
|
||||||
|
get_local $iCandidateNode
|
||||||
|
set_local $iFound
|
||||||
|
end end
|
||||||
|
;; }
|
||||||
|
;; // 2. If no rules match, the prevailing rule is "*".
|
||||||
|
;; if ( iFound === 0 ) {
|
||||||
|
;; if ( buf8[iCandidates + 1 << 2] !== 0x2A /* '*' */ ) { break; }
|
||||||
|
;; iFound = iCandidates;
|
||||||
|
;; }
|
||||||
|
get_local $iFound
|
||||||
|
i32.eqz
|
||||||
|
if
|
||||||
|
get_local $iCandidates
|
||||||
|
i32.load8_u offset=4
|
||||||
|
i32.const 0x2A
|
||||||
|
i32.ne
|
||||||
|
br_if $labelLookupDone
|
||||||
|
get_local $iCandidates
|
||||||
|
set_local $iFound
|
||||||
|
end
|
||||||
|
;; iNode = iFound;
|
||||||
|
get_local $iFound
|
||||||
|
tee_local $iNode
|
||||||
|
;; // 5. If the prevailing rule is a exception rule, modify it by
|
||||||
|
;; // removing the leftmost label.
|
||||||
|
;; if ( (buf32[iNode+0] & 0x00000200) !== 0 ) {
|
||||||
|
;; if ( iLabel > LABEL_INDICES_SLOT ) {
|
||||||
|
;; return iLabel - 2;
|
||||||
|
;; }
|
||||||
|
;; break;
|
||||||
|
;; }
|
||||||
|
i32.load8_u offset=1
|
||||||
|
tee_local $_1
|
||||||
|
i32.const 0x02
|
||||||
|
i32.and
|
||||||
|
if
|
||||||
|
get_local $iLabel
|
||||||
|
i32.const 256
|
||||||
|
i32.gt_u
|
||||||
|
if
|
||||||
|
get_local $iLabel
|
||||||
|
i32.const -2
|
||||||
|
i32.add
|
||||||
|
return
|
||||||
|
end
|
||||||
|
br $labelLookupDone
|
||||||
|
end
|
||||||
|
;; if ( (buf32[iNode+0] & 0x00000100) !== 0 ) {
|
||||||
|
;; cursorPos = labelBeg;
|
||||||
|
;; }
|
||||||
|
get_local $_1
|
||||||
|
i32.const 0x01
|
||||||
|
i32.and
|
||||||
|
if
|
||||||
|
get_local $iLabel
|
||||||
|
set_local $cursorPos
|
||||||
|
end
|
||||||
|
;; if ( labelBeg === 0 ) { break; }
|
||||||
|
get_local $labelBeg
|
||||||
|
i32.eqz
|
||||||
|
br_if $labelLookupDone
|
||||||
|
;; iLabel += 2;
|
||||||
|
get_local $iLabel
|
||||||
|
i32.const 2
|
||||||
|
i32.add
|
||||||
|
set_local $iLabel
|
||||||
|
br $labelLookup
|
||||||
|
end end
|
||||||
|
get_local $cursorPos
|
||||||
|
)
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; module end
|
||||||
|
;;
|
||||||
|
)
|
|
@ -2,83 +2,148 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=560, initial-scale=1">
|
||||||
<link rel="stylesheet" href="css/common.css" type="text/css">
|
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||||
<link rel="stylesheet" href="css/fa-icons.css" type="text/css">
|
<link rel="stylesheet" href="css/fa-icons.css" type="text/css">
|
||||||
<link rel="stylesheet" href="css/scope-selector.css" type="text/css">
|
<link rel="stylesheet" type="text/css" href="css/logger-ui.css">
|
||||||
<link rel="stylesheet" href="css/logger-ui.css" type="text/css">
|
<link rel="shortcut icon" type="image/png" href="img/icon_64.png">
|
||||||
<link rel="shortcut icon" href="img/icon_16.png" type="image/png">
|
|
||||||
<title data-i18n="loggerPageName"></title>
|
<title data-i18n="loggerPageName"></title>
|
||||||
|
<style id="vwRendererRuntimeStyles"></style>
|
||||||
</head>
|
</head>
|
||||||
<body class="compactView f">
|
<body>
|
||||||
|
|
||||||
<div id="toolbar">
|
<div class="permatoolbar">
|
||||||
<div>
|
<div>
|
||||||
<select id="pageSelector">
|
<select id="pageSelector">
|
||||||
<option value="" data-i18n="statsPageDetailedAllPages">
|
<option value="0" data-i18n="logAll">
|
||||||
<option value="-1" data-i18n="statsPageDetailedBehindTheScenePage">
|
<option value="-1" data-i18n="logBehindTheScene">
|
||||||
</select>
|
<option value="_" data-i18n="loggerCurrentTab">
|
||||||
<span id="reloadTab" class="fa-icon disabled">refresh</span>
|
</select>
|
||||||
<span id="popupPanelButton" class="fa-icon disabled">th</span>
|
<span id="refresh" class="button fa-icon disabled needtab" data-i18n-title="loggerReloadTip">refresh</span>
|
||||||
|
<span id="showpopup" class="button px-icon disabled needtab" data-i18n-title="loggerPopupPanelTip"><img src="/img/icon_64.png"></span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span id="compactViewToggler" class="fa-icon">double-angle-up</span>
|
<a id="info" class="button fa-icon" href="https://github.com/gorhill/uBlock/wiki/The-logger" target="_blank" data-i18n-title="loggerInfoTip">info-circle</a>
|
||||||
<span id="clean" class="fa-icon disabled">times</span>
|
|
||||||
<span id="clear" class="fa-icon disabled">eraser</span>
|
|
||||||
<span id="filterButton" class="fa-icon">filter</span>
|
|
||||||
<input id="filterInput" type="text" placeholder="loggerFilterInputPlaceholder">
|
|
||||||
<input id="maxEntries" type="text" size="5" title="loggerMaxEntriesTip">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="popupPanelContainer"></div>
|
<div id="inspectors">
|
||||||
|
<div id="netInspector" class="inspector f">
|
||||||
<div id="content">
|
<div class="permatoolbar">
|
||||||
<style id="tabFilterer"></style>
|
<div>
|
||||||
<table>
|
<span class="button fa-icon vCompactToggler">double-angle-up</span>
|
||||||
<colgroup><col><col><col><col><col><col></colgroup>
|
<span id="clean" class="button fa-icon disabled">times</span>
|
||||||
<tbody></tbody>
|
<span id="clear" class="button fa-icon disabled" data-i18n-title="loggerClearTip">eraser</span>
|
||||||
</table>
|
<span id="pause"><span class="button fa-icon" data-i18n-title="loggerPauseTip">pause-circle-o</span><span class="button fa-icon" data-i18n-title="loggerUnpauseTip">play-circle-o</span></span>
|
||||||
</div>
|
<span id="filterExprGroup">
|
||||||
|
<span id="filterButton" class="button fa-icon" data-i18n-title="loggerRowFiltererButtonTip">filter</span>
|
||||||
<div style="display: none;">
|
<span id="filterInput">
|
||||||
<div id="emphasizeTemplate"><span><span></span><b></b><span></span></span></div>
|
<input type="search" placeholder="logFilterPrompt">
|
||||||
<div id="hiddenTemplate"><span style="display:none;"></span></div>
|
<span id="filterExprButton" class="button fa-icon expanded" data-i18n-title="loggerRowFiltererBuiltinTip">angle-up</span>
|
||||||
<div id="ruleEditor" class="modalDialog">
|
<div id="filterExprPicker">
|
||||||
<div class="dialog">
|
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t(?:--|\+\+)\t" data-i18n="loggerRowFiltererBuiltinBlocked"></span><span data-filtex="\tinfo\t" data-i18n="loggerRowFiltererBuiltinInfo"></span></div>
|
||||||
<section class="scopeWidget">
|
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span>
|
||||||
<span class="scope" id="specificScope"><span> </span></span><!--
|
<span style="flex-direction: column;">
|
||||||
--><span class="scope tip-anchor-right" id="globalScope" data-scope="*" data-i18n-tip="matrixGlobalScopeTip"><span><span>∗</span></span></span>
|
<div style="margin-bottom: 1px;"><span data-filtex="\tcookie\t">cookie</span><span data-filtex="\t(?:css|(?:inline-)?font)\t">css/font</span><span data-filtex="\timage\t">image</span><span data-filtex="\tmedia\t">media</span><span data-filtex="\t(?:inline-)?script(?:ing)?\t">script</span></div>
|
||||||
<div class="ruleEditorToolbar">
|
<div><span data-filtex="\t(?:fetch|websocket|xhr)\t">fetch</span><span data-filtex="\tframe\t">frame</span><span data-filtex="\t(?:beacon|csp_report|ping|other)\t">other</span></div>
|
||||||
<span id="matrixReloadButton" class="fa-icon tip-anchor-right" data-i18n-tip="matrixReloadButton">refresh</span>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\t(?:0,)?1\t" data-i18n="loggerRowFiltererBuiltin1p"></span><span data-filtex="\t(?:3(?:,\d)?|0,3)\t" data-i18n="loggerRowFiltererBuiltin3p"></span></div>
|
||||||
|
<div id="filterExprCnameOf" style="display:none"><span data-filtex="!" data-i18n="loggerRowFiltererBuiltinNot"></span><span data-filtex="\taliasURL=.">CNAME</span></div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span id="loggerStats" class="button fa-icon" style="display: none;">bar-chart</span>
|
||||||
|
<span id="loggerExport" class="button fa-icon">clipboard</span>
|
||||||
|
<span id="loggerSettings" class="button fa-icon">cog</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="vscrollable">
|
||||||
|
<div id="vwRenderer">
|
||||||
|
<div id="vwScroller">
|
||||||
|
<div id="vwVirtualContent">
|
||||||
|
<div id="vwContent"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div id="vwLineSizer">
|
||||||
<section>
|
<div class="logEntry oneLine"><div><span>00:00:00</span><span>%35%</span><span>+++</span><span>%65%</span><span>1234567890</span><span>+++</span></div></div>
|
||||||
<div class="ruleWidgets"></div>
|
</div>
|
||||||
<div class="ruleEditorToolbar">
|
</div>
|
||||||
<span id="matrixRevertButton" class="fa-icon scopeRel tip-anchor-right" data-i18n-tip="matrixRevertButtonTip">reply</span>
|
|
||||||
<span id="matrixPersistButton" class="fa-icon fa-icon-badged scopeRel tip-anchor-right" data-i18n-tip="matrixPersistButtonTip">lock</span>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="ruleRowTemplate" style="display: none;">
|
<iframe id="popupContainer"></iframe>
|
||||||
<div class="ruleRow"><!--
|
</div>
|
||||||
--><span class="ruleCell" data-type="*"> </span><!--
|
|
||||||
--><span class="ruleCell" data-type> </span><!--
|
<div id="modalOverlay">
|
||||||
--></div>
|
<div>
|
||||||
|
<div id="modalOverlayContainer"></div>
|
||||||
|
<div id="modalOverlayClose"><svg viewBox="0 0 64 64"><path d="M 16 16 L 48 48 M 16 48 L 48 16" /></svg></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="ruleActionPicker"><div class="allowRule"></div><div class="blockRule"></div></div>
|
</div>
|
||||||
|
|
||||||
|
<div id="templates" style="display: none;">
|
||||||
|
<div id="logEntryTemplate"><div><span></span>​<span>--</span>​<span></span>​<span></span>​<span></span>​<span></span></div></div>
|
||||||
|
|
||||||
|
<div id="netFilteringDialog" data-pane="rule">
|
||||||
|
<div class="headers">
|
||||||
|
 
|
||||||
|
<span class="header" data-pane="rule" data-i18n="loggerEntryRuleHeader"></span>
|
||||||
|
<span class="header" data-pane="details" data-i18n="loggerEntryDetailsHeader"></span>
|
||||||
|
</div>
|
||||||
|
<div class="panes">
|
||||||
|
<div class="pane" data-pane="rule">
|
||||||
|
<iframe></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="pane" data-pane="details">
|
||||||
|
<div><span data-i18n="loggerEntryDetailsContext"></span><span></span></div>
|
||||||
|
<div><span data-i18n="loggerEntryDetailsPartyness"></span><span class="prose"></span></div>
|
||||||
|
<div><span data-i18n="loggerEntryDetailsType"></span><span></span></div>
|
||||||
|
<div><span data-i18n="loggerEntryDetailsURL"></span><span></span></div>
|
||||||
|
<div><span>CNAME</span><span></span></div>
|
||||||
|
<div><span>Original URL</span><span></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="loggerExportDialog">
|
||||||
|
<div class="options">
|
||||||
|
<div data-radio="format">
|
||||||
|
<span data-i18n="loggerExportFormatList" data-radio-item="list"></span>
|
||||||
|
<span data-i18n="loggerExportFormatTable" data-radio-item="table"></span>
|
||||||
|
</div>
|
||||||
|
<div data-radio="encoding">
|
||||||
|
<span data-i18n="loggerExportEncodePlain" data-radio-item="plain"></span>
|
||||||
|
<span data-i18n="loggerExportEncodeMarkdown" data-radio-item="markdown"></span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span data-i18n="genericCopyToClipboard" class="pushbutton"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea class="output" readonly spellcheck="false"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="loggerSettingsDialog">
|
||||||
|
<div><span data-i18n="loggerSettingDiscardPrompt"></span>
|
||||||
|
<ul>
|
||||||
|
<li><label data-i18n="loggerSettingPerEntryMaxAge"><input type="number" min="0" max="50000" /></label>
|
||||||
|
<li><label data-i18n="loggerSettingPerTabMaxLoads"><input type="number" min="0" max="1000000" /></label>
|
||||||
|
<li><label data-i18n="loggerSettingPerTabMaxEntries"><input type="number" min="0" max="1000000" /></label>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div><label data-i18n="loggerSettingPerEntryLineCount"><input type="number" min="2" max="6"></label></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/fa-icons.js"></script>
|
<script src="js/fa-icons.js"></script>
|
||||||
<script src="lib/punycode.js"></script>
|
<script src="js/vapi.js"></script>
|
||||||
<script src="lib/publicsuffixlist.js"></script>
|
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
|
<script src="js/vapi-client-extra.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
<script src="js/i18n.js"></script>
|
<script src="js/i18n.js"></script>
|
||||||
<script src="js/scope-selector.js"></script>
|
|
||||||
<script src="js/logger-ui.js"></script>
|
<script src="js/logger-ui.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/fa-icons.js"></script>
|
<script src="js/fa-icons.js"></script>
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="paneHead">
|
<div class="paneHead">
|
||||||
<a id="gotoDashboard" class="extensionURL" href="#" data-extension-url="dashboard.html" data-i18n-tip="matrixDashboardMenuEntry">uMatrix <span id="version"> </span></a>
|
<div id="gotoDashboard" data-extension-url="dashboard.html" data-i18n-tip="matrixDashboardMenuEntry">uMatrix <span id="version"> </span></div>
|
||||||
<div id="toolbarContainer">
|
<div id="toolbarContainer">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<span class="scope" id="specificScope"><span> </span></span><!--
|
<span class="scope" id="specificScope"><span> </span></span><!--
|
||||||
|
@ -25,16 +25,16 @@
|
||||||
<span id="buttonPersist" class="fa-icon fa-icon-badged scopeRel tip-anchor-center" data-i18n-tip="matrixPersistButtonTip">lock</span>
|
<span id="buttonPersist" class="fa-icon fa-icon-badged scopeRel tip-anchor-center" data-i18n-tip="matrixPersistButtonTip">lock</span>
|
||||||
<span id="buttonRevertScope" class="fa-icon fa-icon-badged scopeRel tip-anchor-center" data-i18n-tip="matrixRevertButtonTip">reply</span>
|
<span id="buttonRevertScope" class="fa-icon fa-icon-badged scopeRel tip-anchor-center" data-i18n-tip="matrixRevertButtonTip">reply</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar needtab">
|
||||||
<span id="buttonReload" class="fa-icon tip-anchor-right" data-i18n-tip="matrixReloadButton">refresh</span>
|
<span id="buttonReload" class="fa-icon tip-anchor-right" data-i18n-tip="matrixReloadButton">refresh</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<span id="buttonRevertAll" class="fa-icon tip-anchor-right" data-i18n-tip="matrixRevertAllEntry">reply-all</span>
|
<span id="buttonRevertAll" class="fa-icon tip-anchor-right needtab" data-i18n-tip="matrixRevertAllEntry">reply-all</span>
|
||||||
<span class="fa-icon extensionURL tip-anchor-right" data-extension-url="logger-ui.html" data-i18n-tip="matrixLoggerMenuEntry">list-alt</span>
|
<span class="fa-icon tip-anchor-right" data-extension-url="logger-ui.html#_" data-i18n-tip="matrixLoggerMenuEntry">list-alt</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="matHead" class="matrix collapsible">
|
<div id="matHead" class="matrix collapsible">
|
||||||
<div class="matRow rw" style="display:none"><div class="matCell" data-req-type="all">all</div><div class="matCell" data-req-type="cookie">cookie</div><div class="matCell" data-req-type="css">css</div><div class="matCell" data-req-type="image">img</div><div class="matCell" data-req-type="media">media</div><div class="matCell" data-req-type="script">script</div><div class="matCell" data-req-type="xhr">XHR</div><div class="matCell" data-req-type="frame">frame</div><div class="matCell" data-req-type="other">other</div></div>
|
<div class="matRow rw" style="display:none"><div class="matCell" data-req-type="all">all</div><div class="matCell" data-req-type="cookie">cookie</div><div class="matCell" data-req-type="css">css</div><div class="matCell" data-req-type="image">img</div><div class="matCell" data-req-type="media">media</div><div class="matCell" data-req-type="script">script</div><div class="matCell" data-req-type="fetch">fetch</div><div class="matCell" data-req-type="frame">frame</div><div class="matCell" data-req-type="other">other</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -68,10 +68,11 @@
|
||||||
<div id="dropDownMenuSwitches" class="dropdown-menu-capture">
|
<div id="dropDownMenuSwitches" class="dropdown-menu-capture">
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<ul id="mtxSwitches">
|
<ul id="mtxSwitches">
|
||||||
<li id="mtxSwitch_https-strict" class="dropdown-menu-entry exists"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g style="fill:#bbb;"><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off" style="fill:#bbb;"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on" style="fill:#bbb;"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchNoMixedContent"></span> <a class="fa-icon" href="https://developer.mozilla.org/docs/Web/Security/Mixed_content" target="_blank">info-circle</a>
|
<li id="mtxSwitch_https-strict" class="dropdown-menu-entry exists"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchNoMixedContent"></span> <a class="fa-icon" href="https://developer.mozilla.org/docs/Web/Security/Mixed_content" target="_blank">info-circle</a>
|
||||||
<li id="mtxSwitch_no-workers" class="dropdown-menu-entry exists"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g style="fill:#bbb;"><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off" style="fill:#bbb;"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on" style="fill:#bbb;"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchNoWorker"></span> <a class="fa-icon" href="https://developer.mozilla.org/docs/Web/API/Web_Workers_API" target="_blank">info-circle</a>
|
<li id="mtxSwitch_no-workers" class="dropdown-menu-entry exists"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchNoWorker"></span> <a class="fa-icon" href="https://developer.mozilla.org/docs/Web/API/Web_Workers_API" target="_blank">info-circle</a>
|
||||||
<li id="mtxSwitch_referrer-spoof" class="dropdown-menu-entry"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g style="fill:#bbb;"><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off" style="fill:#bbb;"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on" style="fill:#bbb;"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchReferrerSpoof"></span> <a class="fa-icon" href="https://developer.mozilla.org/docs/Web/HTTP/Headers/Referer" target="_blank">info-circle</a>
|
<li id="mtxSwitch_referrer-spoof" class="dropdown-menu-entry"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchReferrerSpoof"></span> <a class="fa-icon" href="https://developer.mozilla.org/docs/Web/HTTP/Headers/Referer" target="_blank">info-circle</a>
|
||||||
<li id="mtxSwitch_noscript-spoof" class="dropdown-menu-entry"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g style="fill:#bbb;"><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off" style="fill:#bbb;"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on" style="fill:#bbb;"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchNoscriptSpoof"></span> <a class="fa-icon" href="https://developer.mozilla.org/docs/Web/HTML/Element/noscript" target="_blank">info-circle</a>
|
<li id="mtxSwitch_noscript-spoof" class="dropdown-menu-entry"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchNoscriptSpoof"></span> <a class="fa-icon" href="https://developer.mozilla.org/docs/Web/HTML/Element/noscript" target="_blank">info-circle</a>
|
||||||
|
<li id="mtxSwitch_cname-reveal" class="dropdown-menu-entry"><!-- <svg><use xlink:href="#toggleButton" /></svg> --><svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 152 96"><g><ellipse cx="48" cy="48" rx="24" ry="24" /><ellipse cx="104" cy="48" rx="24" ry="24" /><rect width="56" height="48" x="48" y="24" /></g><g class="off"><ellipse cx="48" cy="48" rx="48" ry="48" /><ellipse style="fill:#fff;" cx="48" cy="48" rx="40" ry="40" /><ellipse class="dot" cx="48" cy="48" rx="12" ry="12" /></g><g class="on"><ellipse style="fill:#444;" cx="104" cy="48" rx="48" ry="48" /><ellipse class="dot" cx="104" cy="48" rx="12" ry="12" /></g></svg><span data-i18n="matrixSwitchRevealCname"></span> <a class="fa-icon" href="https://en.wikipedia.org/wiki/CNAME_record" target="_blank">info-circle</a>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,6 +119,7 @@
|
||||||
|
|
||||||
<script src="js/fa-icons.js"></script>
|
<script src="js/fa-icons.js"></script>
|
||||||
<script src="lib/punycode.js"></script>
|
<script src="lib/punycode.js"></script>
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
|
|
|
@ -3,10 +3,15 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title data-i18n="rawSettingsPageName"></title>
|
<title data-i18n="rawSettingsPageName"></title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="lib/codemirror/lib/codemirror.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="css/fa-icons.css">
|
<link rel="stylesheet" href="css/fa-icons.css">
|
||||||
<link rel="stylesheet" href="css/common.css">
|
<link rel="stylesheet" href="css/common.css">
|
||||||
<link rel="stylesheet" href="css/dashboard-common.css">
|
<link rel="stylesheet" href="css/dashboard-common.css">
|
||||||
<link rel="stylesheet" href="css/raw-settings.css">
|
<link rel="stylesheet" href="css/raw-settings.css">
|
||||||
|
<link rel="stylesheet" href="css/codemirror.css">
|
||||||
|
|
||||||
<link rel="shortcut icon" type="image/png" href="img/icon_16.png"/>
|
<link rel="shortcut icon" type="image/png" href="img/icon_16.png"/>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -18,11 +23,17 @@
|
||||||
<p><button id="rawSettingsApply" class="custom important" type="button" disabled="true" data-i18n="genericApplyChanges"></button> 
|
<p><button id="rawSettingsApply" class="custom important" type="button" disabled="true" data-i18n="genericApplyChanges"></button> 
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<textarea id="rawSettings" dir="auto" spellcheck="false"></textarea>
|
|
||||||
|
|
||||||
</div><!-- end of div.body -->
|
</div><!-- end of div.body -->
|
||||||
|
|
||||||
|
<div id="rawSettings" class="codeMirrorContainer codeMirrorFillVertical"></div>
|
||||||
|
|
||||||
|
<script src="lib/codemirror/lib/codemirror.js"></script>
|
||||||
|
<script src="lib/codemirror/addon/selection/active-line.js"></script>
|
||||||
|
|
||||||
|
<script src="js/codemirror/mode/raw-settings.js"></script>
|
||||||
|
|
||||||
<script src="js/fa-icons.js"></script>
|
<script src="js/fa-icons.js"></script>
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
|
|
|
@ -115,6 +115,7 @@ ul > li.separator {
|
||||||
</div><!-- end of div.body -->
|
</div><!-- end of div.body -->
|
||||||
|
|
||||||
<script src="js/fa-icons.js"></script>
|
<script src="js/fa-icons.js"></script>
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<button type="button" id="editSaveButton" data-i18n="userRulesEditSave"></button>
|
<button type="button" id="editSaveButton" data-i18n="userRulesEditSave"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="ruleFilter"><span class="fa-icon">filter</span> <input type="text" size="32"></div>
|
<div id="ruleFilter"><span class="fa-icon">filter</span> <input type="search" size="32"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="codeMirrorContainer codeMirrorMergeContainer vfill-available"></div>
|
<div class="codeMirrorContainer codeMirrorMergeContainer vfill-available"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,6 +56,7 @@
|
||||||
<script src="lib/codemirror/addon/selection/active-line.js"></script>
|
<script src="lib/codemirror/addon/selection/active-line.js"></script>
|
||||||
|
|
||||||
<script src="js/fa-icons.js"></script>
|
<script src="js/fa-icons.js"></script>
|
||||||
|
<script src="js/vapi.js"></script>
|
||||||
<script src="js/vapi-common.js"></script>
|
<script src="js/vapi-common.js"></script>
|
||||||
<script src="js/vapi-client.js"></script>
|
<script src="js/vapi-client.js"></script>
|
||||||
<script src="js/udom.js"></script>
|
<script src="js/udom.js"></script>
|
||||||
|
|
Loading…
Reference in a new issue