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.pull/2/head
parent
1d936742a7
commit
9b292304d3
@ -1 +1 @@
|
||||
1.4.0
|
||||
1.4.1.0
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
@ -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;
|
@ -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
|
||||
})();
|
@ -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;
|
||||
});
|
||||
}
|
||||
}());
|
||||
|
||||
/******************************************************************************/
|
@ -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
|
@ -1,425 +1,621 @@
|
||||
body {
|
||||
background-color: white;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
color: black;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.fa-icon {
|
||||
cursor: pointer;
|
||||
font-size: 150%;
|
||||
padding: 0.4em 0.6em;
|
||||
width: 100vw;
|
||||
}
|
||||
.fa-icon:hover {
|
||||
background-color: #eee;
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
direction: ltr;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
}
|
||||
#toolbar {
|
||||
.permatoolbar {
|
||||
background-color: white;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
font-size: 120%;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
padding: 0.5em 1em;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
padding: 0.25em;
|
||||
}
|
||||
#toolbar > div {
|
||||
.permatoolbar > div {
|
||||
display: flex;
|
||||
padding: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.permatoolbar .button {
|
||||
cursor: pointer;
|
||||
font-size: 150%;
|
||||
padding: 0.25em;
|
||||
}
|
||||
.permatoolbar .button.active {
|
||||
fill: #5F9EA0;
|
||||
}
|
||||
.permatoolbar .button:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
#pageSelector {
|
||||
padding: 0.25em 0;
|
||||
width: 28em;
|
||||
margin-right: 0.5em;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
body[dir="ltr"] #pageSelector {
|
||||
margin-right: 1em;
|
||||
}
|
||||
body[dir="rtl"] #pageSelector {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
#showpopup {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
#info {
|
||||
fill: #ccc;
|
||||
}
|
||||
#info:hover {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
/*
|
||||
https://github.com/gorhill/uBlock/issues/3293
|
||||
=> https://devhints.io/css-system-font-stack
|
||||
*/
|
||||
#inspectors {
|
||||
flex-grow: 1;
|
||||
font-family: "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
.inspector {
|
||||
border-top: 1px solid #ccc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.vscrollable {
|
||||
direction: ltr;
|
||||
flex-grow: 1;
|
||||
font-size: small;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.inspector:not(.vExpanded) .vCompactToggler.button {
|
||||
transform: scaleY(-1)
|
||||
}
|
||||
.hCompact .hCompactToggler.button {
|
||||
transform: scaleX(-1)
|
||||
}
|
||||
|
||||
@keyframes popupPanelShow {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
#inspectors.dom #netInspector {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#netInspector #pause > span:last-of-type {
|
||||
display: none;
|
||||
}
|
||||
#popupPanelContainer {
|
||||
background: white;
|
||||
border: 1px solid gray;
|
||||
#netInspector.paused #pause > span:first-of-type {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
z-index: 2000;
|
||||
}
|
||||
#netInspector.paused #pause > span:last-of-type {
|
||||
display: inline-flex;
|
||||
fill: #5F9EA0;
|
||||
}
|
||||
#netInspector #filterExprGroup {
|
||||
display: flex;
|
||||
margin: 0 1em;
|
||||
position: relative;
|
||||
}
|
||||
body.popupPanelOn #popupPanelContainer {
|
||||
animation-duration: 0.25s;
|
||||
animation-name: popupPanelShow;
|
||||
display: block;
|
||||
#netInspector #filterButton {
|
||||
opacity: 0.25;
|
||||
}
|
||||
#popupPanelContainer.hide {
|
||||
width: 6em !important;
|
||||
}
|
||||
#popupPanelContainer > iframe {
|
||||
#netInspector.f #filterButton {
|
||||
opacity: 1;
|
||||
}
|
||||
#netInspector #filterInput {
|
||||
border: 1px solid gray;
|
||||
display: inline-flex;
|
||||
}
|
||||
#netInspector #filterInput > input {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
min-width: 16em;
|
||||
}
|
||||
#netInspector #filterExprButton {
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
#netInspector #filterExprButton:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
#netInspector #filterExprButton.expanded {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
#popupPanelContainer.hide > iframe {
|
||||
#netInspector #filterExprPicker {
|
||||
background-color: white;
|
||||
border: 1px solid gray;
|
||||
display: none;
|
||||
position: absolute;
|
||||
flex-direction: column;
|
||||
font-size: small;
|
||||
top: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
body[dir="ltr"] #netInspector #filterExprPicker {
|
||||
right: 0;
|
||||
}
|
||||
body[dir="rtl"] #netInspector #filterExprPicker {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#popupPanelButton use {
|
||||
transform: scale(1, 0.4);
|
||||
#netInspector #filterExprGroup:hover #filterExprButton.expanded ~ #filterExprPicker {
|
||||
display: flex;
|
||||
}
|
||||
body.popupPanelOn #popupPanelButton use {
|
||||
transform: scale(1, 1);
|
||||
#netInspector #filterExprPicker > div {
|
||||
border: 1px dotted #ddd;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
display: flex;
|
||||
padding: 0.5em;
|
||||
}
|
||||
body.compactView #compactViewToggler use {
|
||||
transform: scale(1, -1);
|
||||
transform-origin: center;
|
||||
#netInspector #filterExprPicker > div:first-of-type {
|
||||
border-top: 0;
|
||||
}
|
||||
#filterButton {
|
||||
opacity: 0.25;
|
||||
#netInspector #filterExprPicker > div:last-of-type {
|
||||
border-bottom: 0;
|
||||
}
|
||||
body.f #filterButton {
|
||||
opacity: 1;
|
||||
#netInspector #filterExprPicker div {
|
||||
display: flex;
|
||||
}
|
||||
#filterInput.bad {
|
||||
background-color: #fee;
|
||||
#netInspector #filterExprPicker span[data-filtex] {
|
||||
align-items: center;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
margin: 0 0.5em 0 0;
|
||||
padding: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#maxEntries {
|
||||
margin: 0 2em;
|
||||
#netInspector #filterExprPicker span[data-filtex]:last-of-type {
|
||||
margin: 0;
|
||||
}
|
||||
input:focus {
|
||||
background-color: #ffe;
|
||||
#netInspector #filterExprPicker span[data-filtex]:hover {
|
||||
background-color: aliceblue;
|
||||
border: 1px solid lightblue;
|
||||
}
|
||||
#content {
|
||||
font-family: "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
#netInspector #filterExprPicker span.on[data-filtex] {
|
||||
background-color: lightblue;
|
||||
border: 1px solid lightblue;
|
||||
}
|
||||
|
||||
#content table {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
direction: ltr;
|
||||
table-layout: fixed;
|
||||
#netInspector .vscrollable {
|
||||
overflow: hidden;
|
||||
}
|
||||
#vwRenderer {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
#content table > colgroup > col:nth-of-type(1) {
|
||||
width: 4.6em;
|
||||
#vwRenderer #vwScroller {
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
#content table > colgroup > col:nth-of-type(2) {
|
||||
width: 25%;
|
||||
#vwRenderer #vwScroller #vwVirtualContent {
|
||||
overflow: hidden;
|
||||
}
|
||||
#content table > colgroup > col:nth-of-type(3) {
|
||||
width: 2.2em;
|
||||
#vwRenderer #vwContent {
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
#content table > colgroup > col:nth-of-type(4) {
|
||||
width: 5.4em;
|
||||
#vwRenderer .logEntry {
|
||||
display: block;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
#content table > colgroup > col:nth-of-type(5) {
|
||||
width: calc(100% - 4.6em - 25% - 2.2em - 5.4em - 1.8em);
|
||||
#vwRenderer .logEntry:empty {
|
||||
display: none;
|
||||
}
|
||||
#content table > colgroup > col:nth-of-type(6) {
|
||||
width: 1.8em;
|
||||
#vwRenderer .logEntry > div {
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#content table tr {
|
||||
background-color: #fafafa;
|
||||
#vwRenderer .logEntry > div[data-status="--"] {
|
||||
background-color: rgba(192, 0, 0, 0.1);
|
||||
}
|
||||
body.f table tr.f {
|
||||
display: none;
|
||||
body.colorBlind #vwRenderer .logEntry > div[data-status="--"] {
|
||||
background-color: rgba(0, 19, 110, 0.1);
|
||||
}
|
||||
#content table tr:nth-of-type(2n+1) {
|
||||
background-color: #eee;
|
||||
#vwRenderer .logEntry > div[data-status="3"] {
|
||||
background-color: rgba(108, 108, 108, 0.1);
|
||||
}
|
||||
|
||||
#content table tr.cat_info {
|
||||
color: #00f;
|
||||
body.colorBlind #vwRenderer .logEntry > div[data-status="3"] {
|
||||
background-color: rgba(96, 96, 96, 0.1);
|
||||
}
|
||||
#vwRenderer .logEntry > div[data-status="++"] {
|
||||
background-color: rgba(0, 160, 0, 0.1);
|
||||
}
|
||||
body.colorBlind #vwRenderer .logEntry > div[data-status="++"] {
|
||||
background-color: rgba(255, 194, 57, 0.1)
|
||||
}
|
||||
#vwRenderer .logEntry > div[data-tabid="-1"] {
|
||||
text-shadow: 0 0.2em 0.4em #aaa;
|
||||
}
|
||||
#content table tr.blocked {
|
||||
color: #f00;
|
||||
#vwRenderer .logEntry > div[data-aliasid] {
|
||||
color: mediumblue;
|
||||
}
|
||||
#content table tr.doc {
|
||||
#vwRenderer .logEntry > div[data-type="tabLoad"] {
|
||||
background-color: #666;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
#vwRenderer .logEntry > div[data-type="error"] {
|
||||
color: #800;
|
||||
}
|
||||
#vwRenderer .logEntry > div[data-type="info"] {
|
||||
color: #008;
|
||||
}
|
||||
#vwRenderer .logEntry > div.voided {
|
||||
opacity: 0.3;
|
||||
}
|
||||
#vwRenderer .logEntry > div.voided:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
body #content td {
|
||||
#vwRenderer .logEntry > div > span {
|
||||
border: 1px solid #ccc;
|
||||
min-width: 0.5em;
|
||||
padding: 3px;
|
||||
vertical-align: top;
|
||||
white-space: normal;
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0.2em;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
#content table tr td:first-of-type {
|
||||
border-left: none;
|
||||
#vwRenderer .logEntry > div.canDetails:hover > span {
|
||||
background-color: rgba(0,0,0,0.04);
|
||||
}
|
||||
#content table tr td:last-of-type {
|
||||
border-right: none;
|
||||
body[dir="ltr"] #vwRenderer .logEntry > div > span:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
body.compactView #content tr:not(.vExpanded) td {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
body[dir="rtl"] #vwRenderer .logEntry > div > span:first-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
#content table tr td:nth-of-type(1) {
|
||||
cursor: default;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(1) {
|
||||
text-align: center;
|
||||
}
|
||||
#content table tr td:nth-of-type(2):not([colspan]) {
|
||||
direction: rtl;
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(2) {
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.vExpanded #vwRenderer .logEntry > div > span:nth-of-type(2) {
|
||||
overflow-y: auto;
|
||||
white-space: pre-line;
|
||||
}
|
||||
#netInspector.vExpanded #vwRenderer .logEntry > div > span:nth-of-type(2) {
|
||||
text-align: left;
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
#content table tr[data-tabid="-1"] td:nth-of-type(2):not([colspan]) {
|
||||
position: relative;
|
||||
#vwRenderer .logEntry > div:not(.messageRealm) > span:nth-of-type(2) {
|
||||
direction: rtl;
|
||||
}
|
||||
#vwRenderer .logEntry > div.messageRealm > span:nth-of-type(2) {
|
||||
color: blue;
|
||||
text-align: left;
|
||||
}
|
||||
#vwRenderer .logEntry > div.messageRealm[data-type="tabLoad"] > span:nth-of-type(2) {
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
#vwRenderer .logEntry > div.messageRealm > span:nth-of-type(2) ~ span {
|
||||
display: none;
|
||||
}
|
||||
#content table tr td:nth-of-type(3) {
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(3) {
|
||||
text-align: center;
|
||||
}
|
||||
#vwRenderer #vwContent .logEntry > div > span:nth-of-type(4) {
|
||||
color: #888;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
/* visual for tabless network requests */
|
||||
#content table tr[data-tabid="-1"] td:nth-of-type(3)::before {
|
||||
border: 5px solid #bbb;
|
||||
border-bottom: 0;
|
||||
border-top: 0;
|
||||
bottom: 0;
|
||||
content: '\00a0';
|
||||
left: 0;
|
||||
#vwRenderer #vwContent .logEntry > div[data-header] > span:nth-of-type(4) {
|
||||
color: black;
|
||||
}
|
||||
.vExpanded #vwRenderer #vwContent .logEntry > div > span:nth-of-type(4) {
|
||||
overflow-y: auto;
|
||||
text-overflow: clip;
|
||||
white-space: pre-line;
|
||||
}
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(4) b {
|
||||
color: black;
|
||||
font-weight: normal;
|
||||
}
|
||||
#vwRenderer .logEntry > div[data-aliasid] > span:nth-of-type(4) b {
|
||||
color: mediumblue;
|
||||
}
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(4) a {
|
||||
background-color: dimgray;
|
||||
color: white;
|
||||
display: none;
|
||||
height: 100%;
|
||||
padding: 0 0.25em;
|
||||
opacity: 0.4;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-decoration: none;
|
||||
top: 0;
|
||||
width: calc(100% - 10px);
|
||||
}
|
||||
#content table tr.tab:not(.canMtx) {
|
||||
opacity: 0.3;
|
||||
#netInspector.vExpanded #vwRenderer .logEntry > div > span:nth-of-type(4) a {
|
||||
bottom: 0px;
|
||||
height: unset;
|
||||
padding: 0.25em;
|
||||
top: unset;
|
||||
}
|
||||
#content table tr.tab:not(.canMtx):hover {
|
||||
opacity: 0.7;
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(4) a::after {
|
||||
content: '\2197';
|
||||
}
|
||||
#content table tr.cat_net td:nth-of-type(3) {
|
||||
cursor: pointer;
|
||||
#vwRenderer .logEntry > div.networkRealm > span:nth-of-type(4):hover a {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
}
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(4) a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(5) {
|
||||
text-align: right;
|
||||
}
|
||||
/* visual for tabless network requests */
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(5) {
|
||||
}
|
||||
#vwRenderer .logEntry > div > span:nth-of-type(6) {
|
||||
font: 12px monospace;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#content table tr.cat_net td:nth-of-type(5) {
|
||||
#vwRenderer .logEntry > div.canDetails:hover > span:nth-of-type(6) {
|
||||
background: rgba(0, 0, 255, 0.1);
|
||||
cursor: zoom-in;
|
||||
}
|
||||
#content table tr.cat_net td:nth-of-type(5) > span > * {
|
||||
opacity: 0.6;
|
||||
|
||||
#vwRenderer #vwBottom {
|
||||
background-color: #00F;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
#content table tr.cat_net td:nth-of-type(5) > span > b:first-of-type {
|
||||
opacity: 1;
|
||||
#vwRenderer #vwLineSizer {
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
visibility: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#popupContainer {
|
||||
background: white;
|
||||
border: 1px solid gray;
|
||||
bottom: 0;
|
||||
box-sizing: content-box;
|
||||
display: none;
|
||||
max-height: 75vh;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
z-index: 200;
|
||||
}
|
||||
#inspectors.popupOn #popupContainer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modalDialog {
|
||||
#modalOverlay {
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
border: 0;
|
||||
bottom: 0;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
z-index: 5000;
|
||||
z-index: 400;
|
||||
}
|
||||
.modalDialog > .dialog {
|
||||
background-color: white;
|
||||
font: 15px httpsb,sans-serif;
|
||||
min-width: fit-content;
|
||||
padding: 0.5em;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
#ruleEditor section {
|
||||
#modalOverlay.on {
|
||||
display: flex;
|
||||
}
|
||||
.scopeWidget {
|
||||
line-height: 2.5em;
|
||||
margin-bottom: 0.5em;
|
||||
#modalOverlay > div {
|
||||
position: relative;
|
||||
}
|
||||
#specificScope, .ruleCell:nth-of-type(1) {
|
||||
flex-grow: 1;
|
||||
#modalOverlay > div > div:nth-of-type(1) {
|
||||
background-color: white;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 1em;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
width: 90vw;
|
||||
}
|
||||
#modalOverlay > div > div:nth-of-type(2) {
|
||||
stroke: #000;
|
||||
stroke-width: 3px;
|
||||
position: absolute;
|
||||
width: 1.6em;
|
||||
height: 1.6em;
|
||||
bottom: calc(100% + 2px);
|
||||
background-color: white;
|
||||
}
|
||||
#globalScope, .ruleCell:nth-of-type(2) {
|
||||
width: 4em;
|
||||
body[dir="ltr"] #modalOverlay > div > div:nth-of-type(2) {
|
||||
right: 0;
|
||||
}
|
||||
.ruleEditorToolbar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
margin-left: 0.5em;
|
||||
padding: 0.2em;
|
||||
body[dir="rtl"] #modalOverlay > div > div:nth-of-type(2) {
|
||||
left: 0;
|
||||
}
|
||||
.ruleEditorToolbar .fa-icon {
|
||||
padding: 0.4em;
|
||||
#modalOverlay > div > div:nth-of-type(2):hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.fa-icon.scopeRel {
|
||||
color: #24c;
|
||||
fill: #24c;
|
||||
#modalOverlay > div > div:nth-of-type(2) > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
body[data-scope="*"] .fa-icon.scopeRel {
|
||||
color: #000;
|
||||
fill: #000;
|
||||
|
||||
#netFilteringDialog {
|
||||
font-size: 95%;
|
||||
}
|
||||
.ruleWidgets {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
#netFilteringDialog a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.ruleRow {
|
||||
display: flex;
|
||||
line-height: 2em;
|
||||
margin-top: 1px;
|
||||
#netFilteringDialog > .headers {
|
||||
border-bottom: 1px solid #888;
|
||||
line-height: 2;
|
||||
position: relative;
|
||||
}
|
||||
.ruleCell {
|
||||
#netFilteringDialog > .headers > .header {
|
||||
background-color: #eee;
|
||||
border: 1px dotted rgba(0,0,0,0.2);
|
||||
border: 1px solid #aaa;
|
||||
border-bottom: 1px solid #888;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-left: 1px;
|
||||
padding: 1px;
|
||||
padding: 0 1em;
|
||||
position: relative;
|
||||
}
|
||||
.ruleCell:hover {
|
||||
border-style: solid;
|
||||
}
|
||||
.ruleCell:nth-of-type(1) {
|
||||
margin-left: 0;
|
||||
text-align: right;
|
||||
}
|
||||
.ruleCell:nth-of-type(2) {
|
||||
text-align: center;
|
||||
top: 1px;
|
||||
}
|
||||
.ruleCell[data-tcolor="1"] {
|
||||
border-color: #debaba;
|
||||
color: black;
|
||||
background-color: #f8d0d0;
|
||||
}
|
||||
#ruleEditor.colorblind .ruleCell[data-tcolor="1"] {
|
||||
border-color: rgba(0, 19, 110, 0.3);
|
||||
#netFilteringDialog[data-pane="details"] > .headers > [data-pane="details"],
|
||||
#netFilteringDialog[data-pane="rule"] > .headers > [data-pane="rule"] {
|
||||
background-color: white;
|
||||
border-color: #888;
|
||||
border-bottom: 1px solid white;
|
||||
color: black;
|
||||
background-color: rgba(0, 19, 110, 0.2);
|
||||
}
|
||||
.ruleCell[data-tcolor="2"] {
|
||||
border-color: #bad6ba;
|
||||
color: black;
|
||||
background-color: #d0f0d0;
|
||||
#netFilteringDialog > div.panes {
|
||||
height: 50vh;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
padding-top: 1em;
|
||||
}
|
||||
#ruleEditor.colorblind .ruleCell[data-tcolor="2"] {
|
||||
border-color: rgba(255, 194, 57, 0.3);
|
||||
color: black;
|
||||
background-color: rgba(255, 194, 57, 0.2);
|
||||
#netFilteringDialog > div.panes > div {
|
||||
display: none;
|
||||
height: 100%;
|
||||
}
|
||||
.ruleCell[data-tcolor="129"] {
|
||||
color: white;
|
||||
background-color: #c00;
|
||||
#netFilteringDialog[data-pane="details"] > .panes > [data-pane="details"],
|
||||
#netFilteringDialog[data-pane="rule"] > .panes > [data-pane="rule"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#ruleEditor.colorblind .ruleCell[data-tcolor="129"] {
|
||||
color: white;
|
||||
background-color: rgb(0, 19, 110);
|
||||
#netFilteringDialog > .panes > [data-pane="details"] > div {
|
||||
align-items: stretch;
|
||||
background-color: #e6e6e6;
|
||||
border: 0;
|
||||
border-bottom: 1px solid white;
|
||||
display: flex;
|
||||
}
|
||||
.ruleCell[data-tcolor="130"] {
|
||||
color: white;
|
||||
background-color: #080;
|
||||
#netFilteringDialog > .panes > [data-pane="details"] > div > span {
|
||||
padding: 0.5em;
|
||||
}
|
||||
#ruleEditor.colorblind .ruleCell[data-tcolor="130"] {
|
||||
border-color: rgb(255, 194, 57);
|
||||
color: black;
|
||||
background-color: rgb(255, 194, 57);
|
||||
#netFilteringDialog > .panes > [data-pane="details"] > div > span:nth-of-type(1) {
|
||||
border: 0;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
text-align: right;
|
||||
width: 8em;
|
||||
}
|
||||
.ruleCell[data-pcolor="129"] {
|
||||
background-image: url('../img/permanent-black-small.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: -1px -1px;
|
||||
body[dir="ltr"] #netFilteringDialog > .panes > [data-pane="details"] > div > span:nth-of-type(1) {
|
||||
border-right: 1px solid white;
|
||||
}
|
||||
#ruleEditor.colorblind .ruleCell[data-pcolor="129"] {
|
||||
background-image: url('../img/permanent-black-small-cb.png');
|
||||
body[dir="rtl"] #netFilteringDialog > .panes > [data-pane="details"] > div > span:nth-of-type(1) {
|
||||
border-left: 1px solid white;
|
||||
}
|
||||
.ruleCell[data-pcolor="130"] {
|
||||
background-image: url('../img/permanent-white-small.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: -1px -1px;
|
||||
#netFilteringDialog > .panes > [data-pane="details"] > div > span:nth-of-type(2) {
|
||||
flex-grow: 1;
|
||||
max-height: 20vh;
|
||||
overflow: hidden auto;
|
||||
white-space: pre-line
|
||||
}
|
||||
#ruleEditor.colorblind .ruleCell[data-pcolor="130"] {
|
||||
background-image: url('../img/permanent-white-small-cb.png');
|
||||
#netFilteringDialog > .panes > [data-pane="details"] > div > span:nth-of-type(2):not(.prose) {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#ruleActionPicker {
|
||||
#netFilteringDialog > .panes > [data-pane="rule"] iframe {
|
||||
border: 0;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
.allowRule, .blockRule {
|
||||
|
||||
#loggerExportDialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#loggerExportDialog .options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#loggerExportDialog .options > div {
|
||||
display: inline-flex;
|
||||
}
|
||||
#loggerExportDialog .options span[data-i18n] {
|
||||
border: 1px solid lightblue;
|
||||
cursor: pointer;
|
||||
font-size: 90%;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
background: transparent;
|
||||
padding: 0.5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.allowRule {
|
||||
top: 0;
|
||||
#loggerExportDialog .options span[data-i18n]:hover {
|
||||
background-color: aliceblue;
|
||||
}
|
||||
.blockRule {
|
||||
top: 50%;
|
||||
#loggerExportDialog .options span.on[data-i18n],
|
||||
#loggerExportDialog .options span.pushbutton:active {
|
||||
background-color: lightblue;
|
||||
}
|
||||
.ruleCell[data-tcolor="1"] .allowRule:hover {
|
||||
background-color: #080;
|
||||
opacity: 0.25;
|
||||
#loggerExportDialog .output {
|
||||
font: smaller mono;
|
||||
height: 60vh;
|
||||
padding: 0.5em;
|
||||
white-space: pre;
|
||||
}
|
||||
.ruleCell[data-tcolor="1"] .blockRule:hover {
|
||||
background-color: #c00;
|
||||
opacity: 0.25;
|
||||
|
||||
#loggerSettingsDialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.ruleCell[data-tcolor="2"] .allowRule:hover {
|
||||
background-color: #080;
|
||||
opacity: 0.25;
|
||||
#loggerSettingsDialog > div {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
.ruleCell[data-tcolor="2"] .blockRule:hover {
|
||||
background-color: #c00;
|
||||
opacity: 0.25;
|
||||
#loggerSettingsDialog > div:last-of-type {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.ruleCell[data-tcolor="129"] .allowRule:hover {
|
||||
background-color: transparent;
|
||||
#loggerSettingsDialog ul {
|
||||
padding: 0;
|
||||
}
|
||||
.ruleCell[data-tcolor="129"] .blockRule:hover {
|
||||
background-color: transparent;
|
||||
body[dir="ltr"] #loggerSettingsDialog ul {
|
||||
padding-left: 2em;
|
||||
}
|
||||
.ruleCell[data-pcolor="130"] .allowRule:hover {
|
||||
background-color: transparent;
|
||||
body[dir="rtl"] #loggerSettingsDialog ul {
|
||||
padding-right: 2em;
|
||||
}
|
||||
.ruleCell[data-pcolor="130"] .blockRule:hover {
|
||||
background-color: transparent;
|
||||
#loggerSettingsDialog li {
|
||||
list-style-type: none;
|
||||
margin: 0.5em 0 0 0;
|
||||
}
|
||||
#ruleEditor.colorblind .ruleCell[data-tcolor="1"] .allowRule:hover,
|
||||
#ruleEditor.colorblind .ruleCell[data-tcolor="2"] .allowRule:hover {
|
||||
background-color: rgb(255, 194, 57);
|
||||
opacity: 0.6;
|
||||
#loggerSettingsDialog input {
|
||||
max-width: 6em;
|
||||
}
|
||||
#ruleEditor.colorblind .ruleCell[data-tcolor="1"] .blockRule:hover,
|
||||
#ruleEditor.colorblind .ruleCell[data-tcolor="2"] .blockRule:hover {
|
||||
background-color: rgb(0, 19, 110);
|
||||
opacity: 0.4;
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
div.body {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
html {
|
||||
height: 100vh;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
p {
|
||||
margin: 0.5em 0;
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
flex-grow: 1;
|
||||
resize: none;
|
||||
#rawSettings {
|
||||
border-top: 1px solid #ddd;
|
||||
height: 75vh;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
width: 100%;
|
||||
word-wrap: normal;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 26 KiB |
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}());
|
||||
|
||||
/******************************************************************************/
|
@ -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;
|
||||
}
|
||||
};
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
@ -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,
|
||||
};
|
||||
})();
|
@ -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();
|
@ -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
|
||||
// *****************************************************************************
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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.
|
Binary file not shown.
@ -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
|
||||
;;
|
||||
)
|
@ -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
|
||||
}
|
||||
});
|
@ -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);
|
||||
};
|
||||
});
|
@ -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;
|
||||
}
|
@ -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();
|
||||
};
|
||||
});
|
@ -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)
|
||||
})
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@ -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>.
|
@ -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
|
||||
|
||||
/******************************************************************************/
|
@ -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
|
||||
|
||||
/******************************************************************************/
|
@ -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
|
||||
|
||||
/******************************************************************************/
|
Binary file not shown.
@ -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);
|
||||
|
@ -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);
|
@ -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/
|
Binary file not shown.
@ -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
|
||||
;;
|
||||
)
|
Loading…
Reference in New Issue