/*******************************************************************************

    uBlock Origin - a browser extension to block requests.
    Copyright (C) 2014-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 DOMTokenList */
/* exported uDom */

'use strict';

/******************************************************************************/

// It's just a silly, minimalist DOM framework: this allows me to not rely
// on jQuery. jQuery contains way too much stuff than I need, and as per
// Opera rules, I am not allowed to use a cut-down version of jQuery. So
// the code here does *only* what I need, and nothing more, and with a lot
// of assumption on passed parameters, etc. I grow it on a per-need-basis only.

const uDom = (function() {

/******************************************************************************/

const DOMList = function() {
    this.nodes = [];
};

/******************************************************************************/

Object.defineProperty(
    DOMList.prototype,
    'length',
    {
        get: function() {
            return this.nodes.length;
        }
    }
);

/******************************************************************************/

const DOMListFactory = function(selector, context) {
    var r = new DOMList();
    if ( typeof selector === 'string' ) {
        selector = selector.trim();
        if ( selector !== '' ) {
            return addSelectorToList(r, selector, context);
        }
    }
    if ( selector instanceof Node ) {
        return addNodeToList(r, selector);
    }
    if ( selector instanceof NodeList ) {
        return addNodeListToList(r, selector);
    }
    if ( selector instanceof DOMList ) {
        return addListToList(r, selector);
    }
    return r;
};

/******************************************************************************/

DOMListFactory.onLoad = function(callback) {
    window.addEventListener('load', callback);
};

/******************************************************************************/

DOMListFactory.nodeFromId = function(id) {
    return document.getElementById(id);
};

DOMListFactory.nodeFromSelector = function(selector) {
    return document.querySelector(selector);
};

/******************************************************************************/

const addNodeToList = function(list, node) {
    if ( node ) {
        list.nodes.push(node);
    }
    return list;
};

/******************************************************************************/

const addNodeListToList = function(list, nodelist) {
    if ( nodelist ) {
        var n = nodelist.length;
        for ( var i = 0; i < n; i++ ) {
            list.nodes.push(nodelist[i]);
        }
    }
    return list;
};

/******************************************************************************/

const addListToList = function(list, other) {
    list.nodes = list.nodes.concat(other.nodes);
    return list;
};

/******************************************************************************/

const addSelectorToList = function(list, selector, context) {
    var p = context || document;
    var r = p.querySelectorAll(selector);
    var n = r.length;
    for ( var i = 0; i < n; i++ ) {
        list.nodes.push(r[i]);
    }
    return list;
};

/******************************************************************************/

DOMList.prototype.nodeAt = function(i) {
    return this.nodes[i] || null;
};

DOMList.prototype.at = function(i) {
    return addNodeToList(new DOMList(), this.nodes[i]);
};

/******************************************************************************/

DOMList.prototype.toArray = function() {
    return this.nodes.slice();
};

/******************************************************************************/

DOMList.prototype.pop = function() {
    return addNodeToList(new DOMList(), this.nodes.pop());
};

/******************************************************************************/

DOMList.prototype.forEach = function(fn) {
    var n = this.nodes.length;
    for ( var i = 0; i < n; i++ ) {
        fn(this.at(i), i);
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.subset = function(i, l) {
    var r = new DOMList();
    var n = l !== undefined ? l : this.nodes.length;
    var j = Math.min(i + n, this.nodes.length);
    if ( i < j ) {
        r.nodes = this.nodes.slice(i, j);
    }
    return r;
};

/******************************************************************************/

DOMList.prototype.first = function() {
    return this.subset(0, 1);
};

/******************************************************************************/

DOMList.prototype.next = function(selector) {
    var r = new DOMList();
    var n = this.nodes.length;
    var node;
    for ( var i = 0; i < n; i++ ) {
        node = this.nodes[i];
        while ( node.nextSibling !== null ) {
            node = node.nextSibling;
            if ( node.nodeType !== 1 ) { continue; }
            if ( node.matches(selector) === false ) { continue; }
            addNodeToList(r, node);
            break;
        }
    }
    return r;
};

/******************************************************************************/

DOMList.prototype.parent = function() {
    var r = new DOMList();
    if ( this.nodes.length ) {
        addNodeToList(r, this.nodes[0].parentNode);
    }
    return r;
};

/******************************************************************************/

DOMList.prototype.filter = function(filter) {
    var r = new DOMList();
    var filterFunc;
    if ( typeof filter === 'string' ) {
        filterFunc = function() {
            return this.matches(filter);
        };
    } else if ( typeof filter === 'function' ) {
        filterFunc = filter;
    } else {
        filterFunc = function(){
            return true;
        };
    }
    var n = this.nodes.length;
    var node;
    for ( var i = 0; i < n; i++ ) {
        node = this.nodes[i];
        if ( filterFunc.apply(node) ) {
            addNodeToList(r, node);
        }
    }
    return r;
};

/******************************************************************************/

// TODO: Avoid possible duplicates

DOMList.prototype.ancestors = function(selector) {
    var r = new DOMList();
    for ( var i = 0, n = this.nodes.length; i < n; i++ ) {
        var node = this.nodes[i].parentNode;
        while ( node ) {
            if (
                node instanceof Element &&
                node.matches(selector)
            ) {
                addNodeToList(r, node);
            }
            node = node.parentNode;
        }
    }
    return r;
};

/******************************************************************************/

DOMList.prototype.descendants = function(selector) {
    var r = new DOMList();
    var n = this.nodes.length;
    var nl;
    for ( var i = 0; i < n; i++ ) {
        nl = this.nodes[i].querySelectorAll(selector);
        addNodeListToList(r, nl);
    }
    return r;
};

/******************************************************************************/

DOMList.prototype.contents = function() {
    var r = new DOMList();
    var cnodes, cn, ci;
    var n = this.nodes.length;
    for ( var i = 0; i < n; i++ ) {
        cnodes = this.nodes[i].childNodes;
        cn = cnodes.length;
        for ( ci = 0; ci < cn; ci++ ) {
            addNodeToList(r, cnodes.item(ci));
        }
    }
    return r;
};

/******************************************************************************/

DOMList.prototype.remove = function() {
    var cn, p;
    var i = this.nodes.length;
    while ( i-- ) {
        cn = this.nodes[i];
        if ( (p = cn.parentNode) ) {
            p.removeChild(cn);
        }
     }
    return this;
};

DOMList.prototype.detach = DOMList.prototype.remove;

/******************************************************************************/

DOMList.prototype.empty = function() {
    var node;
    var i = this.nodes.length;
    while ( i-- ) {
        node = this.nodes[i];
        while ( node.firstChild ) {
            node.removeChild(node.firstChild);
        }
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.append = function(selector, context) {
    var p = this.nodes[0];
    if ( p ) {
        var c = DOMListFactory(selector, context);
        var n = c.nodes.length;
        for ( var i = 0; i < n; i++ ) {
            p.appendChild(c.nodes[i]);
        }
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.prepend = function(selector, context) {
    var p = this.nodes[0];
    if ( p ) {
        var c = DOMListFactory(selector, context);
        var i = c.nodes.length;
        while ( i-- ) {
            p.insertBefore(c.nodes[i], p.firstChild);
        }
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.appendTo = function(selector, context) {
    var p = selector instanceof DOMListFactory ? selector : DOMListFactory(selector, context);
    var n = p.length;
    for ( var i = 0; i < n; i++ ) {
        p.nodes[0].appendChild(this.nodes[i]);
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.insertAfter = function(selector, context) {
    if ( this.nodes.length === 0 ) {
        return this;
    }
    var p = this.nodes[0].parentNode;
    if ( !p ) {
        return this;
    }
    var c = DOMListFactory(selector, context);
    var n = c.nodes.length;
    for ( var i = 0; i < n; i++ ) {
        p.appendChild(c.nodes[i]);
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.insertBefore = function(selector, context) {
    if ( this.nodes.length === 0 ) {
        return this;
    }
    var referenceNodes = DOMListFactory(selector, context);
    if ( referenceNodes.nodes.length === 0 ) {
        return this;
    }
    var referenceNode = referenceNodes.nodes[0];
    var parentNode = referenceNode.parentNode;
    if ( !parentNode ) {
        return this;
    }
    var n = this.nodes.length;
    for ( var i = 0; i < n; i++ ) {
        parentNode.insertBefore(this.nodes[i], referenceNode);
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.clone = function(notDeep) {
    var r = new DOMList();
    var n = this.nodes.length;
    for ( var i = 0; i < n; i++ ) {
        addNodeToList(r, this.nodes[i].cloneNode(!notDeep));
    }
    return r;
};

/******************************************************************************/

DOMList.prototype.nthOfType = function() {
    if ( this.nodes.length === 0 ) {
        return 0;
    }
    var node = this.nodes[0];
    var tagName = node.tagName;
    var i = 1;
    while ( node.previousElementSibling !== null ) {
        node = node.previousElementSibling;
        if ( typeof node.tagName !== 'string' ) {
            continue;
        }
        if ( node.tagName !== tagName ) {
            continue;
        }
        i++;
    }
    return i;
};

/******************************************************************************/

DOMList.prototype.attr = function(attr, value) {
    var i = this.nodes.length;
    if ( value === undefined && typeof attr !== 'object' ) {
        return i ? this.nodes[0].getAttribute(attr) : undefined;
    }
    if ( typeof attr === 'object' ) {
        var attrNames = Object.keys(attr);
        var node, j, attrName;
        while ( i-- ) {
            node = this.nodes[i];
            j = attrNames.length;
            while ( j-- ) {
                attrName = attrNames[j];
                node.setAttribute(attrName, attr[attrName]);
            }
        }
    } else {
        while ( i-- ) {
            this.nodes[i].setAttribute(attr, value);
        }
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.removeAttr = function(attr) {
    var i = this.nodes.length;
    while ( i-- ) {
        this.nodes[i].removeAttribute(attr);
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.prop = function(prop, value) {
    var i = this.nodes.length;
    if ( value === undefined ) {
        return i !== 0 ? this.nodes[0][prop] : undefined;
    }
    while ( i-- ) {
        this.nodes[i][prop] = value;
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.css = function(prop, value) {
    var i = this.nodes.length;
    if ( value === undefined ) {
        return i ? this.nodes[0].style[prop] : undefined;
    }
    if ( value !== '' ) {
        while ( i-- ) {
            this.nodes[i].style.setProperty(prop, value);
        }
        return this;
    }
    while ( i-- ) {
        this.nodes[i].style.removeProperty(prop);
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.val = function(value) {
    return this.prop('value', value);
};

/******************************************************************************/

DOMList.prototype.text = function(text) {
    var i = this.nodes.length;
    if ( text === undefined ) {
        return i ? this.nodes[0].textContent : '';
    }
    while ( i-- ) {
        this.nodes[i].textContent = text;
    }
    return this;
};

/******************************************************************************/

const toggleClass = function(node, className, targetState) {
    var tokenList = node.classList;
    if ( tokenList instanceof DOMTokenList === false ) {
        return;
    }
    var currentState = tokenList.contains(className);
    var newState = targetState;
    if ( newState === undefined ) {
        newState = !currentState;
    }
    if ( newState === currentState ) {
        return;
    }
    tokenList.toggle(className, newState);
};

/******************************************************************************/

DOMList.prototype.hasClass = function(className) {
    if ( !this.nodes.length ) {
        return false;
    }
    var tokenList = this.nodes[0].classList;
    return tokenList instanceof DOMTokenList &&
           tokenList.contains(className);
};
DOMList.prototype.hasClassName = DOMList.prototype.hasClass;

DOMList.prototype.addClass = function(className) {
    return this.toggleClass(className, true);
};

DOMList.prototype.removeClass = function(className) {
    if ( className !== undefined ) {
        return this.toggleClass(className, false);
    }
    var i = this.nodes.length;
    while ( i-- ) {
        this.nodes[i].className = '';
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.toggleClass = function(className, targetState) {
    if ( className.indexOf(' ') !== -1 ) {
        return this.toggleClasses(className, targetState);
    }
    var i = this.nodes.length;
    while ( i-- ) {
        toggleClass(this.nodes[i], className, targetState);
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.toggleClasses = function(classNames, targetState) {
    var tokens = classNames.split(/\s+/);
    var i = this.nodes.length;
    var node, j;
    while ( i-- ) {
        node = this.nodes[i];
        j = tokens.length;
        while ( j-- ) {
            toggleClass(node, tokens[j], targetState);
        }
    }
    return this;
};

/******************************************************************************/

const listenerEntries = [];

const ListenerEntry = function(target, type, capture, callback) {
    this.target = target;
    this.type = type;
    this.capture = capture;
    this.callback = callback;
    target.addEventListener(type, callback, capture);
};

ListenerEntry.prototype.dispose = function() {
    this.target.removeEventListener(this.type, this.callback, this.capture);
    this.target = null;
    this.callback = null;
};

/******************************************************************************/

const makeEventHandler = function(selector, callback) {
    return function(event) {
        const dispatcher = event.currentTarget;
        if (
            dispatcher instanceof HTMLElement === false ||
            typeof dispatcher.querySelectorAll !== 'function'
        ) {
            return;
        }
        const receiver = event.target;
        const ancestor = receiver.closest(selector);
        if (
            ancestor === receiver &&
            ancestor !== dispatcher &&
            dispatcher.contains(ancestor)
        ) {
            callback.call(receiver, event);
        }
    };
};

DOMList.prototype.on = function(etype, selector, callback) {
    if ( typeof selector === 'function' ) {
        callback = selector;
        selector = undefined;
    } else {
        callback = makeEventHandler(selector, callback);
    }

    for ( const node of this.nodes ) {
        listenerEntries.push(
            new ListenerEntry(node, etype, selector !== undefined, callback)
        );
    }
    return this;
};

/******************************************************************************/

// TODO: Won't work for delegated handlers. Need to figure
// what needs to be done.

DOMList.prototype.off = function(evtype, callback) {
    var i = this.nodes.length;
    while ( i-- ) {
        this.nodes[i].removeEventListener(evtype, callback);
    }
    return this;
};

/******************************************************************************/

DOMList.prototype.trigger = function(etype) {
    var ev = new CustomEvent(etype);
    var i = this.nodes.length;
    while ( i-- ) {
        this.nodes[i].dispatchEvent(ev);
    }
    return this;
};

/******************************************************************************/

return DOMListFactory;

})();


for ( let i = 0, n = aa.length; i < n; i++ ) {

/*******************************************************************************

    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
*/

'use strict';

// Packaging this file is optional: it is not necessary to package it if the
// platform is known to not support user stylesheets.

// >>>>>>>> start of HUGE-IF-BLOCK
if ( typeof vAPI === 'object' && vAPI.supportsUserStylesheets ) {

/******************************************************************************/
/******************************************************************************/

vAPI.userStylesheet = {
    added: new Set(),
    removed: new Set(),
    apply: function(callback) {
        if ( this.added.size === 0 && this.removed.size === 0 ) { return; }
        vAPI.messaging.send('vapi', {
            what: 'userCSS',
            add: Array.from(this.added),
            remove: Array.from(this.removed)
        }, callback);
        this.added.clear();
        this.removed.clear();
    },
    add: function(cssText, now) {
        if ( cssText === '' ) { return; }
        this.added.add(cssText);
        if ( now ) { this.apply(); }
    },
    remove: function(cssText, now) {
        if ( cssText === '' ) { return; }
        this.removed.add(cssText);
        if ( now ) { this.apply(); }
    }
};

/******************************************************************************/

vAPI.DOMFilterer = class {
    constructor() {
        this.commitTimer = new vAPI.SafeAnimationFrame(( ) => this.commitNow);
        this.domIsReady = document.readyState !== 'loading';
        this.disabled = false;
        this.listeners = [];
        this.filterset = new Set();
        this.excludedNodeSet = new WeakSet();
        this.addedCSSRules = new Set();
        this.exceptedCSSRules = [];
        this.reOnlySelectors = /\n\{[^\n]+/g;

        // https://github.com/uBlockOrigin/uBlock-issues/issues/167
        //   By the time the DOMContentLoaded is fired, the content script might
        //   have been disconnected from the background page. Unclear why this
        //   would happen, so far seems to be a Chromium-specific behavior at
        //   launch time.
        if ( this.domIsReady !== true ) {
            document.addEventListener('DOMContentLoaded', ( ) => {
                if ( vAPI instanceof Object === false ) { return; }
                this.domIsReady = true;
                this.commit();
            });
        }
    }

    // Here we will deal with:
    // - Injecting low priority user styles;
    // - Notifying listeners about changed filterset.
    // https://www.reddit.com/r/uBlockOrigin/comments/9jj0y1/no_longer_blocking_ads/
    //   Ensure vAPI is still valid -- it can go away by the time we are
    //   called, since the port could be force-disconnected from the main
    //   process. Another approach would be to have vAPI.SafeAnimationFrame
    //   register a shutdown job: to evaluate. For now I will keep the fix
    //   trivial.
    commitNow() {
        this.commitTimer.clear();
        if ( vAPI instanceof Object === false ) { return; }
        const userStylesheet = vAPI.userStylesheet;
        for ( const entry of this.addedCSSRules ) {
            if (
                this.disabled === false &&
                entry.lazy &&
                entry.injected === false
            ) {
                userStylesheet.add(
                    entry.selectors + '\n{' + entry.declarations + '}'
                );
            }
        }
        this.addedCSSRules.clear();
        userStylesheet.apply();
    }

    commit(commitNow) {
        if ( commitNow ) {
            this.commitTimer.clear();
            this.commitNow();
        } else {
            this.commitTimer.start();
        }
    }

    addCSSRule(selectors, declarations, details) {
        if ( selectors === undefined ) { return; }
        const selectorsStr = Array.isArray(selectors)
                ? selectors.join(',\n')
                : selectors;
        if ( selectorsStr.length === 0 ) { return; }
        if ( details === undefined ) { details = {}; }
        const entry = {
            selectors: selectorsStr,
            declarations,
            lazy: details.lazy === true,
            injected: details.injected === true
        };
        this.addedCSSRules.add(entry);
        this.filterset.add(entry);
        if (
            this.disabled === false &&
            entry.lazy !== true &&
            entry.injected !== true
        ) {
            vAPI.userStylesheet.add(selectorsStr + '\n{' + declarations + '}');
        }
        this.commit();
        if ( this.hasListeners() ) {
            this.triggerListeners({
                declarative: [ [ selectorsStr, declarations ] ]
            });
        }
    }

    exceptCSSRules(exceptions) {
        if ( exceptions.length === 0 ) { return; }
        this.exceptedCSSRules.push(...exceptions);
        if ( this.hasListeners() ) {
            this.triggerListeners({ exceptions });
        }
    }

    addListener(listener) {
        if ( this.listeners.indexOf(listener) !== -1 ) { return; }
        this.listeners.push(listener);
    }

    removeListener(listener) {
        const pos = this.listeners.indexOf(listener);
        if ( pos === -1 ) { return; }
        this.listeners.splice(pos, 1);
    }

    hasListeners() {
        return this.listeners.length !== 0;
    }

    triggerListeners(changes) {
        for ( const listener of this.listeners ) {
            listener.onFiltersetChanged(changes);
        }
    }

    excludeNode(node) {
        this.excludedNodeSet.add(node);
        this.unhideNode(node);
    }

    unexcludeNode(node) {
        this.excludedNodeSet.delete(node);
    }

    hideNode(node) {
        if ( this.excludedNodeSet.has(node) ) { return; }
        if ( this.hideNodeAttr === undefined ) { return; }
        node.setAttribute(this.hideNodeAttr, '');
        if ( this.hideNodeStyleSheetInjected === false ) {
            this.hideNodeStyleSheetInjected = true;
            this.addCSSRule(
                `[${this.hideNodeAttr}]`,
                'display:none!important;'
            );
        }
    }

    unhideNode(node) {
        if ( this.hideNodeAttr === undefined ) { return; }
        node.removeAttribute(this.hideNodeAttr);
    }

    toggle(state, callback) {
        if ( state === undefined ) { state = this.disabled; }
        if ( state !== this.disabled ) { return; }
        this.disabled = !state;
        const userStylesheet = vAPI.userStylesheet;
        for ( const entry of this.filterset ) {
            const rule = `${entry.selectors}\n{${entry.declarations}}`;
            if ( this.disabled ) {
                userStylesheet.remove(rule);
            } else {
                userStylesheet.add(rule);
            }
        }
        userStylesheet.apply(callback);
    }

    getAllSelectors_(all) {
        const out = {
            declarative: [],
            exceptions: this.exceptedCSSRules,
        };
        for ( const entry of this.filterset ) {
            let selectors = entry.selectors;
            if ( all !== true && this.hideNodeAttr !== undefined ) {
                selectors = selectors
                                .replace(`[${this.hideNodeAttr}]`, '')
                                .replace(/^,\n|,\n$/gm, '');
                if ( selectors === '' ) { continue; }
            }
            out.declarative.push([ selectors, entry.declarations ]);
        }
        return out;
    }

    getFilteredElementCount() {
        const details = this.getAllSelectors_(true);
        if ( Array.isArray(details.declarative) === false ) { return 0; }
        const selectors = details.declarative.map(entry => entry[0]);
        if ( selectors.length === 0 ) { return 0; }
        return document.querySelectorAll(selectors.join(',\n')).length;
    }

    getAllSelectors() {
        return this.getAllSelectors_(false);
    }
};

/******************************************************************************/
/******************************************************************************/

}
// <<<<<<<< end of HUGE-IF-BLOCK

/*******************************************************************************

    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;


;;
;; 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
;;
)


/*******************************************************************************

    uBlock Origin - a browser extension to block requests.
    Copyright (C) 2014-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 publicSuffixList */

'use strict';

/*******************************************************************************

RFC 3986 as reference: http://tools.ietf.org/html/rfc3986#appendix-A

Naming convention from https://en.wikipedia.org/wiki/URI_scheme#Examples

*/

/******************************************************************************/

µBlock.URI = (function() {

/******************************************************************************/

const punycode = self.punycode;

// Favorite regex tool: http://regex101.com/

// Ref: <http://tools.ietf.org/html/rfc3986#page-50>
// I removed redundant capture groups: capture less = peform faster. See
// <http://jsperf.com/old-uritools-vs-new-uritools>
// Performance improvements welcomed.
// jsperf: <http://jsperf.com/old-uritools-vs-new-uritools>
const reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;

// Derived
const reSchemeFromURI          = /^[^:\/?#]+:/;
const reAuthorityFromURI       = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/;
const reOriginFromURI          = /^(?:[^:\/?#]+:)\/\/[^\/?#]+/;
const reCommonHostnameFromURL  = /^https?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//;
const rePathFromURI            = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?([^?#]*)/;
const reMustNormalizeHostname  = /[^0-9a-z._-]/;

// These are to parse authority field, not parsed by above official regex
// IPv6 is seen as an exception: a non-compatible IPv6 is first tried, and
// if it fails, the IPv6 compatible regex istr used. This helps
// peformance by avoiding the use of a too complicated regex first.

// https://github.com/gorhill/httpswitchboard/issues/211
// "While a hostname may not contain other characters, such as the
// "underscore character (_), other DNS names may contain the underscore"
const reHostPortFromAuthority  = /^(?:[^@]*@)?([^:]*)(:\d*)?$/;
const reIPv6PortFromAuthority  = /^(?:[^@]*@)?(\[[0-9a-f:]*\])(:\d*)?$/i;

const reHostFromNakedAuthority = /^[0-9a-z._-]+[0-9a-z]$/i;
const reHostFromAuthority      = /^(?:[^@]*@)?([^:]+)(?::\d*)?$/;
const reIPv6FromAuthority      = /^(?:[^@]*@)?(\[[0-9a-f:]+\])(?::\d*)?$/i;

// Coarse (but fast) tests
const reValidHostname          = /^([a-z\d]+(-*[a-z\d]+)*)(\.[a-z\d]+(-*[a-z\d])*)*$/;
const reIPAddressNaive         = /^\d+\.\d+\.\d+\.\d+$|^\[[\da-zA-Z:]+\]$/;

/******************************************************************************/

const reset = function(o) {
    o.scheme = '';
    o.hostname = '';
    o._ipv4 = undefined;
    o._ipv6 = undefined;
    o.port = '';
    o.path = '';
    o.query = '';
    o.fragment = '';
    return o;
};

const resetAuthority = function(o) {
    o.hostname = '';
    o._ipv4 = undefined;
    o._ipv6 = undefined;
    o.port = '';
    return o;
};

/******************************************************************************/

// This will be exported

const URI = {
    scheme:      '',
    authority:   '',
    hostname:    '',
    _ipv4:       undefined,
    _ipv6:       undefined,
    port:        '',
    domain:      undefined,
    path:        '',
    query:       '',
    fragment:    '',
    schemeBit:   (1 << 0),
    userBit:     (1 << 1),
    passwordBit: (1 << 2),
    hostnameBit: (1 << 3),
    portBit:     (1 << 4),
    pathBit:     (1 << 5),
    queryBit:    (1 << 6),
    fragmentBit: (1 << 7),
    allBits:     (0xFFFF)
};

URI.authorityBit  = (URI.userBit | URI.passwordBit | URI.hostnameBit | URI.portBit);
URI.normalizeBits = (URI.schemeBit | URI.hostnameBit | URI.pathBit | URI.queryBit);

/******************************************************************************/

// See: https://en.wikipedia.org/wiki/URI_scheme#Examples
//     URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
//
//       foo://example.com:8042/over/there?name=ferret#nose
//       \_/   \______________/\_________/ \_________/ \__/
//        |           |            |            |        |
//     scheme     authority       path        query   fragment
//        |   _____________________|__
//       / \ /                        \
//       urn:example:animal:ferret:nose

URI.set = function(uri) {
    if ( uri === undefined ) {
        return reset(URI);
    }
    let matches = reRFC3986.exec(uri);
    if ( !matches ) {
        return reset(URI);
    }
    this.scheme = matches[1] !== undefined ? matches[1].slice(0, -1) : '';
    this.authority = matches[2] !== undefined ? matches[2].slice(2).toLowerCase() : '';
    this.path = matches[3] !== undefined ? matches[3] : '';

    // <http://tools.ietf.org/html/rfc3986#section-6.2.3>
    // "In general, a URI that uses the generic syntax for authority
    // "with an empty path should be normalized to a path of '/'."
    if ( this.authority !== '' && this.path === '' ) {
        this.path = '/';
    }
    this.query = matches[4] !== undefined ? matches[4].slice(1) : '';
    this.fragment = matches[5] !== undefined ? matches[5].slice(1) : '';

    // Assume very simple authority, i.e. just a hostname (highest likelihood
    // case for µBlock)
    if ( reHostFromNakedAuthority.test(this.authority) ) {
        this.hostname = this.authority;
        this.port = '';
        return this;
    }
    // Authority contains more than just a hostname
    matches = reHostPortFromAuthority.exec(this.authority);
    if ( !matches ) {
        matches = reIPv6PortFromAuthority.exec(this.authority);
        if ( !matches ) {
            return resetAuthority(URI);
        }
    }
    this.hostname = matches[1] !== undefined ? matches[1] : '';
    // http://en.wikipedia.org/wiki/FQDN
    if ( this.hostname.endsWith('.') ) {
        this.hostname = this.hostname.slice(0, -1);
    }
    this.port = matches[2] !== undefined ? matches[2].slice(1) : '';
    return this;
};

/******************************************************************************/

//     URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
//
//       foo://example.com:8042/over/there?name=ferret#nose
//       \_/   \______________/\_________/ \_________/ \__/
//        |           |            |            |        |
//     scheme     authority       path        query   fragment
//        |   _____________________|__
//       / \ /                        \
//       urn:example:animal:ferret:nose

URI.assemble = function(bits) {
    if ( bits === undefined ) {
        bits = this.allBits;
    }
    const s = [];
    if ( this.scheme && (bits & this.schemeBit) ) {
        s.push(this.scheme, ':');
    }
    if ( this.hostname && (bits & this.hostnameBit) ) {
        s.push('//', this.hostname);
    }
    if ( this.port && (bits & this.portBit) ) {
        s.push(':', this.port);
    }
    if ( this.path && (bits & this.pathBit) ) {
        s.push(this.path);
    }
    if ( this.query && (bits & this.queryBit) ) {
        s.push('?', this.query);
    }
    if ( this.fragment && (bits & this.fragmentBit) ) {
        s.push('#', this.fragment);
    }
    return s.join('');
};

/******************************************************************************/

URI.originFromURI = function(uri) {
    const matches = reOriginFromURI.exec(uri);
    return matches !== null ? matches[0].toLowerCase() : '';
};

/******************************************************************************/

URI.schemeFromURI = function(uri) {
    const matches = reSchemeFromURI.exec(uri);
    if ( !matches ) { return ''; }
    return matches[0].slice(0, -1).toLowerCase();
};

/******************************************************************************/

URI.authorityFromURI = function(uri) {
    const matches = reAuthorityFromURI.exec(uri);
    if ( !matches ) { return ''; }
    return matches[1].slice(2).toLowerCase();
};

/******************************************************************************/

// The most used function, so it better be fast.

// https://github.com/gorhill/uBlock/issues/1559
//   See http://en.wikipedia.org/wiki/FQDN
// https://bugzilla.mozilla.org/show_bug.cgi?id=1360285
//   Revisit punycode dependency when above issue is fixed in Firefox.

URI.hostnameFromURI = function(uri) {
    let matches = reCommonHostnameFromURL.exec(uri);
    if ( matches !== null ) { return matches[1]; }
    matches = reAuthorityFromURI.exec(uri);
    if ( matches === null ) { return ''; }
    const authority = matches[1].slice(2);
    // Assume very simple authority (most common case for µBlock)
    if ( reHostFromNakedAuthority.test(authority) ) {
        return authority.toLowerCase();
    }
    matches = reHostFromAuthority.exec(authority);
    if ( matches === null ) {
        matches = reIPv6FromAuthority.exec(authority);
        if ( matches === null ) { return ''; }
    }
    let hostname = matches[1];
    while ( hostname.endsWith('.') ) {
        hostname = hostname.slice(0, -1);
    }
    if ( reMustNormalizeHostname.test(hostname) ) {
        hostname = punycode.toASCII(hostname.toLowerCase());
    }
    return hostname;
};

/******************************************************************************/

URI.domainFromHostname = function(hostname) {
    return reIPAddressNaive.test(hostname) ? hostname : psl.getDomain(hostname);
};

URI.domain = function() {
    return this.domainFromHostname(this.hostname);
};

// It is expected that there is higher-scoped `publicSuffixList` lingering
// somewhere. Cache it. See <https://github.com/gorhill/publicsuffixlist.js>.
const psl = publicSuffixList;

/******************************************************************************/

URI.entityFromDomain = function(domain) {
    const pos = domain.indexOf('.');
    return pos !== -1 ? domain.slice(0, pos) + '.*' : '';
};

/******************************************************************************/

URI.pathFromURI = function(uri) {
    const matches = rePathFromURI.exec(uri);
    return matches !== null ? matches[1] : '';
};

/******************************************************************************/

URI.domainFromURI = function(uri) {
    if ( !uri ) { return ''; }
    return this.domainFromHostname(this.hostnameFromURI(uri));
};

/******************************************************************************/

URI.isNetworkURI = function(uri) {
    return reNetworkURI.test(uri);
};

const reNetworkURI = /^(?:ftps?|https?|wss?):\/\//;

/******************************************************************************/

URI.isNetworkScheme = function(scheme) {
    return reNetworkScheme.test(scheme);
};

const reNetworkScheme = /^(?:ftps?|https?|wss?)$/;

/******************************************************************************/

// Normalize the way µBlock expects it

URI.normalizedURI = function() {
    // Will be removed:
    // - port
    // - user id/password
    // - fragment
    return this.assemble(this.normalizeBits);
};

/******************************************************************************/

URI.rootURL = function() {
    if ( !this.hostname ) { return ''; }
    return this.assemble(this.schemeBit | this.hostnameBit);
};

/******************************************************************************/

URI.isValidHostname = function(hostname) {
    try {
        return reValidHostname.test(hostname);
    }
    catch (e) {
    }
    return false;
};

/******************************************************************************/

// Return the parent domain. For IP address, there is no parent domain.

URI.parentHostnameFromHostname = function(hostname) {
    // `locahost` => ``
    // `example.org` => `example.org`
    // `www.example.org` => `example.org`
    // `tomato.www.example.org` => `example.org`
    const domain = this.domainFromHostname(hostname);

    // `locahost` === `` => bye
    // `example.org` === `example.org` => bye
    // `www.example.org` !== `example.org` => stay
    // `tomato.www.example.org` !== `example.org` => stay
    if ( domain === '' || domain === hostname ) { return; }

    // Parent is hostname minus first label
    return hostname.slice(hostname.indexOf('.') + 1);
};

/******************************************************************************/

// Return all possible parent hostnames which can be derived from `hostname`,
// ordered from direct parent up to domain inclusively.

URI.parentHostnamesFromHostname = function(hostname) {
    // TODO: I should create an object which is optimized to receive
    // the list of hostnames by making it reusable (junkyard etc.) and which
    // has its own element counter property in order to avoid memory
    // alloc/dealloc.
    const domain = this.domainFromHostname(hostname);
    if ( domain === '' || domain === hostname ) {
        return [];
    }
    const nodes = [];
    for (;;) {
        const pos = hostname.indexOf('.');
        if ( pos < 0 ) { break; }
        hostname = hostname.slice(pos + 1);
        nodes.push(hostname);
        if ( hostname === domain ) { break; }
    }
    return nodes;
};

/******************************************************************************/

// Return all possible hostnames which can be derived from `hostname`,
// ordered from self up to domain inclusively.

URI.allHostnamesFromHostname = function(hostname) {
    const nodes = this.parentHostnamesFromHostname(hostname);
    nodes.unshift(hostname);
    return nodes;
};

/******************************************************************************/

URI.toString = function() {
    return this.assemble();
};

/******************************************************************************/

// Export

return URI;

/******************************************************************************/

})();

/******************************************************************************/