Browse Source
Bring uMatrix up to date
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
Raymond Hill
5 years ago
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
91 changed files with 24736 additions and 16318 deletions
-
4.jshintrc
-
2dist/version
-
2platform/chromium/manifest.json
-
1841platform/chromium/vapi-background.js
-
308platform/chromium/vapi-client-extra.js
-
273platform/chromium/vapi-client.js
-
237platform/chromium/vapi-common.js
-
230platform/chromium/vapi-webrequest.js
-
86platform/chromium/vapi.js
-
176platform/chromium/webext.js
-
1platform/firefox/manifest.json
-
263platform/firefox/vapi-cachestorage.js
-
316platform/firefox/vapi-webrequest.js
-
24platform/firefox/webext.js
-
187src/_locales/en/messages.json
-
7src/about.html
-
33src/asset-viewer.html
-
23src/background.html
-
68src/css/codemirror.css
-
34src/css/common.css
-
6src/css/dashboard.css
-
778src/css/logger-ui.css
-
62src/css/popup.css
-
22src/css/raw-settings.css
-
9src/dashboard.html
-
2src/hosts-files.html
-
3src/img/fontawesome/fontawesome-defs.svg
-
124src/js/about.js
-
51src/js/asset-viewer.js
-
1113src/js/assets.js
-
49src/js/background.js
-
25src/js/browsercache.js
-
465src/js/cachestorage.js
-
104src/js/cloud-ui.js
-
37src/js/codemirror/mode/raw-settings.js
-
336src/js/codemirror/search.js
-
34src/js/console.js
-
39src/js/contentscript-start.js
-
192src/js/contentscript.js
-
293src/js/cookies.js
-
133src/js/dashboard-common.js
-
56src/js/dashboard.js
-
310src/js/filtering-context.js
-
760src/js/hntrie.js
-
200src/js/hosts-files.js
-
9src/js/httpsb.js
-
228src/js/i18n.js
-
4src/js/liquid-dict.js
-
2722src/js/logger-ui.js
-
36src/js/logger.js
-
210src/js/lz4.js
-
56src/js/main-blocked.js
-
310src/js/matrix.js
-
776src/js/messaging.js
-
133src/js/pagestats.js
-
1117src/js/popup.js
-
112src/js/raw-settings.js
-
114src/js/settings.js
-
120src/js/start.js
-
908src/js/storage.js
-
549src/js/tab.js
-
541src/js/traffic.js
-
171src/js/uritools.js
-
240src/js/user-rules.js
-
128src/js/utils.js
-
24src/js/wasm/README.md
-
BINsrc/js/wasm/hntrie.wasm
-
710src/js/wasm/hntrie.wat
-
127src/lib/codemirror/addon/display/panel.js
-
122src/lib/codemirror/addon/scroll/annotatescrollbar.js
-
8src/lib/codemirror/addon/search/matchesonscrollbar.css
-
97src/lib/codemirror/addon/search/matchesonscrollbar.js
-
293src/lib/codemirror/addon/search/searchcursor.js
-
18878src/lib/codemirror/lib/codemirror.js
-
52src/lib/lz4/README.md
-
151src/lib/lz4/lz4-block-codec-any.js
-
297src/lib/lz4/lz4-block-codec-js.js
-
195src/lib/lz4/lz4-block-codec-wasm.js
-
BINsrc/lib/lz4/lz4-block-codec.wasm
-
745src/lib/lz4/lz4-block-codec.wat
-
343src/lib/publicsuffixlist.js
-
647src/lib/publicsuffixlist/publicsuffixlist.js
-
29src/lib/publicsuffixlist/wasm/README.md
-
BINsrc/lib/publicsuffixlist/wasm/publicsuffixlist.wasm
-
317src/lib/publicsuffixlist/wasm/publicsuffixlist.wat
-
171src/logger-ui.html
-
1src/main-blocked.html
-
20src/popup.html
-
15src/raw-settings.html
-
1src/settings.html
-
3src/user-rules.html
@ -1 +1 @@ |
|||
1.4.0 |
|||
1.4.1.0 |
1841
platform/chromium/vapi-background.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,308 @@ |
|||
/******************************************************************************* |
|||
|
|||
uBlock Origin - a browser extension to block requests. |
|||
Copyright (C) 2019-present Raymond Hill |
|||
|
|||
This program is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|||
|
|||
Home: https://github.com/gorhill/uBlock
|
|||
*/ |
|||
|
|||
// For non-background page
|
|||
|
|||
'use strict'; |
|||
|
|||
/******************************************************************************/ |
|||
|
|||
// Direct messaging connection ability
|
|||
|
|||
(( ) => { |
|||
// >>>>>>>> start of private namespace
|
|||
|
|||
if ( |
|||
typeof vAPI !== 'object' || |
|||
vAPI.messaging instanceof Object === false || |
|||
vAPI.MessagingConnection instanceof Function |
|||
) { |
|||
return; |
|||
} |
|||
|
|||
const listeners = new Set(); |
|||
const connections = new Map(); |
|||
|
|||
vAPI.MessagingConnection = class { |
|||
constructor(handler, details) { |
|||
this.messaging = vAPI.messaging; |
|||
this.handler = handler; |
|||
this.id = details.id; |
|||
this.to = details.to; |
|||
this.toToken = details.toToken; |
|||
this.from = details.from; |
|||
this.fromToken = details.fromToken; |
|||
this.checkTimer = undefined; |
|||
// On Firefox it appears ports are not automatically disconnected
|
|||
// when navigating to another page.
|
|||
const ctor = vAPI.MessagingConnection; |
|||
if ( ctor.pagehide !== undefined ) { return; } |
|||
ctor.pagehide = ( ) => { |
|||
for ( const connection of connections.values() ) { |
|||
connection.disconnect(); |
|||
connection.handler( |
|||
connection.toDetails('connectionBroken') |
|||
); |
|||
} |
|||
}; |
|||
window.addEventListener('pagehide', ctor.pagehide); |
|||
} |
|||
toDetails(what, payload) { |
|||
return { |
|||
what: what, |
|||
id: this.id, |
|||
from: this.from, |
|||
fromToken: this.fromToken, |
|||
to: this.to, |
|||
toToken: this.toToken, |
|||
payload: payload |
|||
}; |
|||
} |
|||
disconnect() { |
|||
if ( this.checkTimer !== undefined ) { |
|||
clearTimeout(this.checkTimer); |
|||
this.checkTimer = undefined; |
|||
} |
|||
connections.delete(this.id); |
|||
const port = this.messaging.getPort(); |
|||
if ( port === null ) { return; } |
|||
port.postMessage({ |
|||
channel: 'vapi', |
|||
msg: this.toDetails('connectionBroken'), |
|||
}); |
|||
} |
|||
checkAsync() { |
|||
if ( this.checkTimer !== undefined ) { |
|||
clearTimeout(this.checkTimer); |
|||
} |
|||
this.checkTimer = vAPI.setTimeout( |
|||
( ) => { this.check(); }, |
|||
499 |
|||
); |
|||
} |
|||
check() { |
|||
this.checkTimer = undefined; |
|||
if ( connections.has(this.id) === false ) { return; } |
|||
const port = this.messaging.getPort(); |
|||
if ( port === null ) { return; } |
|||
port.postMessage({ |
|||
channel: 'vapi', |
|||
msg: this.toDetails('connectionCheck'), |
|||
}); |
|||
this.checkAsync(); |
|||
} |
|||
receive(details) { |
|||
switch ( details.what ) { |
|||
case 'connectionAccepted': |
|||
this.toToken = details.toToken; |
|||
this.handler(details); |
|||
this.checkAsync(); |
|||
break; |
|||
case 'connectionBroken': |
|||
connections.delete(this.id); |
|||
this.handler(details); |
|||
break; |
|||
case 'connectionMessage': |
|||
this.handler(details); |
|||
this.checkAsync(); |
|||
break; |
|||
case 'connectionCheck': |
|||
const port = this.messaging.getPort(); |
|||
if ( port === null ) { return; } |
|||
if ( connections.has(this.id) ) { |
|||
this.checkAsync(); |
|||
} else { |
|||
details.what = 'connectionBroken'; |
|||
port.postMessage({ channel: 'vapi', msg: details }); |
|||
} |
|||
break; |
|||
case 'connectionRefused': |
|||
connections.delete(this.id); |
|||
this.handler(details); |
|||
break; |
|||
} |
|||
} |
|||
send(payload) { |
|||
const port = this.messaging.getPort(); |
|||
if ( port === null ) { return; } |
|||
port.postMessage({ |
|||
channel: 'vapi', |
|||
msg: this.toDetails('connectionMessage', payload), |
|||
}); |
|||
} |
|||
|
|||
static addListener(listener) { |
|||
listeners.add(listener); |
|||
} |
|||
static async connectTo(from, to, handler) { |
|||
const port = vAPI.messaging.getPort(); |
|||
if ( port === null ) { return; } |
|||
const connection = new vAPI.MessagingConnection(handler, { |
|||
id: `${from}-${to}-${vAPI.sessionId}`, |
|||
to: to, |
|||
from: from, |
|||
fromToken: port.name |
|||
}); |
|||
connections.set(connection.id, connection); |
|||
port.postMessage({ |
|||
channel: 'vapi', |
|||
msg: { |
|||
what: 'connectionRequested', |
|||
id: connection.id, |
|||
from: from, |
|||
fromToken: port.name, |
|||
to: to, |
|||
} |
|||
}); |
|||
return connection.id; |
|||
} |
|||
static disconnectFrom(connectionId) { |
|||
const connection = connections.get(connectionId); |
|||
if ( connection === undefined ) { return; } |
|||
connection.disconnect(); |
|||
} |
|||
static sendTo(connectionId, payload) { |
|||
const connection = connections.get(connectionId); |
|||
if ( connection === undefined ) { return; } |
|||
connection.send(payload); |
|||
} |
|||
static canDestroyPort() { |
|||
return listeners.length === 0 && connections.size === 0; |
|||
} |
|||
static mustDestroyPort() { |
|||
if ( connections.size === 0 ) { return; } |
|||
for ( const connection of connections.values() ) { |
|||
connection.receive({ what: 'connectionBroken' }); |
|||
} |
|||
connections.clear(); |
|||
} |
|||
static canProcessMessage(details) { |
|||
if ( details.channel !== 'vapi' ) { return; } |
|||
switch ( details.msg.what ) { |
|||
case 'connectionAccepted': |
|||
case 'connectionBroken': |
|||
case 'connectionCheck': |
|||
case 'connectionMessage': |
|||
case 'connectionRefused': { |
|||
const connection = connections.get(details.msg.id); |
|||
if ( connection === undefined ) { break; } |
|||
connection.receive(details.msg); |
|||
return true; |
|||
} |
|||
case 'connectionRequested': |
|||
if ( listeners.length === 0 ) { return; } |
|||
const port = vAPI.messaging.getPort(); |
|||
if ( port === null ) { break; } |
|||
let listener, result; |
|||
for ( listener of listeners ) { |
|||
result = listener(details.msg); |
|||
if ( result !== undefined ) { break; } |
|||
} |
|||
if ( result === undefined ) { break; } |
|||
if ( result === true ) { |
|||
details.msg.what = 'connectionAccepted'; |
|||
details.msg.toToken = port.name; |
|||
const connection = new vAPI.MessagingConnection( |
|||
listener, |
|||
details.msg |
|||
); |
|||
connections.set(connection.id, connection); |
|||
} else { |
|||
details.msg.what = 'connectionRefused'; |
|||
} |
|||
port.postMessage(details); |
|||
return true; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
}; |
|||
|
|||
vAPI.messaging.extensions.push(vAPI.MessagingConnection); |
|||
|
|||
// <<<<<<<< end of private namespace
|
|||
})(); |
|||
|
|||
/******************************************************************************/ |
|||
|
|||
// Broadcast listening ability
|
|||
|
|||
(( ) => { |
|||
// >>>>>>>> start of private namespace
|
|||
|
|||
if ( |
|||
typeof vAPI !== 'object' || |
|||
vAPI.messaging instanceof Object === false || |
|||
vAPI.broadcastListener instanceof Object |
|||
) { |
|||
return; |
|||
} |
|||
|
|||
const listeners = new Set(); |
|||
|
|||
vAPI.broadcastListener = { |
|||
add: function(listener) { |
|||
listeners.add(listener); |
|||
vAPI.messaging.getPort(); |
|||
}, |
|||
remove: function(listener) { |
|||
listeners.delete(listener); |
|||
}, |
|||
canDestroyPort() { |
|||
return listeners.size === 0; |
|||
}, |
|||
mustDestroyPort() { |
|||
listeners.clear(); |
|||
}, |
|||
canProcessMessage(details) { |
|||
if ( details.broadcast === false ) { return; } |
|||
for ( const listener of listeners ) { |
|||
listener(details.msg); |
|||
} |
|||
}, |
|||
}; |
|||
|
|||
vAPI.messaging.extensions.push(vAPI.broadcastListener); |
|||
|
|||
// <<<<<<<< end of private namespace
|
|||
})(); |
|||
|
|||
/******************************************************************************/ |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
/******************************************************************************* |
|||
|
|||
DO NOT: |
|||
- Remove the following code |
|||
- Add code beyond the following code |
|||
Reason: |
|||
- https://github.com/gorhill/uBlock/pull/3721
|
|||
- uBO never uses the return value from injected content scripts |
|||
|
|||
**/ |
|||
|
|||
void 0; |
@ -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; |
|||
} |
1113
src/js/assets.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,465 @@ |
|||
/******************************************************************************* |
|||
|
|||
uBlock Origin - a browser extension to block requests. |
|||
Copyright (C) 2016-present The uBlock Origin authors |
|||
|
|||
This program is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|||
|
|||
Home: https://github.com/gorhill/uBlock
|
|||
*/ |
|||
|
|||
/* global IDBDatabase, indexedDB */ |
|||
|
|||
'use strict'; |
|||
|
|||
/******************************************************************************/ |
|||
|
|||
// The code below has been originally manually imported from:
|
|||
// Commit: https://github.com/nikrolls/uBlock-Edge/commit/d1538ea9bea89d507219d3219592382eee306134
|
|||
// Commit date: 29 October 2016
|
|||
// Commit author: https://github.com/nikrolls
|
|||
// Commit message: "Implement cacheStorage using IndexedDB"
|
|||
|
|||
// The original imported code has been subsequently modified as it was not
|
|||
// compatible with Firefox.
|
|||
// (a Promise thing, see https://github.com/dfahlander/Dexie.js/issues/317)
|
|||
// Furthermore, code to migrate from browser.storage.local to vAPI.storage
|
|||
// has been added, for seamless migration of cache-related entries into
|
|||
// indexedDB.
|
|||
|
|||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1371255
|
|||
// Firefox-specific: we use indexedDB because browser.storage.local() has
|
|||
// poor performance in Firefox.
|
|||
// https://github.com/uBlockOrigin/uBlock-issues/issues/328
|
|||
// Use IndexedDB for Chromium as well, to take advantage of LZ4
|
|||
// compression.
|
|||
// https://github.com/uBlockOrigin/uBlock-issues/issues/399
|
|||
// Revert Chromium support of IndexedDB, use advanced setting to force
|
|||
// IndexedDB.
|
|||
// https://github.com/uBlockOrigin/uBlock-issues/issues/409
|
|||
// Allow forcing the use of webext storage on Firefox.
|
|||
|
|||
µMatrix.cacheStorage = (function() { |
|||
|
|||
const STORAGE_NAME = 'uMatrixCacheStorage'; |
|||
|
|||
// Default to webext storage.
|
|||
const localStorage = webext.storage.local; |
|||
const api = { |
|||
name: 'browser.storage.local', |
|||
get: localStorage.get, |
|||
set: localStorage.set, |
|||
remove: localStorage.remove, |
|||
clear: localStorage.clear, |
|||
getBytesInUse: localStorage.getBytesInUse, |
|||
select: function(selectedBackend) { |
|||
let actualBackend = selectedBackend; |
|||
if ( actualBackend === undefined || actualBackend === 'unset' ) { |
|||
actualBackend = vAPI.webextFlavor.soup.has('firefox') |
|||
? 'indexedDB' |
|||
: 'browser.storage.local'; |
|||
} |
|||
if ( actualBackend === 'indexedDB' ) { |
|||
return selectIDB().then(success => { |
|||
if ( success || selectedBackend === 'indexedDB' ) { |
|||
clearWebext(); |
|||
return 'indexedDB'; |
|||
} |
|||
clearIDB(); |
|||
return 'browser.storage.local'; |
|||
}); |
|||
} |
|||
if ( actualBackend === 'browser.storage.local' ) { |
|||
clearIDB(); |
|||
} |
|||
return Promise.resolve('browser.storage.local'); |
|||
|
|||
}, |
|||
error: undefined |
|||
}; |
|||
|
|||
// Reassign API entries to that of indexedDB-based ones
|
|||
const selectIDB = async function() { |
|||
let db; |
|||
let dbPromise; |
|||
let dbTimer; |
|||
|
|||
const noopfn = function () { |
|||
}; |
|||
|
|||
const disconnect = function() { |
|||
if ( dbTimer !== undefined ) { |
|||
clearTimeout(dbTimer); |
|||
dbTimer = undefined; |
|||
} |
|||
if ( db instanceof IDBDatabase ) { |
|||
db.close(); |
|||
db = undefined; |
|||
} |
|||
}; |
|||
|
|||
const keepAlive = function() { |
|||
if ( dbTimer !== undefined ) { |
|||
clearTimeout(dbTimer); |
|||
} |
|||
dbTimer = vAPI.setTimeout( |
|||
( ) => { |
|||
dbTimer = undefined; |
|||
disconnect(); |
|||
}, |
|||
Math.max( |
|||
µMatrix.hiddenSettings.autoUpdateAssetFetchPeriod * 2 * 1000, |
|||
180000 |
|||
) |
|||
); |
|||
}; |
|||
|
|||
// https://github.com/gorhill/uBlock/issues/3156
|
|||
// I have observed that no event was fired in Tor Browser 7.0.7 +
|
|||
// medium security level after the request to open the database was
|
|||
// created. When this occurs, I have also observed that the `error`
|
|||
// property was already set, so this means uBO can detect here whether
|
|||
// the database can be opened successfully. A try-catch block is
|
|||
// necessary when reading the `error` property because we are not
|
|||
// allowed to read this propery outside of event handlers in newer
|
|||
// implementation of IDBRequest (my understanding).
|
|||
|
|||
const getDb = function() { |
|||
keepAlive(); |
|||
if ( db !== undefined ) { |
|||
return Promise.resolve(db); |
|||
} |
|||
if ( dbPromise !== undefined ) { |
|||
return dbPromise; |
|||
} |
|||
dbPromise = new Promise(resolve => { |
|||
let req; |
|||
try { |
|||
req = indexedDB.open(STORAGE_NAME, 1); |
|||
if ( req.error ) { |
|||
console.log(req.error); |
|||
req = undefined; |
|||
} |
|||
} catch(ex) { |
|||
} |
|||
if ( req === undefined ) { |
|||
db = null; |
|||
dbPromise = undefined; |
|||
return resolve(null); |
|||
} |
|||
req.onupgradeneeded = function(ev) { |
|||
if ( ev.oldVersion === 1 ) { return; } |
|||
try { |
|||
const db = ev.target.result; |
|||
db.createObjectStore(STORAGE_NAME, { keyPath: 'key' }); |
|||
} catch(ex) { |
|||
req.onerror(); |
|||
} |
|||
}; |
|||
req.onsuccess = function(ev) { |
|||
if ( resolve === undefined ) { return; } |
|||
req = undefined; |
|||
db = ev.target.result; |
|||
dbPromise = undefined; |
|||
resolve(db); |
|||
resolve = undefined; |
|||
}; |
|||
req.onerror = req.onblocked = function() { |
|||
if ( resolve === undefined ) { return; } |
|||
req = undefined; |
|||
console.log(this.error); |
|||
db = null; |
|||
dbPromise = undefined; |
|||
resolve(null); |
|||
resolve = undefined; |
|||
}; |
|||
setTimeout(( ) => { |
|||
if ( resolve === undefined ) { return; } |
|||
db = null; |
|||
dbPromise = undefined; |
|||
resolve(null); |
|||
resolve = undefined; |
|||
}, 5000); |
|||
}); |
|||
return dbPromise; |
|||
}; |
|||
|
|||
const getFromDb = async function(keys, keyvalStore, callback) { |
|||
if ( typeof callback !== 'function' ) { return; } |
|||
if ( keys.length === 0 ) { return callback(keyvalStore); } |
|||
const promises = []; |
|||
const gotOne = function() { |
|||
if ( typeof this.result !== 'object' ) { return; } |
|||
keyvalStore[this.result.key] = this.result.value; |
|||
if ( this.result.value instanceof Blob === false ) { return; } |
|||
promises.push( |
|||
µMatrix.lz4Codec.decode( |
|||
this.result.key, |
|||
this.result.value |
|||
).then(result => { |
|||
keyvalStore[result.key] = result.data; |
|||
}) |
|||
); |
|||
}; |
|||
try { |
|||
const db = await getDb(); |
|||
if ( !db ) { return callback(); } |
|||
const transaction = db.transaction(STORAGE_NAME, 'readonly'); |
|||
transaction.oncomplete = |
|||
transaction.onerror = |
|||
transaction.onabort = ( ) => { |
|||
Promise.all(promises).then(( ) => { |
|||
callback(keyvalStore); |
|||
}); |
|||
}; |
|||
const table = transaction.objectStore(STORAGE_NAME); |
|||
for ( const key of keys ) { |
|||
const req = table.get(key); |
|||
req.onsuccess = gotOne; |
|||
req.onerror = noopfn; |
|||
} |
|||
} |
|||
catch(reason) { |
|||
console.info(`cacheStorage.getFromDb() failed: ${reason}`); |
|||
callback(); |
|||
} |
|||
}; |
|||
|
|||
const visitAllFromDb = async function(visitFn) { |
|||
const db = await getDb(); |
|||
if ( !db ) { return visitFn(); } |
|||
const transaction = db.transaction(STORAGE_NAME, 'readonly'); |
|||
transaction.oncomplete = |
|||
transaction.onerror = |
|||
transaction.onabort = ( ) => visitFn(); |
|||
const table = transaction.objectStore(STORAGE_NAME); |
|||
const req = table.openCursor(); |
|||
req.onsuccess = function(ev) { |
|||
let cursor = ev.target && ev.target.result; |
|||
if ( !cursor ) { return; } |
|||
let entry = cursor.value; |
|||
visitFn(entry); |
|||
cursor.continue(); |
|||
}; |
|||
}; |
|||
|
|||
const getAllFromDb = function(callback) { |
|||
if ( typeof callback !== 'function' ) { return; } |
|||
const promises = []; |
|||
const keyvalStore = {}; |
|||
visitAllFromDb(entry => { |
|||
if ( entry === undefined ) { |
|||
Promise.all(promises).then(( ) => { |
|||
callback(keyvalStore); |
|||
}); |
|||
return; |
|||
} |
|||
keyvalStore[entry.key] = entry.value; |
|||
if ( entry.value instanceof Blob === false ) { return; } |
|||
promises.push( |
|||
µMatrix.lz4Codec.decode( |
|||
entry.key, |
|||
entry.value |
|||
).then(result => { |
|||
keyvalStore[result.key] = result.value; |
|||
}) |
|||
); |
|||
}).catch(reason => { |
|||
console.info(`cacheStorage.getAllFromDb() failed: ${reason}`); |
|||
callback(); |
|||
}); |
|||
}; |
|||
|
|||
// https://github.com/uBlockOrigin/uBlock-issues/issues/141
|
|||
// Mind that IDBDatabase.transaction() and IDBObjectStore.put()
|
|||
// can throw:
|
|||
// https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction
|
|||
// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put
|
|||
|
|||
const putToDb = async function(keyvalStore, callback) { |
|||
if ( typeof callback !== 'function' ) { |
|||
callback = noopfn; |
|||
} |
|||
const keys = Object.keys(keyvalStore); |
|||
if ( keys.length === 0 ) { return callback(); } |
|||
const promises = [ getDb() ]; |
|||
const entries = []; |
|||
const dontCompress = |
|||
µMatrix.hiddenSettings.cacheStorageCompression !== true; |
|||
const handleEncodingResult = result => { |
|||
entries.push({ key: result.key, value: result.data }); |
|||
}; |
|||
for ( const key of keys ) { |
|||
const data = keyvalStore[key]; |
|||
const isString = typeof data === 'string'; |
|||
if ( isString === false || dontCompress ) { |
|||
entries.push({ key, value: data }); |
|||
continue; |
|||
} |
|||
promises.push( |
|||
µMatrix.lz4Codec.encode(key, data).then(handleEncodingResult) |
|||
); |
|||
} |
|||
const finish = ( ) => { |
|||
if ( callback === undefined ) { return; } |
|||
let cb = callback; |
|||
callback = undefined; |
|||
cb(); |
|||
}; |
|||
try { |
|||
const results = await Promise.all(promises); |
|||
const db = results[0]; |
|||
if ( !db ) { return callback(); } |
|||
const transaction = db.transaction( |
|||
STORAGE_NAME, |
|||
'readwrite' |
|||
); |
|||
transaction.oncomplete = |
|||
transaction.onerror = |
|||
transaction.onabort = finish; |
|||
const table = transaction.objectStore(STORAGE_NAME); |
|||
for ( const entry of entries ) { |
|||
table.put(entry); |
|||
} |
|||
} catch (ex) { |
|||
finish(); |
|||
} |
|||
}; |
|||
|
|||
const deleteFromDb = async function(input, callback) { |
|||
if ( typeof callback !== 'function' ) { |
|||
callback = noopfn; |
|||
} |
|||
const keys = Array.isArray(input) ? input.slice() : [ input ]; |
|||
if ( keys.length === 0 ) { return callback(); } |
|||
const finish = ( ) => { |
|||
if ( callback === undefined ) { return; } |
|||
let cb = callback; |
|||
callback = undefined; |
|||
cb(); |
|||
}; |
|||
try { |
|||
const db = await getDb(); |
|||
if ( !db ) { return callback(); } |
|||
const transaction = db.transaction(STORAGE_NAME, 'readwrite'); |
|||
transaction.oncomplete = |
|||
transaction.onerror = |
|||
transaction.onabort = finish; |
|||
const table = transaction.objectStore(STORAGE_NAME); |
|||
for ( const key of keys ) { |
|||
table.delete(key); |
|||
} |
|||
} catch (ex) { |
|||
finish(); |
|||
} |
|||
}; |
|||
|
|||
const clearDb = async function(callback) { |
|||
if ( typeof callback !== 'function' ) { |
|||
callback = noopfn; |
|||
} |
|||
try { |
|||
const db = await getDb(); |
|||
if ( !db ) { return callback(); } |
|||
const transaction = db.transaction(STORAGE_NAME, 'readwrite'); |
|||
transaction.oncomplete = |
|||
transaction.onerror = |
|||
transaction.onabort = ( ) => { |
|||
callback(); |
|||
}; |
|||
transaction.objectStore(STORAGE_NAME).clear(); |
|||
} |
|||
catch(reason) { |
|||
console.info(`cacheStorage.clearDb() failed: ${reason}`); |
|||
callback(); |
|||
} |
|||
}; |
|||
|
|||
await getDb(); |
|||
if ( !db ) { return false; } |
|||
|
|||
api.name = 'indexedDB'; |
|||
api.get = function get(keys) { |
|||
return new Promise(resolve => { |
|||
if ( keys === null ) { |
|||
return getAllFromDb(bin => resolve(bin)); |
|||
} |
|||
let toRead, output = {}; |
|||
if ( typeof keys === 'string' ) { |
|||
toRead = [ keys ]; |
|||
} else if ( Array.isArray(keys) ) { |
|||
toRead = keys; |
|||
} else /* if ( typeof keys === 'object' ) */ { |
|||
toRead = Object.keys(keys); |
|||
output = keys; |
|||
} |
|||
getFromDb(toRead, output, bin => resolve(bin)); |
|||
}); |
|||
}; |
|||
api.set = function set(keys) { |
|||
return new Promise(resolve => { |
|||
putToDb(keys, details => resolve(details)); |
|||
}); |
|||
}; |
|||
api.remove = function remove(keys) { |
|||
return new Promise(resolve => { |
|||
deleteFromDb(keys, ( ) => resolve()); |
|||
}); |
|||
}; |
|||
api.clear = function clear() { |
|||
return new Promise(resolve => { |
|||
clearDb(( ) => resolve()); |
|||
}); |
|||
}; |
|||
api.getBytesInUse = function getBytesInUse() { |
|||
return Promise.resolve(0); |
|||
}; |
|||
return true; |
|||
}; |
|||
|
|||
// https://github.com/uBlockOrigin/uBlock-issues/issues/328
|
|||
// Delete cache-related entries from webext storage.
|
|||
const clearWebext = async function() { |
|||
const bin = await webext.storage.local.get('assetCacheRegistry'); |
|||
if ( |
|||
bin instanceof Object === false || |
|||
bin.assetCacheRegistry instanceof Object === false |
|||
) { |
|||
return; |
|||
} |
|||
const toRemove = [ |
|||
'assetCacheRegistry', |
|||
'assetSourceRegistry', |
|||
'resourcesSelfie', |
|||
'selfie' |
|||
]; |
|||
for ( const key in bin.assetCacheRegistry ) { |
|||
if ( bin.assetCacheRegistry.hasOwnProperty(key) ) { |
|||
toRemove.push('cache/' + key); |
|||
} |
|||
} |
|||
webext.storage.local.remove(toRemove); |
|||
}; |
|||
|
|||
const clearIDB = function() { |
|||
try { |
|||
indexedDB.deleteDatabase(STORAGE_NAME); |
|||
} catch(ex) { |
|||
} |
|||
}; |
|||
|
|||
return api; |
|||
}()); |
|||
|
|||
/******************************************************************************/ |
@ -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
|
|||
// *****************************************************************************
|
|||
|
|||
} |
2722
src/js/logger-ui.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,210 @@ |
|||
/******************************************************************************* |
|||
|
|||
uBlock Origin - a browser extension to block requests. |
|||
Copyright (C) 2018-present Raymond Hill |
|||
|
|||
This program is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program. If not, see {http://www.gnu.org/licenses/}.
|
|||
|
|||
Home: https://github.com/gorhill/uBlock
|
|||
*/ |
|||
|
|||
/* global lz4BlockCodec */ |
|||
|
|||
'use strict'; |
|||
|
|||
/******************************************************************************* |
|||
|
|||
Experimental support for storage compression. |
|||
|
|||
For background information on the topic, see: |
|||
https://github.com/uBlockOrigin/uBlock-issues/issues/141#issuecomment-407737186
|
|||
|
|||
**/ |
|||
|
|||
µMatrix.lz4Codec = (function() { // >>>> Start of private namespace
|
|||
|
|||
/******************************************************************************/ |
|||
|
|||
let lz4CodecInstance; |
|||
let pendingInitialization; |
|||
let textEncoder, textDecoder; |
|||
let ttlCount = 0; |
|||
let ttlTimer; |
|||
let ttlDelay = 60000; |
|||
|
|||
const init = function() { |
|||
ttlDelay = µMatrix.rawSettings.autoUpdateAssetFetchPeriod * 1000 + 15000; |
|||
if ( lz4CodecInstance === null ) { |
|||
return Promise.resolve(null); |
|||
} |
|||
if ( lz4CodecInstance !== undefined ) { |
|||
return Promise.resolve(lz4CodecInstance); |
|||
} |
|||
if ( pendingInitialization === undefined ) { |
|||
let flavor; |
|||
if ( µMatrix.rawSettings.disableWebAssembly === true ) { |
|||
flavor = 'js'; |
|||
} |
|||
pendingInitialization = lz4BlockCodec.createInstance(flavor) |
|||
.then(instance => { |
|||
lz4CodecInstance = instance; |
|||
pendingInitialization = undefined; |
|||
}); |
|||
} |
|||
return pendingInitialization; |
|||
}; |
|||
|
|||
// We can't shrink memory usage of lz4 codec instances, and in the
|
|||
// current case memory usage can grow to a significant amount given
|
|||
// that a single contiguous memory buffer is required to accommodate
|
|||
// both input and output data. Thus a time-to-live implementation
|
|||
// which will cause the wasm instance to be forgotten after enough
|
|||
// time elapse without the instance being used.
|
|||
|
|||
const destroy = function() { |
|||
//if ( lz4CodecInstance !== undefined ) {
|
|||
// console.info(
|
|||
// 'uBO: freeing lz4-block-codec instance (%s KB)',
|
|||
// lz4CodecInstance.bytesInUse() >>> 10
|
|||
// );
|
|||
//}
|
|||
lz4CodecInstance = undefined; |
|||
textEncoder = textDecoder = undefined; |
|||
ttlCount = 0; |
|||
ttlTimer = undefined; |
|||
}; |
|||
|
|||
const ttlManage = function(count) { |
|||
if ( ttlTimer !== undefined ) { |
|||
clearTimeout(ttlTimer); |
|||
ttlTimer = undefined; |
|||
} |
|||
ttlCount += count; |
|||
if ( ttlCount > 0 ) { return; } |
|||
if ( lz4CodecInstance === null ) { return; } |
|||
ttlTimer = vAPI.setTimeout(destroy, ttlDelay); |
|||
}; |
|||
|
|||
const uint8ArrayFromBlob = function(key, data) { |
|||
if ( data instanceof Blob === false ) { |
|||
return Promise.resolve({ key, data }); |
|||
} |
|||
return new Promise(resolve => { |
|||
let blobReader = new FileReader(); |
|||
blobReader.onloadend = ev => { |
|||
resolve({ key, data: new Uint8Array(ev.target.result) }); |
|||
}; |
|||
blobReader.readAsArrayBuffer(data); |
|||
}); |
|||
}; |
|||
|
|||
const encodeValue = function(key, value) { |
|||
if ( !lz4CodecInstance ) { return; } |
|||
//let t0 = window.performance.now();
|
|||
if ( textEncoder === undefined ) { |
|||
textEncoder = new TextEncoder(); |
|||
} |
|||
let inputArray = textEncoder.encode(value); |
|||
let inputSize = inputArray.byteLength; |
|||
let outputArray = lz4CodecInstance.encodeBlock(inputArray, 8); |
|||
if ( outputArray instanceof Uint8Array === false ) { return; } |
|||
outputArray[0] = 0x18; |
|||
outputArray[1] = 0x4D; |
|||
outputArray[2] = 0x22; |
|||
outputArray[3] = 0x04; |
|||
outputArray[4] = (inputSize >>> 0) & 0xFF; |
|||
outputArray[5] = (inputSize >>> 8) & 0xFF; |
|||
outputArray[6] = (inputSize >>> 16) & 0xFF; |
|||
outputArray[7] = (inputSize >>> 24) & 0xFF; |
|||
//console.info(
|
|||
// 'uBO: [%s] compressed %d KB => %d KB (%s%%) in %s ms',
|
|||
// key,
|
|||
// inputArray.byteLength >> 10,
|
|||
// outputArray.byteLength >> 10,
|
|||
// (outputArray.byteLength / inputArray.byteLength * 100).toFixed(0),
|
|||
// (window.performance.now() - t0).toFixed(1)
|
|||
//);
|
|||
return outputArray; |
|||
}; |
|||
|
|||
const decodeValue = function(key, inputArray) { |
|||
if ( !lz4CodecInstance ) { return; } |
|||
//let t0 = window.performance.now();
|
|||
if ( |
|||
inputArray[0] !== 0x18 || inputArray[1] !== 0x4D || |
|||
inputArray[2] !== 0x22 || inputArray[3] !== 0x04 |
|||
) { |
|||
return; |
|||
} |
|||
let outputSize = |
|||
(inputArray[4] << 0) | (inputArray[5] << 8) | |
|||
(inputArray[6] << 16) | (inputArray[7] << 24); |
|||
let outputArray = lz4CodecInstance.decodeBlock(inputArray, 8, outputSize); |
|||
if ( outputArray instanceof Uint8Array === false ) { return; } |
|||
if ( textDecoder === undefined ) { |
|||
textDecoder = new TextDecoder(); |
|||
} |
|||
let value = textDecoder.decode(outputArray); |
|||
//console.info(
|
|||
// 'uBO: [%s] decompressed %d KB => %d KB (%s%%) in %s ms',
|
|||
// key,
|
|||
// inputArray.byteLength >>> 10,
|
|||
// outputSize >>> 10,
|
|||
// (inputArray.byteLength / outputSize * 100).toFixed(0),
|
|||
// (window.performance.now() - t0).toFixed(1)
|
|||
//);
|
|||
return value; |
|||
}; |
|||
|
|||
return { |
|||
encode: function(key, dataIn) { |
|||
if ( typeof dataIn !== 'string' || dataIn.length < 4096 ) { |
|||
return Promise.resolve({ key, data: dataIn }); |
|||
} |
|||
ttlManage(1); |
|||
return init().then(( ) => { |
|||
ttlManage(-1); |
|||
let dataOut = encodeValue(key, dataIn) || dataIn; |
|||
if ( dataOut instanceof Uint8Array ) { |
|||
dataOut = new Blob([ dataOut ]); |
|||
} |
|||
return { key, data: dataOut || dataIn }; |
|||
}); |
|||
}, |
|||
decode: function(key, dataIn) { |
|||
if ( dataIn instanceof Blob === false ) { |
|||
return Promise.resolve({ key, data: dataIn }); |
|||
} |
|||
ttlManage(1); |
|||
return Promise.all([ |
|||
init(), |
|||
uint8ArrayFromBlob(key, dataIn) |
|||
]).then(results => { |
|||
ttlManage(-1); |
|||
let result = results[1]; |
|||
return { |
|||
key: result.key, |
|||
data: decodeValue(result.key, result.data) || result.data |
|||
}; |
|||
}); |
|||
}, |
|||
relinquish: function() { |
|||
ttlDelay = 1; |
|||
ttlManage(0); |
|||
}, |
|||
}; |
|||
|
|||
/******************************************************************************/ |
|||
|
|||
})(); // <<<< End of private namespace
|
776
src/js/messaging.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
1117
src/js/popup.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
908
src/js/storage.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,24 @@ |
|||
### For code reviewers |
|||
|
|||
All `wasm` files in that directory where created by compiling the |
|||
corresponding `wat` file using the command (using `hntrie.wat`/`hntrie.wasm` |
|||
as example): |
|||
|
|||
wat2wasm hntrie.wat -o hntrie.wasm |
|||
|
|||
Assuming: |
|||
|
|||
- The command is executed from within the present directory. |
|||
|
|||
### `wat2wasm` tool |
|||
|
|||
The `wat2wasm` tool can be downloaded from an official WebAssembly project: |
|||
<https://github.com/WebAssembly/wabt/releases>. |
|||
|
|||
### `wat2wasm` tool online |
|||
|
|||
You can also use the following online `wat2wasm` tool: |
|||
<https://webassembly.github.io/wabt/demo/wat2wasm/>. |
|||
|
|||
Just paste the whole content of the `wat` file to compile into the WAT pane. |
|||
Click "Download" button to retrieve the resulting `wasm` file. |
@ -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) |
|||
}) |
|||
}); |
18878
src/lib/codemirror/lib/codemirror.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,52 @@ |
|||
## Purpose |
|||
|
|||
The purpose of this library is to implement LZ4 compression/decompression, |
|||
as documented at the official LZ4 repository: |
|||
|
|||
https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md |
|||
|
|||
The files in this directory are developed as a separate project at: |
|||
|
|||
https://github.com/gorhill/lz4-wasm |
|||
|
|||
## Files |
|||
|
|||
### `lz4-block-codec-any.js` |
|||
|
|||
The purpose is to instanciate a WebAssembly- or pure javascript-based |
|||
LZ4 block codec. |
|||
|
|||
If the choosen implementation is not specified, there will be an attempt to |
|||
create a WebAssembly-based instance. If for whatever reason this fails, a |
|||
pure javascript-based instance will be created. |
|||
|
|||
The script for either instance are dynamically loaded and only when needed, |
|||
such that no resources are wasted by keeping in memory code which won't be |
|||
used. |
|||
|
|||
### `lz4-block-codec-wasm.js` |
|||
|
|||
This contains the code to instanciate WebAssembly-based LZ4 block codec. Note |
|||
that the WebAssembly module is loaded using a `same-origin` fetch, hence |
|||
ensuring that no code outside the package is loaded. |
|||
|
|||
### `lz4-block-codec-js.js` |
|||
|
|||
This contains the code to instanciate pure javascript-based LZ4 block codec. |
|||
|
|||
This is used as a fallback implementation should WebAssembly not be available |
|||
for whatever reason. |
|||
|
|||
### `lz4-block-codec.wasm` |
|||
|
|||
This is the WebAssembly module, loaded by `lz4-block-codec-wasm.js` using a |
|||
`same-origin` fetch. |
|||
|
|||
### `lz4-block-codec.wat` |
|||
|
|||
The WebAssembly source code used to generate the WebAssembly module `lz4-block-codec.wasm`. |
|||
|
|||
wat2wasm ./lz4-block-codec.wat -o ./lz4-block-codec.wasm |
|||
wasm-opt ./lz4-block-codec.wasm -O4 -o ./lz4-block-codec.wasm |
|||
|
|||
You can get `wat2wasm` at <https://github.com/WebAssembly/wabt>, and `wasm-opt` at <https://github.com/WebAssembly/binaryen>. |
@ -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
|
|||
|
|||
/******************************************************************************/ |
@ -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/ |
@ -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 |
|||
;; |
|||
) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue