uBlock Origin

extension name.

Behingoz, blokeatzaile eraginkor bat. PUZ eta memorian arina.

this will be in the Chrome web store: must be 132 characters or less

uBlock₀ — Kontrol panela

English: uBlock₀ — Dashboard

Ezarpenak

appears as tab name in dashboard

Iragazki-zerrendak

appears as tab name in dashboard

Nire iragazkiak

appears as tab name in dashboard

Nire arauak

appears as tab name in dashboard

Zerrenda zuria

appears as tab name in dashboard

Lasterbideak

appears as tab name in dashboard

uBlock₀ — Egunkaria

Title for the logger window

Honi buruz

appears as tab name in dashboard

uBlock₀ — Aktibo-ikuslea

Title for the asset viewer page

Ezarpen aurreratuak

Title for the advanced settings page

Klik: gaitu/ezgaitu uBlock₀ gune honetan.

Ktrl+klik: ezgaitu uBlock₀ orri honetan soilik.

English: Click: disable/enable uBlock₀ for this site.

Ctrl+click: disable uBlock₀ only on this page.

Klik: Gaitu/ezgaitu uBlock₀ gune honetan.

Ktrl+klik: Desgaitu uBlock₀ orri honetan soilik.

Message to be read by screen readers

Kilk: Gaitu uBlock₀ gune honetan.

Message to be read by screen readers

blokeatutako eskariak

English: requests blocked

orri honetan

English: on this page

{{count}} edo %{{percent}}

Example: 15 or 13%

instalaziotik

English: since install

edo

English: or

Ireki kontrol panela

English: Click to open the dashboard

Elementuak lekuz aldatzeko modua

Tooltip for the element-zapper icon in the popup panel

Elementuak hautatzeko modua

English: Enter element picker mode

Ireki egunkaria

Tooltip used for the logger icon in the panel

Txandakatu laster-leihoen blokeoa gune honetan

Tooltip for the no-popups per-site switch

Click: Blokeatu gune honetako laster leiho guztiak

Click: Utzi gune honetako laster-leihoak blokeatzeari

Txandakatu multimedia elementu handiak blokeatzea gune honetan

Tooltip for the no-large-media per-site switch

Klik: Blokeatu gune honetako tamaina handiko elementuak

Klik: Utzi gune honetako tamaina handiko elementuak blokeatzeari

Txandakatu iragazki kosmetikoa gune honetan

Tooltip for the no-cosmetic-filtering per-site switch

Klik: Desgaitu iragazki kosmetikoak gune honetan

Klik: Gaitu iragazki kosmetikoak gune honetan

Txandakatu urruneko letra tipoen blokeoa gune honetan

Tooltip for the no-remote-fonts per-site switch

Klik: Blokeatu urruneko letra-tipoak gune honetan

Klik: Utzi urruneko letra-tipoak gune honetan blokeatzeari

Egin klik gune honetan JavaScript desgaitzeko

Tooltip for the no-scripting per-site switch

Egin klik gune honetan JavaScript desgaitzeari uzteko

Arau orokorrak: Zutabe hau gune guztietan aplikatzen diren arauentzat da.

Tooltip when hovering the top-most cell of the global-rules column.

Tokiko arauak: Zutabe hau soilik uneko gunean aplikatuko diren arauentzat da. Tokiko arauek arau orokorrak gainidazten dituzte.

Tooltip when hovering the top-most cell of the local-rules column.

Sakatu aldaketak gordetzeko.

Tooltip when hovering over the padlock in the dynamic filtering pane.

Sakatu aldaketak desegiteko.

Tooltip when hovering over the eraser in the dynamic filtering pane.

guztiak

irudiak

Hirugarrengoak

Hirugarrengoen CSS/Irudiak

barne scriptak

bertako scriptak

hirugarrengoen scriptak

hirugarrengoen markoak

konektatutako domeinuak

appears in popup

{{count}} / {{total}}

appears in popup

Sortu

English: Create

Hautatu

English: Pick

Irten

English: Quit

Aurreikusi

Element picker preview mode: will cause the elements matching the current filter to be removed from the page

Sare iragazkiak

English: header for a type of filter in the element picker dialog

Iragazki kosmetikoak

English: Cosmetic filters

Klik, Ktrl-klik

English: Click, Ctrl-click

Blokeatu elementua

English: Block element

Ezkutatu blokeatutako elementuen hutsuneak

English: Hide placeholders of blocked elements

Bistaratu blokeatutako eskari kopurua ikonoan

English: Show the number of blocked requests on the icon

Desgaitu argibideak

A checkbox in the Settings pane

Erabili laster-menua egokia denean

English: Make use of context menu where appropriate

Kolore-itsuentzat egokia

English: Color-blind friendly

Gaitu hodei biltegiratzearen euskarria

Erabiltzaile aurreratua naiz (Irakurri beharrekoa)

ezarpen aurreratuak

For the tooltip of a link which gives access to advanced settings

Desgaitu aurrez-kargatzea (blokeatutako sare eskaeretako edozein konexio galarazteko)

English:

Desgaitu loturen auditoretza

English:

Galarazi WebRTCk tokiko IP helbidea iragartzea

English:

Lehenetsitako portaera

Lehenetsitako portaera hauek gunez gune gainidatzi daitezke

Desgaitu iragazki kosmetikoa

Blokeatu {{input:number}} kB baino handiagoak diren multimedia elementuak

Blokeatu urruneko letra-tipoak

Desgaitu JavaScript

The default state for the per-site no-scripting switch

Blokeatu CSP txostenak

background information: https://github.com/gorhill/uBlock/issues/3150

Erabilitako biltegiratzea: {{value}} byte

English: Storage used: {{}} bytes

Azken berreskuratzea:

English: Last restore:

Azken babeskopia:

English: Last backup:

{{netFilterCount}} sare iragazki + {{cosmeticFilterCount}} iragazki kosmetiko hemendik:

Appears at the top of the _3rd-party filters_ pane

{{used}} erabilita, guztira {{total}}

Appears aside each filter list in the _3rd-party filters_ pane

Automatikoki eguneratu iragazkien zerrenda.

A checkbox in the _3rd-party filters_ pane

Eguneratu orain

A button in the in the _3rd-party filters_ pane

Garbitu cache guztiak

Prozesatu eta ezarri iragazki kosmetikoak.

English: Parse and enforce Adblock+ element hiding filters.

Aukera honek Adblock Plusekin bateragarriak diren “elementuak ezkutatzeko” iragazkiak prozesatzea eta ezartzea gaitzen du. Iragazki hauek nagusiki kosmetikoak dira, web orri batean itsusitzat jo diren eta sare iragazkien bidez blokeatzerik ez dauden elementuak ezkutatzeko balio dute.

Ezaugarri hau gaitzeak uBlock₀en memoria erabilera handitzen du.

Describes the purpose of the 'Parse and enforce cosmetic filters' feature.

Ezikusi iragazki kosmetiko orokorrak

This will cause uBO to ignore all generic cosmetic filters.

Iragazki kosmetiko orokorrak webgune guztietan aplikatzeko sortu diren iragazki kosmetikoak dira.
uBlock₀ aplikazioak ongi kudeatzen baditu ere iragazki kosmetiko orokorrek wegune batzuetan memoria edo PUZ erabilera nabarmena ekar dezakete handiak eta antzinakoak diren horietan gehienbat.
Aukera hau gaituz iragazki kosmetiko orokorren erabileraren ondoriozko memoria eta PUZ erabilera gehigarria sahiestuko da, eta baita uBlock₀ beraren memoria erabilera gutxiagotu.
 Ahalmen gutxiagoko gailuetan aukera hau gaitzea aholkatzen da.

Describes the purpose of the 'Ignore generic cosmetic filters' feature.

Lists of blocked hosts

English: Lists of blocked hosts

Aplikatu aldaketak

English: Apply changes

Barnekoa

Header for the uBlock filters section in 'Filter lists pane'

Iragarkiak

English: Ads

Pribatutasuna

English: Privacy

Malware domeinuak

English: Malware domains

Eragozpenak

The header identifying the filter lists in the category 'annoyances'

Helburu anitzekoak

English: Multipurpose

Eskualdeak, hizkuntzak

English: Regions, languages

Pertsonala

English: Custom

Inportatu...

The label for the checkbox used to import external filter lists

URL bat lerroko. Baliogabeko URL-ak ezikusiko dira.

Short information about how to use the textarea to import external filter lists by URL

Zaharkituta.

used as a tooltip for the out-of-date icon beside a list

Azken eguneraketa: {{ago}}.

used as a tooltip for the clock icon beside a list

Eguneratzen...

used as a tooltip for the spinner icon beside a list

Sare errore batek baliabidea eguneratzea eragotzi du.

used as a tooltip for error icon beside a list

Iragazki bat lerroko. Iragazkia hostalari izen soila izan daiteke, edo Adblock Plusekin bateragarria den iragazki bat. Hasieran <code>!</code> duten lerroak ezikusiko dira.

Short information about how to create custom filters

Inportatu eta gehitu

English: Import and append

Esportatu

English: Export

nire-ublock-iragazki-estatikoak_{{datetime}}.txt

English: my-ublock-static-filters_{{datetime}}.txt

Aplikatu aldaketak

English: Apply changes

Behin betiko arauak

header

Behin behineko arauak

header

Baztertu

This will remove all temporary rules

Behin betiko bihurtu

This will persist temporary rules

Aldatu

Will enable manual-edit mode (textarea)

Gorde

Will save manually-edited content and exit manual-edit mode

Baztertu

Will discard manually-edited content and exit manual-edit mode

Inportatu fitxategitik...

Esportatu fitxategira

nire-ublock-arau-dinamikoak_{{datetime}}.txt

default file name to use

Zure iragazki dinamikoen arau zerrenda.

English: List of your dynamic filtering rules.

Arauen sintaxia: jatorria helburua mota ekintza(Dokumentazio osoa).

English: dynamic rule syntax and full documentation.

Zerrenda zuriaren direktibek uBlock zeintzu web orrietan desgaituko den zehazten dute. Sarrera bat lerroko. Baliogabeko ostalari izenak ezikusiko dira.

English: An overview of the content of the dashboard's Whitelist pane.

Inportatu eta gehitu

English: Import and append

Esportatu

English: Export

nire-ublock-zerrendazuria_{{datetime}}.txt

English: my-ublock-whitelist_{{datetime}}.txt

Aplikatu aldaketak

English: Apply changes

Mota

English: Type

Domeinua

English: Domain

URL

English: URL

Iragazkia

English: Filter

Guztiak

Appears in the logger's tab selector

Atzeko planoan

Pretty name for behind-the-scene network requests

Uneko fitxa

Appears in the logger's tab selector

Birkargatu fitxako edukia

Tooltip for the reload button in the logger page

Txandakatu DOM ikuskatzailea

Tooltip for the DOM inspector button in the logger page

Txandakatu laster-leiho panela

Tooltip for the popup panel button in the logger page

uBlock Origin wiki: Egunkaria

Tooltip for the top-right info label in the logger page

Garbitu egunkaria

Tooltip for the eraser in the logger page; used to blank the content of the logger

Pausatu egunkaria (baztertu jasotako informazioa)

Tooltip for the pause button in the logger page

Kendu pausa egunkariari

Tooltip for the play button in the logger page

Txandakatu egunkariaren iragazkia

Tooltip for the row filterer button in the logger page

iragazi egunkariko sarrerak

Placeholder string for logger output filtering input field

Egunkaria iragazteko aukerak

Tooltip for the button to bring up logger output filtering options

Ez

A keyword in the built-in row filtering expression

eventful

A keyword in the built-in row filtering expression: all items corresponding to uBO doing something (blocked, allowed, redirected, etc.)

blokeatuta

baimenduta

Xehetasunak

Small header to identify the 'Details' pane for a specific logger entry

Iragazkia

Label to identify a filter field

Iragazki-zerrendak

Label to identify a filter list field

Araua

Label to identify a rule field

Testuingurua

Label to identify a context field (typically a hostname)

Erro testuingurua

Label to identify a root context field (typically a hostname)

Partyness

Label to identify a field providing partyness information

Mota

Label to identify the type of an entry

URLa

Label to identify the URL of an entry

URL araua

Small header to identify the dynamic URL filtering section

Testuingurua:

Label for the context selector

Mota:

Label for the type selector

Iragazketa estatikoa

Small header to identify the static filtering section

{{action}} {{type}} motako sare eskariak {{br}}URL helbidea {{url}} helbidearekin bat datorrenean {{br}}eta jatorria {{origin}} denean,{{br}}{{importance}} bat datorren salbuespen iragazki bat badago.

Used in the static filtering wizard

Blokeatu

Used in the static filtering wizard

Baimendu

Used in the static filtering wizard

mota “{{type}}”

Used in the static filtering wizard

edozein mota

Used in the static filtering wizard

hemendik “{{origin}}”

Used in the static filtering wizard

edonondik

Used in the static filtering wizard

kenduta

Used in the static filtering wizard

nahiz eta

Used in the static filtering wizard

<code>{{filter}}</code> iragazki estatikoa aurkitu da hemen:

Below this sentence, the filter list(s) in which the filter was found

<code>{{filter}}</code> iragazki estatikoa ezin izan da aurkitu orain aktibatutako iragazki zerrendetan

Message to show when a filter cannot be found in any filter lists

Logger entries which do not fulfill all three conditions below will be automatically discarded:

Logger setting: A sentence to describe the purpose of the settings below

Preserve entries from the last {{input}} minutes

A logger setting

Preserve at most {{input}} page loads per tab

A logger setting

Preserve at most {{input}} entries per tab

A logger setting

Use {{input}} lines per entry in vertically expanded mode

A logger setting

Ezkutatu zutabeak:

Logger settings: a sentence to describe the purpose of the checkboxes below

{{input}} Denbora

A label for the time column

{{input}} Iragazkia/araua

A label for the filter or rule column

{{input}} Testuingurua

A label for the context column

{{input}} Partyness

A label for the partyness column

Zerrenda

Label for radio-button to pick export format

Taula

Laua

Label for radio-button to pick export text format

Markdown

Aldaketa egunkaria

Wiki

English: project' wiki on GitHub

Laguntza

A link for where to get support

Akats datu-basea

Text for a link to official issue tracker

Iturburu kodea (GPLv3)

English: Source code (GPLv3)

Parte-hartzaileak

English: Contributors

Kanpo menpekotasunak (GPLv3 lizentziarekin bateragarriak):

Shown in the About pane

Egin babeskopia fitxategian

Text for button to create a backup of all settings

English: my-ublock-backup_{{datetime}}.txt

Berreskuratu fitxategitik...

English: Restore from file...

Leheneratu lehenetsitako ezarpenetara...

English: Reset to default settings...

Zure ezarpen guztiak gainidatziko dira {{time}}ko datuak erabiliz, eta uBlock₀ berrabiaraziko da.

Gainidatzi ezarpen guztiak babeskopiako datuekin?

Message asking user to confirm restore

Datuak ezin dira irakurri edo baliogabeak dira

Message to display when an error occurred during restore

Zure ezarpen guztiak ezabatuko dira, eta uBlock₀ berrabiaraziko da.

Leheneratu uBlock₀ jatorrizko ezarpenetara?

Message asking user to confirm reset

Sare-errorea: {{msg}}

English: Network error: {{msg}}

uBlock₀: Gehitu honako URL hauek zure iragazki pertsonalizatuen zerrendara?

Izenburua: "{{title}}"
URLa: {{url}}

English: The message seen by the user to confirm subscription to a ABP filter list

duela minutu bat

English: a minute ago

duela {{value}} minutu

English: {{value}} minutes ago

duela ordu bat

English: an hour ago

duela {{value}} ordu

English: {{value}} hours ago

duela egun bat

English: a day ago

duela {{value}} egun

English: {{value}} days ago

Bistaratu kontrol panela

Firefox/Fennec-specific: Show Dashboard

Bistaratu egunkaria

Firefox/Fennec-specific: Show Logger

itzalita

Firefox-specific: appears as 'uBlock₀ (off)'

uBlock₀ek orri hau kargatzea galarazi du:

English: uBlock₀ has prevented the following page from loading:

Iragazki hau dela eta

English: Because of the following filter

parametrorik gabe

label to be used for the parameter-less URL: https://cloud.githubusercontent.com/assets/585534/9832014/bfb1b8f0-593b-11e5-8a27-fba472a5529a.png

Hemen aurkitua:

English: List of filter list names follows

Atzera

English: Go back

Itxi leiho hau

English: Close this window

Ezgaitu blokeatze zorrotza {{hostname}} gunean

English: Disable strict blocking for {{hostname}} ...

Une batez

English: Temporarily

Behin betiko

English: Permanently

Esportatu hodei biltegiratzera

tooltip

Inportatu hodei biltegiratzetik

tooltip

Inportatu hodeiko biltegiratzetik eta nahastu oraingo ezarpenekin

tooltip

Gailu honen izena:

used as a prompt for the user to provide a custom device name

Abisua! Ez aldatu ezarpen aurreratu hauek zertan ari zaren ez badakizu.

A warning to users at the top of 'Advanced settings' page

Bidali

for generic 'Submit' buttons

Aplikatu aldaketak

for generic 'Apply changes' buttons

Baztertu

for generic 'Revert' buttons

byte

Une batez baimendu multimedia elementu handiak

A context menu entry, present when large media elements have been blocked on the current site

Idatzi lasterbide bat

Placeholder string for input field used to capture a keyboard shortcut

Txandakatu korritze blokeatua

Tooltip for the button used to lock scrolling between the views in the 'My rules' pane

Kopiatu arbelera

Label for buttons used to copy something to the clipboard

This entry must be the last one

so we dont need to deal with comma for last entry


keep keep keep keep replace keep keep keep keep

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

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

'use strict';

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

              +--> domCollapser
              |
              |
  domWatcher--+
              |                  +-- domSurveyor
              |                  |
              +--> domFilterer --+-- domLogger
                                 |
                                 +-- domInspector

  domWatcher:
    Watches for changes in the DOM, and notify the other components about these
    changes.

  domCollapser:
    Enforces the collapsing of DOM elements for which a corresponding
    resource was blocked through network filtering.

  domFilterer:
    Enforces the filtering of DOM elements, by feeding it cosmetic filters.

  domSurveyor:
    Surveys the DOM to find new cosmetic filters to apply to the current page.

  domLogger:
    Surveys the page to find and report the injected cosmetic filters blocking
    actual elements on the current page. This component is dynamically loaded
    IF AND ONLY IF uBO's logger is opened.

  If page is whitelisted:
    - domWatcher: off
    - domCollapser: off
    - domFilterer: off
    - domSurveyor: off
    - domLogger: off

  I verified that the code in this file is completely flushed out of memory
  when a page is whitelisted.

  If cosmetic filtering is disabled:
    - domWatcher: on
    - domCollapser: on
    - domFilterer: off
    - domSurveyor: off
    - domLogger: off

  If generic cosmetic filtering is disabled:
    - domWatcher: on
    - domCollapser: on
    - domFilterer: on
    - domSurveyor: off
    - domLogger: on if uBO logger is opened

  If generic cosmetic filtering is enabled:
    - domWatcher: on
    - domCollapser: on
    - domFilterer: on
    - domSurveyor: on
    - domLogger: on if uBO logger is opened

  Additionally, the domSurveyor can turn itself off once it decides that
  it has become pointless (repeatedly not finding new cosmetic filters).

  The domFilterer makes use of platform-dependent user stylesheets[1].

  At time of writing, only modern Firefox provides a custom implementation,
  which makes for solid, reliable and low overhead cosmetic filtering on
  Firefox.

  The generic implementation[2] performs as best as can be, but won't ever be
  as reliable and accurate as real user stylesheets.

  [1] "user stylesheets" refer to local CSS rules which have priority over,
       and can't be overriden by a web page's own CSS rules.
  [2] below, see platformUserCSS / platformHideNode / platformUnhideNode

*/

// Abort execution if our global vAPI object does not exist.
//   https://github.com/chrisaljoudi/uBlock/issues/456
//   https://github.com/gorhill/uBlock/issues/2029

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

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

vAPI.contentScript = true;

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

  The purpose of SafeAnimationFrame is to take advantage of the behavior of
  window.requestAnimationFrame[1]. If we use an animation frame as a timer,
  then this timer is described as follow:

  - time events are throttled by the browser when the viewport is not visible --
    there is no point for uBO to play with the DOM if the document is not
    visible.
  - time events are micro tasks[2].
  - time events are synchronized to monitor refresh, meaning that they can fire
    at most 1/60 (typically).

  If a delay value is provided, a plain timer is first used. Plain timers are
  macro-tasks, so this is good when uBO wants to yield to more important tasks
  on a page. Once the plain timer elapse, an animation frame is used to trigger
  the next time at which to execute the job.

  [1] https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
  [2] https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

*/

// https://github.com/gorhill/uBlock/issues/2147

vAPI.SafeAnimationFrame = function(callback) {
    this.fid = this.tid = undefined;
    this.callback = callback;
};

vAPI.SafeAnimationFrame.prototype = {
    start: function(delay) {
        if ( delay === undefined ) {
            if ( this.fid === undefined ) {
                this.fid = requestAnimationFrame(( ) => { this.onRAF(); } );
            }
            if ( this.tid === undefined ) {
                this.tid = vAPI.setTimeout(( ) => { this.onSTO(); }, 20000);
            }
            return;
        }
        if ( this.fid === undefined && this.tid === undefined ) {
            this.tid = vAPI.setTimeout(( ) => { this.macroToMicro(); }, delay);
        }
    },
    clear: function() {
        if ( this.fid !== undefined ) {
            cancelAnimationFrame(this.fid);
            this.fid = undefined;
        }
        if ( this.tid !== undefined ) {
            clearTimeout(this.tid);
            this.tid = undefined;
        }
    },
    macroToMicro: function() {
        this.tid = undefined;
        this.start();
    },
    onRAF: function() {
        if ( this.tid !== undefined ) {
            clearTimeout(this.tid);
            this.tid = undefined;
        }
        this.fid = undefined;
        this.callback();
    },
    onSTO: function() {
        if ( this.fid !== undefined ) {
            cancelAnimationFrame(this.fid);
            this.fid = undefined;
        }
        this.tid = undefined;
        this.callback();
    },
};

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

// https://github.com/uBlockOrigin/uBlock-issues/issues/552
//   Listen and report CSP violations so that blocked resources through CSP
//   are properly reported in the logger.

{
    const events = new Set();
    let timer;

    const send = function() {
        vAPI.messaging.send(
            'scriptlets',
            {
                what: 'securityPolicyViolation',
                type: 'net',
                docURL: document.location.href,
                violations: Array.from(events),
            },
            response => {
                if ( response === true ) { return; }
                stop();
            }
        );
        events.clear();
    };

    const sendAsync = function() {
        if ( timer !== undefined ) { return; }
        timer = self.requestIdleCallback(
            ( ) => { timer = undefined; send(); },
            { timeout: 2000 }
        );
    };

    const listener = function(ev) {
        if ( ev.isTrusted !== true ) { return; }
        if ( ev.disposition !== 'enforce' ) { return; }
        events.add(JSON.stringify({
            url: ev.blockedURL || ev.blockedURI,
            policy: ev.originalPolicy,
            directive: ev.effectiveDirective || ev.violatedDirective,
        }));
        sendAsync();
    };

    const stop = function() {
        events.clear();
        if ( timer !== undefined ) {
            self.cancelIdleCallback(timer);
            timer = undefined;
        }
        document.removeEventListener('securitypolicyviolation', listener);
        vAPI.shutdown.remove(stop);
    };

    document.addEventListener('securitypolicyviolation', listener);
    vAPI.shutdown.add(stop);

    // We need to call at least once to find out whether we really need to
    // listen to CSP violations.
    sendAsync();
}

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

vAPI.domWatcher = (function() {

    const addedNodeLists = [];
    const removedNodeLists = [];
    const addedNodes = [];
    const ignoreTags = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style' ]);
    const listeners = [];

    let domIsReady = false,
        domLayoutObserver,
        listenerIterator = [], listenerIteratorDirty = false,
        removedNodes = false,
        safeObserverHandlerTimer;

    const safeObserverHandler = function() {
        //console.time('dom watcher/safe observer handler');
        let i = addedNodeLists.length,
            j = addedNodes.length;
        while ( i-- ) {
            const nodeList = addedNodeLists[i];
            let iNode = nodeList.length;
            while ( iNode-- ) {
                const node = nodeList[iNode];
                if ( node.nodeType !== 1 ) { continue; }
                if ( ignoreTags.has(node.localName) ) { continue; }
                if ( node.parentElement === null ) { continue; }
                addedNodes[j++] = node;
            }
        }
        addedNodeLists.length = 0;
        i = removedNodeLists.length;
        while ( i-- && removedNodes === false ) {
            const nodeList = removedNodeLists[i];
            let iNode = nodeList.length;
            while ( iNode-- ) {
                if ( nodeList[iNode].nodeType !== 1 ) { continue; }
                removedNodes = true;
                break;
            }
        }
        removedNodeLists.length = 0;
        //console.timeEnd('dom watcher/safe observer handler');
        if ( addedNodes.length === 0 && removedNodes === false ) { return; }
        for ( const listener of getListenerIterator() ) {
            listener.onDOMChanged(addedNodes, removedNodes);
        }
        addedNodes.length = 0;
        removedNodes = false;
    };

    // https://github.com/chrisaljoudi/uBlock/issues/205
    // Do not handle added node directly from within mutation observer.
    const observerHandler = function(mutations) {
        //console.time('dom watcher/observer handler');
        let i = mutations.length;
        while ( i-- ) {
            const mutation = mutations[i];
            let nodeList = mutation.addedNodes;
            if ( nodeList.length !== 0 ) {
                addedNodeLists.push(nodeList);
            }
            nodeList = mutation.removedNodes;
            if ( nodeList.length !== 0 ) {
                removedNodeLists.push(nodeList);
            }
        }
        if ( addedNodeLists.length !== 0 || removedNodes ) {
            safeObserverHandlerTimer.start(
                addedNodeLists.length < 100 ? 1 : undefined
            );
        }
        //console.timeEnd('dom watcher/observer handler');
    };

    const startMutationObserver = function() {
        if ( domLayoutObserver !== undefined || !domIsReady ) { return; }
        domLayoutObserver = new MutationObserver(observerHandler);
        domLayoutObserver.observe(document.documentElement, {
            //attributeFilter: [ 'class', 'id' ],
            //attributes: true,
            childList: true,
            subtree: true
        });
        safeObserverHandlerTimer = new vAPI.SafeAnimationFrame(safeObserverHandler);
        vAPI.shutdown.add(cleanup);
    };

    const stopMutationObserver = function() {
        if ( domLayoutObserver === undefined ) { return; }
        cleanup();
        vAPI.shutdown.remove(cleanup);
    };

    const getListenerIterator = function() {
        if ( listenerIteratorDirty ) {
            listenerIterator = listeners.slice();
            listenerIteratorDirty = false;
        }
        return listenerIterator;
    };

    const addListener = function(listener) {
        if ( listeners.indexOf(listener) !== -1 ) { return; }
        listeners.push(listener);
        listenerIteratorDirty = true;
        if ( domIsReady !== true ) { return; }
        listener.onDOMCreated();
        startMutationObserver();
    };

    const removeListener = function(listener) {
        const pos = listeners.indexOf(listener);
        if ( pos === -1 ) { return; }
        listeners.splice(pos, 1);
        listenerIteratorDirty = true;
        if ( listeners.length === 0 ) {
            stopMutationObserver();
        }
    };

    const cleanup = function() {
        if ( domLayoutObserver !== undefined ) {
            domLayoutObserver.disconnect();
            domLayoutObserver = null;
        }
        if ( safeObserverHandlerTimer !== undefined ) {
            safeObserverHandlerTimer.clear();
            safeObserverHandlerTimer = undefined;
        }
    };

    const start = function() {
        domIsReady = true;
        for ( const listener of getListenerIterator() ) {
            listener.onDOMCreated();
        }
        startMutationObserver();
    };

    return { start, addListener, removeListener };
})();

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

vAPI.matchesProp = (( ) => {
    const docElem = document.documentElement;
    if ( typeof docElem.matches !== 'function' ) {
        if ( typeof docElem.mozMatchesSelector === 'function' ) {
            return 'mozMatchesSelector';
        } else if ( typeof docElem.webkitMatchesSelector === 'function' ) {
            return 'webkitMatchesSelector';
        } else if ( typeof docElem.msMatchesSelector === 'function' ) {
            return 'msMatchesSelector';
        }
    }
    return 'matches';
})();

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

vAPI.injectScriptlet = function(doc, text) {
    if ( !doc ) { return; }
    let script;
    try {
        script = doc.createElement('script');
        script.appendChild(doc.createTextNode(text));
        (doc.head || doc.documentElement).appendChild(script);
    } catch (ex) {
    }
    if ( script ) {
        if ( script.parentNode ) {
            script.parentNode.removeChild(script);
        }
        script.textContent = '';
    }
};

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

  The DOM filterer is the heart of uBO's cosmetic filtering.

  DOMBaseFilterer: platform-specific
  |
  |
  +---- DOMFilterer: adds procedural cosmetic filtering

*/

vAPI.DOMFilterer = (function() {

    // 'P' stands for 'Procedural'

    const PSelectorHasTextTask = class {
        constructor(task) {
            let arg0 = task[1], arg1;
            if ( Array.isArray(task[1]) ) {
                arg1 = arg0[1]; arg0 = arg0[0];
            }
            this.needle = new RegExp(arg0, arg1);
        }
        exec(input) {
            const output = [];
            for ( const node of input ) {
                if ( this.needle.test(node.textContent) ) {
                    output.push(node);
                }
            }
            return output;
        }
    };

    const PSelectorIfTask = class {
        constructor(task) {
            this.pselector = new PSelector(task[1]);
        }
        exec(input) {
            const output = [];
            for ( const node of input ) {
                if ( this.pselector.test(node) === this.target ) {
                    output.push(node);
                }
            }
            return output;
        }
    };
    PSelectorIfTask.prototype.target = true;

    const PSelectorIfNotTask = class extends PSelectorIfTask {
        constructor(task) {
            super(task);
            this.target = false;
        }
    };

    const PSelectorMatchesCSSTask = class {
        constructor(task) {
            this.name = task[1].name;
            let arg0 = task[1].value, arg1;
            if ( Array.isArray(arg0) ) {
                arg1 = arg0[1]; arg0 = arg0[0];
            }
            this.value = new RegExp(arg0, arg1);
        }
        exec(input) {
            const output = [];
            for ( const node of input ) {
                const style = window.getComputedStyle(node, this.pseudo);
                if ( style === null ) { return null; } /* FF */
                if ( this.value.test(style[this.name]) ) {
                    output.push(node);
                }
            }
            return output;
        }
    };
    PSelectorMatchesCSSTask.prototype.pseudo = null;

    const PSelectorMatchesCSSAfterTask = class extends PSelectorMatchesCSSTask {
        constructor(task) {
            super(task);
            this.pseudo = ':after';
        }
    };

    const PSelectorMatchesCSSBeforeTask = class extends PSelectorMatchesCSSTask {
        constructor(task) {
            super(task);
            this.pseudo = ':before';
        }
    };

    const PSelectorNthAncestorTask = class {
        constructor(task) {
            this.nth = task[1];
        }
        exec(input) {
            const output = [];
            for ( let node of input ) {
                let nth = this.nth;
                for (;;) {
                    node = node.parentElement;
                    if ( node === null ) { break; }
                    nth -= 1;
                    if ( nth !== 0 ) { continue; }
                    output.push(node);
                    break;
                }
            }
            return output;
        }
    };

    const PSelectorSpathTask = class {
        constructor(task) {
            this.spath = task[1];
        }
        exec(input) {
            const output = [];
            for ( let node of input ) {
                const parent = node.parentElement;
                if ( parent === null ) { continue; }
                let pos = 1;
                for (;;) {
                    node = node.previousElementSibling;
                    if ( node === null ) { break; }
                    pos += 1;
                }
                const nodes = parent.querySelectorAll(
                    ':scope > :nth-child(' + pos + ')' + this.spath
                );
                for ( const node of nodes ) {
                    output.push(node);
                }
            }
            return output;
        }
    };

    const PSelectorWatchAttrs = class {
        constructor(task) {
            this.observer = null;
            this.observed = new WeakSet();
            this.observerOptions = {
                attributes: true,
                subtree: true,
            };
            const attrs = task[1];
            if ( Array.isArray(attrs) && attrs.length !== 0 ) {
                this.observerOptions.attributeFilter = task[1];
            }
        }
        // TODO: Is it worth trying to re-apply only the current selector?
        handler() {
            const filterer =
                vAPI.domFilterer && vAPI.domFilterer.proceduralFilterer;
            if ( filterer instanceof Object ) {
                filterer.onDOMChanged([ null ]);
            }
        }
        exec(input) {
            if ( input.length === 0 ) { return input; }
            if ( this.observer === null ) {
                this.observer = new MutationObserver(this.handler);
            }
            for ( const node of input ) {
                if ( this.observed.has(node) ) { continue; }
                this.observer.observe(node, this.observerOptions);
                this.observed.add(node);
            }
            return input;
        }
    };

    const PSelectorXpathTask = class {
        constructor(task) {
            this.xpe = document.createExpression(task[1], null);
            this.xpr = null;
        }
        exec(input) {
            const output = [];
            for ( const node of input ) {
                this.xpr = this.xpe.evaluate(
                    node,
                    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
                    this.xpr
                );
                let j = this.xpr.snapshotLength;
                while ( j-- ) {
                    const node = this.xpr.snapshotItem(j);
                    if ( node.nodeType === 1 ) {
                        output.push(node);
                    }
                }
            }
            return output;
        }
    };

    const PSelector = class {
        constructor(o) {
            if ( PSelector.prototype.operatorToTaskMap === undefined ) {
                PSelector.prototype.operatorToTaskMap = new Map([
                    [ ':has', PSelectorIfTask ],
                    [ ':has-text', PSelectorHasTextTask ],
                    [ ':if', PSelectorIfTask ],
                    [ ':if-not', PSelectorIfNotTask ],
                    [ ':matches-css', PSelectorMatchesCSSTask ],
                    [ ':matches-css-after', PSelectorMatchesCSSAfterTask ],
                    [ ':matches-css-before', PSelectorMatchesCSSBeforeTask ],
                    [ ':not', PSelectorIfNotTask ],
                    [ ':nth-ancestor', PSelectorNthAncestorTask ],
                    [ ':spath', PSelectorSpathTask ],
                    [ ':watch-attrs', PSelectorWatchAttrs ],
                    [ ':xpath', PSelectorXpathTask ],
                ]);
            }
            this.budget = 200; // I arbitrary picked a 1/5 second
            this.raw = o.raw;
            this.cost = 0;
            this.lastAllowanceTime = 0;
            this.selector = o.selector;
            this.tasks = [];
            const tasks = o.tasks;
            if ( !tasks ) { return; }
            for ( const task of tasks ) {
                this.tasks.push(new (this.operatorToTaskMap.get(task[0]))(task));
            }
        }
        prime(input) {
            const root = input || document;
            if ( this.selector !== '' ) {
                return root.querySelectorAll(this.selector);
            }
            return [ root ];
        }
        exec(input) {
            let nodes = this.prime(input);
            for ( const task of this.tasks ) {
                if ( nodes.length === 0 ) { break; }
                nodes = task.exec(nodes);
            }
            return nodes;
        }
        test(input) {
            const nodes = this.prime(input);
            const AA = [ null ];
            for ( const node of nodes ) {
                AA[0] = node;
                let aa = AA;
                for ( const task of this.tasks ) {
                    aa = task.exec(aa);
                    if ( aa.length === 0 ) { break; }
                }
                if ( aa.length !== 0 ) { return true; }
            }
            return false;
        }
    };
    PSelector.prototype.operatorToTaskMap = undefined;

    const DOMProceduralFilterer = function(domFilterer) {
        this.domFilterer = domFilterer;
        this.domIsReady = false;
        this.domIsWatched = false;
        this.mustApplySelectors = false;
        this.selectors = new Map();
        this.hiddenNodes = new Set();
    };

    DOMProceduralFilterer.prototype = {

        addProceduralSelectors: function(aa) {
            const addedSelectors = [];
            let mustCommit = this.domIsWatched;
            for ( let i = 0, n = aa.length; i < n; i++ ) {
                const raw = aa[i];
                const o = JSON.parse(raw);
                if ( o.style ) {
                    this.domFilterer.addCSSRule(o.style[0], o.style[1]);
                    mustCommit = true;
                    continue;
                }
                if ( o.pseudoclass ) {
                    this.domFilterer.addCSSRule(
                        o.raw,
                        'display:none!important;'
                    );
                    mustCommit = true;
                    continue;
                }
                if ( o.tasks ) {
                    if ( this.selectors.has(raw) === false ) {
                        const pselector = new PSelector(o);
                        this.selectors.set(raw, pselector);
                        addedSelectors.push(pselector);
                        mustCommit = true;
                    }
                    continue;
                }
            }
            if ( mustCommit === false ) { return; }
            this.mustApplySelectors = this.selectors.size !== 0;
            this.domFilterer.commit();
            if ( this.domFilterer.hasListeners() ) {
                this.domFilterer.triggerListeners({
                    procedural: addedSelectors
                });
            }
        },

        commitNow: function() {
            if ( this.selectors.size === 0 || this.domIsReady === false ) {
                return;
            }

            this.mustApplySelectors = false;

            //console.time('procedural selectors/dom layout changed');

            // https://github.com/uBlockOrigin/uBlock-issues/issues/341
            //   Be ready to unhide nodes which no longer matches any of
            //   the procedural selectors.
            const toRemove = this.hiddenNodes;
            this.hiddenNodes = new Set();

            let t0 = Date.now();

            for ( const entry of this.selectors ) {
                const pselector = entry[1];
                const allowance = Math.floor((t0 - pselector.lastAllowanceTime) / 2000);
                if ( allowance >= 1 ) {
                    pselector.budget += allowance * 50;
                    if ( pselector.budget > 200 ) { pselector.budget = 200; }
                    pselector.lastAllowanceTime = t0;
                }
                if ( pselector.budget <= 0 ) { continue; }
                const nodes = pselector.exec();
                const t1 = Date.now();
                pselector.budget += t0 - t1;
                if ( pselector.budget < -500 ) {
                    console.info('uBO: disabling %s', pselector.raw);
                    pselector.budget = -0x7FFFFFFF;
                }
                t0 = t1;
                for ( const node of nodes ) {
                    this.domFilterer.hideNode(node);
                    this.hiddenNodes.add(node);
                }
            }

            for ( const node of toRemove ) {
                if ( this.hiddenNodes.has(node) ) { continue; }
                this.domFilterer.unhideNode(node);
            }
            //console.timeEnd('procedural selectors/dom layout changed');
        },

        createProceduralFilter: function(o) {
            return new PSelector(o);
        },

        onDOMCreated: function() {
            this.domIsReady = true;
            this.domFilterer.commitNow();
        },

        onDOMChanged: function(addedNodes, removedNodes) {
            if ( this.selectors.size === 0 ) { return; }
            this.mustApplySelectors =
                this.mustApplySelectors ||
                addedNodes.length !== 0 ||
                removedNodes;
            this.domFilterer.commit();
        }
    };

    const DOMFilterer = class extends vAPI.DOMFilterer {
        constructor() {
            super();
            this.exceptions = [];
            this.proceduralFilterer = new DOMProceduralFilterer(this);
            this.hideNodeAttr = undefined;
            this.hideNodeStyleSheetInjected = false;
            if ( vAPI.domWatcher instanceof Object ) {
                vAPI.domWatcher.addListener(this);
            }
        }

        commitNow() {
            super.commitNow();
            this.proceduralFilterer.commitNow();
        }

        addProceduralSelectors(aa) {
            this.proceduralFilterer.addProceduralSelectors(aa);
        }

        createProceduralFilter(o) {
            return this.proceduralFilterer.createProceduralFilter(o);
        }

        getAllSelectors() {
            const out = super.getAllSelectors();
            out.procedural = Array.from(this.proceduralFilterer.selectors.values());
            return out;
        }

        getAllExceptionSelectors() {
            return this.exceptions.join(',\n');
        }

        onDOMCreated() {
            if ( super.onDOMCreated instanceof Function ) {
                super.onDOMCreated();
            }
            this.proceduralFilterer.onDOMCreated();
        }

        onDOMChanged() {
            if ( super.onDOMChanged instanceof Function ) {
                super.onDOMChanged(arguments);
            }
            this.proceduralFilterer.onDOMChanged.apply(
                this.proceduralFilterer,
                arguments
            );
        }
    };

    return DOMFilterer;
})();

vAPI.domFilterer = new vAPI.DOMFilterer();

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

vAPI.domCollapser = (function() {
    const messaging = vAPI.messaging;
    const toCollapse = new Map();
    const src1stProps = {
        embed: 'src',
        iframe: 'src',
        img: 'src',
        object: 'data'
    };
    const src2ndProps = {
        img: 'srcset'
    };
    const tagToTypeMap = {
        embed: 'object',
        iframe: 'sub_frame',
        img: 'image',
        object: 'object'
    };

    let resquestIdGenerator = 1,
        processTimer,
        cachedBlockedSet,
        cachedBlockedSetHash,
        cachedBlockedSetTimer,
        toProcess = [],
        toFilter = [],
        netSelectorCacheCount = 0;

    const cachedBlockedSetClear = function() {
        cachedBlockedSet =
        cachedBlockedSetHash =
        cachedBlockedSetTimer = undefined;
    };

    // https://github.com/chrisaljoudi/uBlock/issues/174
    //   Do not remove fragment from src URL
    const onProcessed = function(response) {
        if ( !response ) { // This happens if uBO is disabled or restarted.
            toCollapse.clear();
            return;
        }

        const targets = toCollapse.get(response.id);
        if ( targets === undefined ) { return; }
        toCollapse.delete(response.id);
        if ( cachedBlockedSetHash !== response.hash ) {
            cachedBlockedSet = new Set(response.blockedResources);
            cachedBlockedSetHash = response.hash;
            if ( cachedBlockedSetTimer !== undefined ) {
                clearTimeout(cachedBlockedSetTimer);
            }
            cachedBlockedSetTimer = vAPI.setTimeout(cachedBlockedSetClear, 30000);
        }
        if ( cachedBlockedSet === undefined || cachedBlockedSet.size === 0 ) {
            return;
        }
        const selectors = [];
        const iframeLoadEventPatch = vAPI.iframeLoadEventPatch;
        let netSelectorCacheCountMax = response.netSelectorCacheCountMax;

        for ( const target of targets ) {
            const tag = target.localName;
            let prop = src1stProps[tag];
            if ( prop === undefined ) { continue; }
            let src = target[prop];
            if ( typeof src !== 'string' || src.length === 0 ) {
                prop = src2ndProps[tag];
                if ( prop === undefined ) { continue; }
                src = target[prop];
                if ( typeof src !== 'string' || src.length === 0 ) { continue; }
            }
            if ( cachedBlockedSet.has(tagToTypeMap[tag] + ' ' + src) === false ) {
                continue;
            }
            // https://github.com/chrisaljoudi/uBlock/issues/399
            // Never remove elements from the DOM, just hide them
            target.style.setProperty('display', 'none', 'important');
            target.hidden = true;
            // https://github.com/chrisaljoudi/uBlock/issues/1048
            // Use attribute to construct CSS rule
            if ( netSelectorCacheCount <= netSelectorCacheCountMax ) {
                const value = target.getAttribute(prop);
                if ( value ) {
                    selectors.push(tag + '[' + prop + '="' + value + '"]');
                    netSelectorCacheCount += 1;
                }
            }
            if ( iframeLoadEventPatch !== undefined ) {
                iframeLoadEventPatch(target);
            }
        }

        if ( selectors.length !== 0 ) {
            messaging.send(
                'contentscript',
                {
                    what: 'cosmeticFiltersInjected',
                    type: 'net',
                    hostname: window.location.hostname,
                    selectors: selectors
                }
            );
        }
    };

    const send = function() {
        processTimer = undefined;
        toCollapse.set(resquestIdGenerator, toProcess);
        const msg = {
            what: 'getCollapsibleBlockedRequests',
            id: resquestIdGenerator,
            frameURL: window.location.href,
            resources: toFilter,
            hash: cachedBlockedSetHash
        };
        messaging.send('contentscript', msg, onProcessed);
        toProcess = [];
        toFilter = [];
        resquestIdGenerator += 1;
    };

    const process = function(delay) {
        if ( toProcess.length === 0 ) { return; }
        if ( delay === 0 ) {
            if ( processTimer !== undefined ) {
                clearTimeout(processTimer);
            }
            send();
        } else if ( processTimer === undefined ) {
            processTimer = vAPI.setTimeout(send, delay || 20);
        }
    };

    const add = function(target) {
        toProcess[toProcess.length] = target;
    };

    const addMany = function(targets) {
        for ( const target of targets ) {
            add(target);
        }
    };

    const iframeSourceModified = function(mutations) {
        for ( const mutation of mutations ) {
            addIFrame(mutation.target, true);
        }
        process();
    };
    const iframeSourceObserver = new MutationObserver(iframeSourceModified);
    const iframeSourceObserverOptions = {
        attributes: true,
        attributeFilter: [ 'src' ]
    };

    // The injected scriptlets are those which were injected in the current
    // document, from within `bootstrapPhase1`, and which scriptlets are
    // selectively looked-up from:
    // https://github.com/uBlockOrigin/uAssets/blob/master/filters/resources.txt
    const primeLocalIFrame = function(iframe) {
        if ( vAPI.injectedScripts ) {
            vAPI.injectScriptlet(iframe.contentDocument, vAPI.injectedScripts);
        }
    };

    // https://github.com/gorhill/uBlock/issues/162
    // Be prepared to deal with possible change of src attribute.
    const addIFrame = function(iframe, dontObserve) {
        if ( dontObserve !== true ) {
            iframeSourceObserver.observe(iframe, iframeSourceObserverOptions);
        }
        const src = iframe.src;
        if ( src === '' || typeof src !== 'string' ) {
            primeLocalIFrame(iframe);
            return;
        }
        if ( src.startsWith('http') === false ) { return; }
        toFilter.push({ type: 'sub_frame', url: iframe.src });
        add(iframe);
    };

    const addIFrames = function(iframes) {
        for ( const iframe of iframes ) {
            addIFrame(iframe);
        }
    };

    const onResourceFailed = function(ev) {
        if ( tagToTypeMap[ev.target.localName] !== undefined ) {
            add(ev.target);
            process();
        }
    };

    const domWatcherInterface = {
        onDOMCreated: function() {
            if ( vAPI instanceof Object === false ) { return; }
            if ( vAPI.domCollapser instanceof Object === false ) {
                if ( vAPI.domWatcher instanceof Object ) {
                    vAPI.domWatcher.removeListener(domWatcherInterface);
                }
                return;
            }
            // Listener to collapse blocked resources.
            // - Future requests not blocked yet
            // - Elements dynamically added to the page
            // - Elements which resource URL changes
            // https://github.com/chrisaljoudi/uBlock/issues/7
            // Preferring getElementsByTagName over querySelectorAll:
            //   http://jsperf.com/queryselectorall-vs-getelementsbytagname/145
            const elems = document.images ||
                          document.getElementsByTagName('img');
            for ( const elem of elems ) {
                if ( elem.complete ) {
                    add(elem);
                }
            }
            addMany(document.embeds || document.getElementsByTagName('embed'));
            addMany(document.getElementsByTagName('object'));
            addIFrames(document.getElementsByTagName('iframe'));
            process(0);

            document.addEventListener('error', onResourceFailed, true);

            vAPI.shutdown.add(function() {
                document.removeEventListener('error', onResourceFailed, true);
                if ( processTimer !== undefined ) {
                    clearTimeout(processTimer);
                }
            });
        },
        onDOMChanged: function(addedNodes) {
            if ( addedNodes.length === 0 ) { return; }
            for ( const node of addedNodes ) {
                if ( node.localName === 'iframe' ) {
                    addIFrame(node);
                }
                if ( node.childElementCount === 0 ) { continue; }
                const iframes = node.getElementsByTagName('iframe');
                if ( iframes.length !== 0 ) {
                    addIFrames(iframes);
                }
            }
            process();
        }
    };

    if ( vAPI.domWatcher instanceof Object ) {
        vAPI.domWatcher.addListener(domWatcherInterface);
    }

    return { add, addMany, addIFrame, addIFrames, process };
})();

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

vAPI.domSurveyor = (function() {
    const messaging = vAPI.messaging;
    const queriedIds = new Set();
    const queriedClasses = new Set();
    const maxSurveyNodes = 65536;
    const maxSurveyTimeSlice = 4;
    const maxSurveyBuffer = 64;

    let domFilterer,
        hostname = '',
        surveyCost = 0;

    const pendingNodes = {
        nodeLists: [],
        buffer: [
            null, null, null, null, null, null, null, null,
            null, null, null, null, null, null, null, null,
            null, null, null, null, null, null, null, null,
            null, null, null, null, null, null, null, null,
            null, null, null, null, null, null, null, null,
            null, null, null, null, null, null, null, null,
            null, null, null, null, null, null, null, null,
            null, null, null, null, null, null, null, null,
        ],
        j: 0,
        accepted: 0,
        iterated: 0,
        stopped: false,
        add: function(nodes) {
            if ( nodes.length === 0 || this.accepted >= maxSurveyNodes ) {
                return;
            }
            this.nodeLists.push(nodes);
            this.accepted += nodes.length;
        },
        next: function() {
            if ( this.nodeLists.length === 0 || this.stopped ) { return 0; }
            const nodeLists = this.nodeLists;
            let ib = 0;
            do {
                const nodeList = nodeLists[0];
                let j = this.j;
                let n = j + maxSurveyBuffer - ib;
                if ( n > nodeList.length ) {
                    n = nodeList.length;
                }
                for ( let i = j; i < n; i++ ) {
                    this.buffer[ib++] = nodeList[j++];
                }
                if ( j !== nodeList.length ) {
                    this.j = j;
                    break;
                }
                this.j = 0;
                this.nodeLists.shift();
            } while ( ib < maxSurveyBuffer && nodeLists.length !== 0 );
            this.iterated += ib;
            if ( this.iterated >= maxSurveyNodes ) {
                this.nodeLists = [];
                this.stopped = true;
                //console.info(`domSurveyor> Surveyed a total of ${this.iterated} nodes. Enough.`);
            }
            return ib;
        },
        hasNodes: function() {
            return this.nodeLists.length !== 0;
        },
    };

    // Extract all classes/ids: these will be passed to the cosmetic
    // filtering engine, and in return we will obtain only the relevant
    // CSS selectors.
    const reWhitespace = /\s/;

    // https://github.com/gorhill/uBlock/issues/672
    // http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens
    // http://jsperf.com/enumerate-classes/6

    const surveyPhase1 = function() {
        //console.time('dom surveyor/surveying');
        const t0 = performance.now();
        const rews = reWhitespace;
        const ids = [];
        const classes = [];
        const nodes = pendingNodes.buffer;
        const deadline = t0 + maxSurveyTimeSlice;
        let qids = queriedIds;
        let qcls = queriedClasses;
        let processed = 0;
        for (;;) {
            const n = pendingNodes.next();
            if ( n === 0 ) { break; }
            for ( let i = 0; i < n; i++ ) {
                const node = nodes[i]; nodes[i] = null;
                let v = node.id;
                if ( typeof v === 'string' && v.length !== 0 ) {
                    v = v.trim();
                    if ( qids.has(v) === false && v.length !== 0 ) {
                        ids.push(v); qids.add(v);
                    }
                }
                let vv = node.className;
                if ( typeof vv === 'string' && vv.length !== 0 ) {
                    if ( rews.test(vv) === false ) {
                        if ( qcls.has(vv) === false ) {
                            classes.push(vv); qcls.add(vv);
                        }
                    } else {
                        vv = node.classList;
                        let j = vv.length;
                        while ( j-- ) {
                            const v = vv[j];
                            if ( qcls.has(v) === false ) {
                                classes.push(v); qcls.add(v);
                            }
                        }
                    }
                }
            }
            processed += n;
            if ( performance.now() >= deadline ) { break; }
        }
        const t1 = performance.now();
        surveyCost += t1 - t0;
        //console.info(`domSurveyor> Surveyed ${processed} nodes in ${(t1-t0).toFixed(2)} ms`);
        // Phase 2: Ask main process to lookup relevant cosmetic filters.
        if ( ids.length !== 0 || classes.length !== 0 ) {
            messaging.send(
                'contentscript',
                {
                    what: 'retrieveGenericCosmeticSelectors',
                    hostname: hostname,
                    ids: ids,
                    classes: classes,
                    exceptions: domFilterer.exceptions,
                    cost: surveyCost
                },
                surveyPhase3
            );
        } else {
            surveyPhase3(null);
        }
        //console.timeEnd('dom surveyor/surveying');
    };

    const surveyTimer = new vAPI.SafeAnimationFrame(surveyPhase1);

    // This is to shutdown the surveyor if result of surveying keeps being
    // fruitless. This is useful on long-lived web page. I arbitrarily
    // picked 5 minutes before the surveyor is allowed to shutdown. I also
    // arbitrarily picked 256 misses before the surveyor is allowed to
    // shutdown.
    let canShutdownAfter = Date.now() + 300000,
        surveyingMissCount = 0;

    // Handle main process' response.

    const surveyPhase3 = function(response) {
        const result = response && response.result;
        let mustCommit = false;

        if ( result ) {
            let selectors = result.simple;
            if ( Array.isArray(selectors) && selectors.length !== 0 ) {
                domFilterer.addCSSRule(
                    selectors,
                    'display:none!important;',
                    { type: 'simple' }
                );
                mustCommit = true;
            }
            selectors = result.complex;
            if ( Array.isArray(selectors) && selectors.length !== 0 ) {
                domFilterer.addCSSRule(
                    selectors,
                    'display:none!important;',
                    { type: 'complex' }
                );
                mustCommit = true;
            }
            selectors = result.injected;
            if ( typeof selectors === 'string' && selectors.length !== 0 ) {
                domFilterer.addCSSRule(
                    selectors,
                    'display:none!important;',
                    { injected: true }
                );
                mustCommit = true;
            }
            selectors = result.excepted;
            if ( Array.isArray(selectors) && selectors.length !== 0 ) {
                domFilterer.exceptCSSRules(selectors);
            }
        }

        if ( pendingNodes.stopped === false ) {
            if ( pendingNodes.hasNodes() ) {
                surveyTimer.start(1);
            }
            if ( mustCommit ) {
                surveyingMissCount = 0;
                canShutdownAfter = Date.now() + 300000;
                return;
            }
            surveyingMissCount += 1;
            if ( surveyingMissCount < 256 || Date.now() < canShutdownAfter ) {
                return;
            }
        }

        //console.info('dom surveyor shutting down: too many misses');

        surveyTimer.clear();
        vAPI.domWatcher.removeListener(domWatcherInterface);
        vAPI.domSurveyor = null;
    };

    const domWatcherInterface = {
        onDOMCreated: function() {
            if (
                vAPI instanceof Object === false ||
                vAPI.domSurveyor instanceof Object === false ||
                vAPI.domFilterer instanceof Object === false
            ) {
                if ( vAPI instanceof Object ) {
                    if ( vAPI.domWatcher instanceof Object ) {
                        vAPI.domWatcher.removeListener(domWatcherInterface);
                    }
                    vAPI.domSurveyor = null;
                }
                return;
            }
            //console.time('dom surveyor/dom layout created');
            domFilterer = vAPI.domFilterer;
            pendingNodes.add(document.querySelectorAll('[id],[class]'));
            surveyTimer.start();
            //console.timeEnd('dom surveyor/dom layout created');
        },
        onDOMChanged: function(addedNodes) {
            if ( addedNodes.length === 0 ) { return; }
            //console.time('dom surveyor/dom layout changed');
            let i = addedNodes.length;
            while ( i-- ) {
                const node = addedNodes[i];
                pendingNodes.add([ node ]);
                if ( node.childElementCount === 0 ) { continue; }
                pendingNodes.add(node.querySelectorAll('[id],[class]'));
            }
            if ( pendingNodes.hasNodes() ) {
                surveyTimer.start(1);
            }
            //console.timeEnd('dom surveyor/dom layout changed');
        }
    };

    const start = function(details) {
        if ( vAPI.domWatcher instanceof Object === false ) { return; }
        hostname = details.hostname;
        vAPI.domWatcher.addListener(domWatcherInterface);
    };

    return { start };
})();

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

// Bootstrapping allows all components of the content script to be launched
// if/when needed.

vAPI.bootstrap = (function() {

    const bootstrapPhase2 = function() {
        // This can happen on Firefox. For instance:
        // https://github.com/gorhill/uBlock/issues/1893
        if ( window.location === null ) { return; }
        if ( vAPI instanceof Object === false ) { return; }

        vAPI.messaging.send(
            'contentscript',
            { what: 'shouldRenderNoscriptTags' }
        );

        if ( vAPI.domWatcher instanceof Object ) {
            vAPI.domWatcher.start();
        }

        // Element picker works only in top window for now.
        if (
            window !== window.top ||
            vAPI.domFilterer instanceof Object === false
        ) {
            return;
        }

        // To send mouse coordinates to main process, as the chrome API fails
        // to provide the mouse position to context menu listeners.
        // https://github.com/chrisaljoudi/uBlock/issues/1143
        // Also, find a link under the mouse, to try to avoid confusing new tabs
        // as nuisance popups.
        // Ref.: https://developer.mozilla.org/en-US/docs/Web/Events/contextmenu

        const onMouseClick = function(ev) {
            let elem = ev.target;
            while ( elem !== null && elem.localName !== 'a' ) {
                elem = elem.parentElement;
            }
            vAPI.messaging.send(
                'contentscript',
                {
                    what: 'mouseClick',
                    x: ev.clientX,
                    y: ev.clientY,
                    url: elem !== null && ev.isTrusted !== false ? elem.href : ''
                }
            );
        };

        document.addEventListener('mousedown', onMouseClick, true);

        // https://github.com/gorhill/uMatrix/issues/144
        vAPI.shutdown.add(function() {
            document.removeEventListener('mousedown', onMouseClick, true);
        });
    };

    // https://github.com/uBlockOrigin/uBlock-issues/issues/403
    //   If there was a spurious port disconnection -- in which case the
    //   response is expressly set to `null`, rather than undefined or
    //   an object -- let's stay around, we may be given the opportunity
    //   to try bootstrapping again later.

    const bootstrapPhase1 = function(response) {
        if ( response === null ) { return; }
        vAPI.bootstrap = undefined;

        // cosmetic filtering engine aka 'cfe'
        const cfeDetails = response && response.specificCosmeticFilters;
        if ( !cfeDetails || !cfeDetails.ready ) {
            vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer =
            vAPI.domSurveyor = vAPI.domIsLoaded = null;
            return;
        }

        if ( response.noCosmeticFiltering ) {
            vAPI.domFilterer = null;
            vAPI.domSurveyor = null;
        } else {
            const domFilterer = vAPI.domFilterer;
            if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) {
                vAPI.domSurveyor = null;
            }
            domFilterer.exceptions = cfeDetails.exceptionFilters;
            domFilterer.hideNodeAttr = cfeDetails.hideNodeAttr;
            domFilterer.hideNodeStyleSheetInjected =
                cfeDetails.hideNodeStyleSheetInjected === true;
            domFilterer.addCSSRule(
                cfeDetails.declarativeFilters,
                'display:none!important;'
            );
            domFilterer.addCSSRule(
                cfeDetails.highGenericHideSimple,
                'display:none!important;',
                { type: 'simple', lazy: true }
            );
            domFilterer.addCSSRule(
                cfeDetails.highGenericHideComplex,
                'display:none!important;',
                { type: 'complex', lazy: true }
            );
            domFilterer.addCSSRule(
                cfeDetails.injectedHideFilters,
                'display:none!important;',
                { injected: true }
            );
            domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters);
            domFilterer.exceptCSSRules(cfeDetails.exceptedFilters);
        }

        if ( cfeDetails.networkFilters.length !== 0 ) {
            vAPI.userStylesheet.add(
                cfeDetails.networkFilters + '\n{display:none!important;}');
        }

        vAPI.userStylesheet.apply();

        // Library of resources is located at:
        // https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt
        if ( response.scriptlets ) {
            vAPI.injectScriptlet(document, response.scriptlets);
            vAPI.injectedScripts = response.scriptlets;
        }

        if ( vAPI.domSurveyor instanceof Object ) {
            vAPI.domSurveyor.start(cfeDetails);
        }

        // https://github.com/chrisaljoudi/uBlock/issues/587
        // If no filters were found, maybe the script was injected before
        // uBlock's process was fully initialized. When this happens, pages
        // won't be cleaned right after browser launch.
        if (
            typeof document.readyState === 'string' &&
            document.readyState !== 'loading'
        ) {
            bootstrapPhase2();
        } else {
            document.addEventListener(
                'DOMContentLoaded',
                bootstrapPhase2,
                { once: true }
            );
        }
    };

    return function() {
        vAPI.messaging.send(
            'contentscript',
            {
                what: 'retrieveContentScriptParameters',
                url: window.location.href,
                isRootFrame: window === window.top,
                charset: document.characterSet,
            },
            bootstrapPhase1
        );
    };
})();

// This starts bootstrap process.
vAPI.bootstrap();

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

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


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

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

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

(function() {

'use strict';

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

// For all media resources which have failed to load, trigger a reload.

var elems, i, elem, src;

// <audio> and <video> elements.
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement

elems = document.querySelectorAll('audio,video');
i = elems.length;
while ( i-- ) {
    elem = elems[i];
    if ( elem.error !== null ) {
        elem.load();
    }
}

// <img> elements.
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement

elems = document.querySelectorAll('img');
i = elems.length;
while ( i-- ) {
    elem = elems[i];
    if ( elem.naturalWidth !== 0 && elem.naturalHeight !== 0 ) {
        continue;
    }
    if ( window.getComputedStyle(elem).getPropertyValue('display') === 'none' ) {
        continue;
    }
    src = elem.getAttribute('src');
    if ( src ) {
        elem.removeAttribute('src');
        elem.setAttribute('src', src);
    }
}

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

})();

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

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

'use strict';

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

A PageRequestStore object is used to store net requests in two ways:

To record distinct net requests
To create a log of net requests

**/

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

µBlock.PageStore = (function() {

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

const µb = µBlock;

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

// To mitigate memory churning
const netFilteringCacheJunkyard = [];
const netFilteringCacheJunkyardMax = 10;

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

const NetFilteringResultCache = function() {
    this.init();
};

NetFilteringResultCache.prototype = {
    shelfLife: 15000,

    init: function() {
        this.blocked = new Map();
        this.results = new Map();
        this.hash = 0;
        this.timer = undefined;
        return this;
    },

    dispose: function() {
        this.empty();
        if ( netFilteringCacheJunkyard.length < netFilteringCacheJunkyardMax ) {
            netFilteringCacheJunkyard.push(this);
        }
        return null;
    },

    rememberResult: function(fctxt, result) {
        if ( fctxt.tabId <= 0 ) { return; }
        if ( this.results.size === 0 ) {
            this.pruneAsync();
        }
        const key = fctxt.getDocHostname() + ' ' + fctxt.type + ' ' + fctxt.url;
        this.results.set(key, {
            result: result,
            logData: fctxt.filter,
            tstamp: Date.now()
        });
        if ( result !== 1 ) { return; }
        const now = Date.now();
        this.blocked.set(key, now);
        this.hash = now;
    },

    rememberBlock: function(fctxt) {
        if ( fctxt.tabId <= 0 ) { return; }
        if ( this.blocked.size === 0 ) {
            this.pruneAsync();
        }
        const now = Date.now();
        this.blocked.set(
            fctxt.getDocHostname() + ' ' + fctxt.type + ' ' + fctxt.url,
            now
        );
        this.hash = now;
    },

    empty: function() {
        this.blocked.clear();
        this.results.clear();
        this.hash = 0;
        if ( this.timer !== undefined ) {
            clearTimeout(this.timer);
            this.timer = undefined;
        }
    },

    prune: function() {
        const obsolete = Date.now() - this.shelfLife;
        for ( const entry of this.blocked ) {
            if ( entry[1] <= obsolete ) {
                this.results.delete(entry[0]);
                this.blocked.delete(entry[0]);
            }
        }
        for ( const entry of this.results ) {
            if ( entry[1].tstamp <= obsolete ) {
                this.results.delete(entry[0]);
            }
        }
        if ( this.blocked.size !== 0 || this.results.size !== 0 ) {
            this.pruneAsync();
        }
    },

    pruneAsync: function() {
        if ( this.timer !== undefined ) { return; }
        this.timer = vAPI.setTimeout(
            ( ) => {
                this.timer = undefined;
                this.prune();
            },
            this.shelfLife
        );
    },

    lookupResult: function(fctxt) {
        return this.results.get(
            fctxt.getDocHostname() + ' ' +
            fctxt.type + ' ' +
            fctxt.url
        );
    },

    lookupAllBlocked: function(hostname) {
        const result = [];
        for ( const entry of this.blocked ) {
            const pos = entry[0].indexOf(' ');
            if ( entry[0].slice(0, pos) === hostname ) {
                result[result.length] = entry[0].slice(pos + 1);
            }
        }
        return result;
    },
};

NetFilteringResultCache.factory = function() {
    const entry = netFilteringCacheJunkyard.pop();
    return entry !== undefined
        ? entry.init()
        : new NetFilteringResultCache();
};

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

// Frame stores are used solely to associate a URL with a frame id. The
// name `pageHostname` is used because of historical reasons. A more
// appropriate name is `frameHostname` -- something to do in a future
// refactoring.

// To mitigate memory churning
const frameStoreJunkyard = [];
const frameStoreJunkyardMax = 50;

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

const FrameStore = function(frameURL) {
    this.init(frameURL);
};

FrameStore.prototype = {
    init: function(frameURL) {
        const µburi = µb.URI;
        this.pageHostname = µburi.hostnameFromURI(frameURL);
        this.pageDomain =
            µburi.domainFromHostname(this.pageHostname) || this.pageHostname;
        return this;
    },

    dispose: function() {
        this.pageHostname = this.pageDomain = '';
        if ( frameStoreJunkyard.length < frameStoreJunkyardMax ) {
            frameStoreJunkyard.push(this);
        }
        return null;
    },
};

FrameStore.factory = function(frameURL) {
    const entry = frameStoreJunkyard.pop();
    if ( entry === undefined ) {
        return new FrameStore(frameURL);
    }
    return entry.init(frameURL);
};

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

// To mitigate memory churning
const pageStoreJunkyard = [];
const pageStoreJunkyardMax = 10;

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

const PageStore = function(tabId, context) {
    this.extraData = new Map();
    this.journal = [];
    this.journalTimer = null;
    this.journalLastCommitted = this.journalLastUncommitted = undefined;
    this.journalLastUncommittedURL = undefined;
    this.init(tabId, context);
};

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

PageStore.factory = function(tabId, context) {
    let entry = pageStoreJunkyard.pop();
    if ( entry === undefined ) {
        entry = new PageStore(tabId, context);
    } else {
        entry.init(tabId, context);
    }
    return entry;
};

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

// https://github.com/gorhill/uBlock/issues/3201
//   The context is used to determine whether we report behavior change to the
//   logger.

PageStore.prototype.init = function(tabId, context) {
    const tabContext = µb.tabContextManager.mustLookup(tabId);
    this.tabId = tabId;

    // If we are navigating from-to same site, remember whether large
    // media elements were temporarily allowed.
    if (
        typeof this.allowLargeMediaElementsUntil !== 'number' ||
        tabContext.rootHostname !== this.tabHostname
    ) {
        this.allowLargeMediaElementsUntil = 0;
    }

    this.tabHostname = tabContext.rootHostname;
    this.title = tabContext.rawURL;
    this.rawURL = tabContext.rawURL;
    this.hostnameToCountMap = new Map();
    this.contentLastModified = 0;
    this.frames = new Map();
    this.logData = undefined;
    this.perLoadBlockedRequestCount = 0;
    this.perLoadAllowedRequestCount = 0;
    this.hiddenElementCount = ''; // Empty string means "unknown"
    this.remoteFontCount = 0;
    this.scriptCount = 0;
    this.popupBlockedCount = 0;
    this.largeMediaCount = 0;
    this.largeMediaTimer = null;
    this.netFilteringCache = NetFilteringResultCache.factory();
    this.internalRedirectionCount = 0;
    this.extraData.clear();

    // The current filtering context is cloned because:
    // - We may be called with or without the current context having been
    //   initialized.
    // - If it has been initialized, we do not want to change the state
    //   of the current context.
    const fctxt = µb.logger.enabled
        ? µBlock.filteringContext
                .duplicate()
                .fromTabId(tabId)
                .setURL(tabContext.rawURL)
        : undefined;

    // https://github.com/uBlockOrigin/uBlock-issues/issues/314
    const masterSwitch = tabContext.getNetFilteringSwitch();

    this.noCosmeticFiltering = µb.sessionSwitches.evaluateZ(
        'no-cosmetic-filtering',
        tabContext.rootHostname
    ) === true;
    if (
        masterSwitch &&
        this.noCosmeticFiltering &&
        µb.logger.enabled &&
        context === 'tabCommitted'
    ) {
        fctxt.setRealm('cosmetic')
             .setType('dom')
             .setFilter(µb.sessionSwitches.toLogData())
             .toLogger();
    }

    // Support `generichide` filter option.
    this.noGenericCosmeticFiltering = masterSwitch !== true;
    if ( this.noGenericCosmeticFiltering !== true ) {
        this.noGenericCosmeticFiltering = this.noCosmeticFiltering;
        if ( this.noGenericCosmeticFiltering !== true ) {
            let result = µb.staticNetFilteringEngine.matchStringGenericHide(
                tabContext.normalURL
            );
            this.noGenericCosmeticFiltering = result === 2;
            if (
                result !== 0 &&
                µb.logger.enabled &&
                context === 'tabCommitted'
            ) {
                fctxt.setRealm('network')
                     .setType('generichide')
                     .setFilter(µb.staticNetFilteringEngine.toLogData())
                     .toLogger();
            }
        }
    }

    return this;
};

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

PageStore.prototype.reuse = function(context) {
    // When force refreshing a page, the page store data needs to be reset.

    // If the hostname changes, we can't merely just update the context.
    const tabContext = µb.tabContextManager.mustLookup(this.tabId);
    if ( tabContext.rootHostname !== this.tabHostname ) {
        context = '';
    }

    // If URL changes without a page reload (more and more common), then we
    // need to keep all that we collected for reuse. In particular, not
    // doing so was causing a problem in `videos.foxnews.com`: clicking a
    // video thumbnail would not work, because the frame hierarchy structure
    // was flushed from memory, while not really being flushed on the page.
    if ( context === 'tabUpdated' ) {
        // As part of https://github.com/chrisaljoudi/uBlock/issues/405
        // URL changed, force a re-evaluation of filtering switch
        this.rawURL = tabContext.rawURL;
        return this;
    }

    // A new page is completely reloaded from scratch, reset all.
    if ( this.largeMediaTimer !== null ) {
        clearTimeout(this.largeMediaTimer);
        this.largeMediaTimer = null;
    }
    this.disposeFrameStores();
    this.netFilteringCache = this.netFilteringCache.dispose();
    this.init(this.tabId, context);
    return this;
};

// https://www.youtube.com/watch?v=dltNSbOupgE

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

PageStore.prototype.dispose = function() {
    this.tabHostname = '';
    this.title = '';
    this.rawURL = '';
    this.hostnameToCountMap = null;
    this.allowLargeMediaElementsUntil = 0;
    if ( this.largeMediaTimer !== null ) {
        clearTimeout(this.largeMediaTimer);
        this.largeMediaTimer = null;
    }
    this.disposeFrameStores();
    this.netFilteringCache = this.netFilteringCache.dispose();
    if ( this.journalTimer !== null ) {
        clearTimeout(this.journalTimer);
        this.journalTimer = null;
    }
    this.journal = [];
    this.journalLastUncommittedURL = undefined;
    if ( pageStoreJunkyard.length < pageStoreJunkyardMax ) {
        pageStoreJunkyard.push(this);
    }
    return null;
};

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

PageStore.prototype.disposeFrameStores = function() {
    for ( const frameStore of this.frames.values() ) {
        frameStore.dispose();
    }
    this.frames.clear();
};

PageStore.prototype.getFrame = function(frameId) {
    return this.frames.get(frameId) || null;
};

PageStore.prototype.setFrame = function(frameId, frameURL) {
    const frameStore = this.frames.get(frameId);
    if ( frameStore !== undefined ) {
        frameStore.init(frameURL);
    } else {
        this.frames.set(frameId, FrameStore.factory(frameURL));
    }
};

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

PageStore.prototype.getNetFilteringSwitch = function() {
    return µb.tabContextManager.mustLookup(this.tabId).getNetFilteringSwitch();
};

PageStore.prototype.getSpecificCosmeticFilteringSwitch = function() {
    return this.noCosmeticFiltering !== true;
};

PageStore.prototype.getGenericCosmeticFilteringSwitch = function() {
    return this.noGenericCosmeticFiltering !== true &&
           this.noCosmeticFiltering !== true;
};

PageStore.prototype.toggleNetFilteringSwitch = function(url, scope, state) {
    µb.toggleNetFilteringSwitch(url, scope, state);
    this.netFilteringCache.empty();
};

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

PageStore.prototype.injectLargeMediaElementScriptlet = function() {
    vAPI.tabs.injectScript(this.tabId, {
        file: '/js/scriptlets/load-large-media-interactive.js',
        allFrames: true,
        runAt: 'document_idle',
    });
    µb.contextMenu.update(this.tabId);
};

PageStore.prototype.temporarilyAllowLargeMediaElements = function(state) {
    this.largeMediaCount = 0;
    µb.contextMenu.update(this.tabId);
    this.allowLargeMediaElementsUntil = state ? Date.now() + 86400000 : 0;
    µb.scriptlets.injectDeep(this.tabId, 'load-large-media-all');
};

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

// https://github.com/gorhill/uBlock/issues/2053
//   There is no way around using journaling to ensure we deal properly with
//   potentially out of order navigation events vs. network request events.

PageStore.prototype.journalAddRequest = function(hostname, result) {
    if ( hostname === '' ) { return; }
    this.journal.push(
        hostname,
        result === 1 ? 0x00000001 : 0x00010000
    );
    if ( this.journalTimer === null ) {
        this.journalTimer = vAPI.setTimeout(
            ( ) => { this.journalProcess(true); },
            µb.hiddenSettings.requestJournalProcessPeriod
        );
    }
};

PageStore.prototype.journalAddRootFrame = function(type, url) {
    if ( type === 'committed' ) {
        this.journalLastCommitted = this.journal.length;
        if (
            this.journalLastUncommitted !== undefined &&
            this.journalLastUncommitted < this.journalLastCommitted &&
            this.journalLastUncommittedURL === url
        ) {
            this.journalLastCommitted = this.journalLastUncommitted;
            this.journalLastUncommitted = undefined;
        }
    } else if ( type === 'uncommitted' ) {
        this.journalLastUncommitted = this.journal.length;
        this.journalLastUncommittedURL = url;
    }
    if ( this.journalTimer !== null ) {
        clearTimeout(this.journalTimer);
    }
    this.journalTimer = vAPI.setTimeout(
        ( ) => { this.journalProcess(true); },
        µb.hiddenSettings.requestJournalProcessPeriod
    );
};

PageStore.prototype.journalProcess = function(fromTimer) {
    if ( !fromTimer ) {
        clearTimeout(this.journalTimer);
    }
    this.journalTimer = null;

    const journal = this.journal;
    const now = Date.now();
    let aggregateCounts = 0;
    let pivot = this.journalLastCommitted || 0;

    // Everything after pivot originates from current page.
    for ( let i = pivot; i < journal.length; i += 2 ) {
        const hostname = journal[i];
        let hostnameCounts = this.hostnameToCountMap.get(hostname);
        if ( hostnameCounts === undefined ) {
            hostnameCounts = 0;
            this.contentLastModified = now;
        }
        let count = journal[i+1];
        this.hostnameToCountMap.set(hostname, hostnameCounts + count);
        aggregateCounts += count;
    }
    this.perLoadBlockedRequestCount += aggregateCounts & 0xFFFF;
    this.perLoadAllowedRequestCount += aggregateCounts >>> 16 & 0xFFFF;
    this.journalLastCommitted = undefined;

    // https://github.com/chrisaljoudi/uBlock/issues/905#issuecomment-76543649
    //   No point updating the badge if it's not being displayed.
    if ( (aggregateCounts & 0xFFFF) && µb.userSettings.showIconBadge ) {
        µb.updateToolbarIcon(this.tabId, 0x02);
    }

    // Everything before pivot does not originate from current page -- we still
    // need to bump global blocked/allowed counts.
    for ( let i = 0; i < pivot; i += 2 ) {
        aggregateCounts += journal[i+1];
    }
    if ( aggregateCounts !== 0 ) {
        µb.localSettings.blockedRequestCount += aggregateCounts & 0xFFFF;
        µb.localSettings.allowedRequestCount += aggregateCounts >>> 16 & 0xFFFF;
        µb.localSettingsLastModified = now;
    }
    journal.length = 0;
};

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

PageStore.prototype.filterRequest = function(fctxt) {
    fctxt.filter = undefined;

    if ( this.getNetFilteringSwitch() === false ) {
        return 0;
    }

    const requestType = fctxt.type;

    if ( requestType === 'csp_report' && this.filterCSPReport(fctxt) === 1 ) {
        return 1;
    }

    if ( requestType.endsWith('font') && this.filterFont(fctxt) === 1 ) {
        return 1;
    }

    if (
        requestType === 'script' &&
        this.filterScripting(fctxt, true) === 1
    ) {
        return 1;
    }

    const cacheableResult = this.cacheableResults.has(requestType);

    if ( cacheableResult ) {
        const entry = this.netFilteringCache.lookupResult(fctxt);
        if ( entry !== undefined ) {
            fctxt.filter = entry.logData;
            return entry.result;
        }
    }

    // Dynamic URL filtering.
    let result = µb.sessionURLFiltering.evaluateZ(
        fctxt.getTabHostname(),
        fctxt.url,
        requestType
    );
    if ( result !== 0 && µb.logger.enabled ) {
        fctxt.filter = µb.sessionURLFiltering.toLogData();
    }

    // Dynamic hostname/type filtering.
    if ( result === 0 && µb.userSettings.advancedUserEnabled ) {
        result = µb.sessionFirewall.evaluateCellZY(
            fctxt.getTabHostname(),
            fctxt.getHostname(),
            requestType
        );
        if ( result !== 0 && result !== 3 && µb.logger.enabled ) {
            fctxt.filter = µb.sessionFirewall.toLogData();
        }
    }

    // Static filtering has lowest precedence.
    if ( result === 0 || result === 3 ) {
        result = µb.staticNetFilteringEngine.matchString(fctxt);
        if ( result !== 0 && µb.logger.enabled ) {
            fctxt.filter = µb.staticNetFilteringEngine.toLogData();
        }
    }

    if ( cacheableResult ) {
        this.netFilteringCache.rememberResult(fctxt, result);
    } else if ( result === 1 && this.collapsibleResources.has(requestType) ) {
        this.netFilteringCache.rememberBlock(fctxt, true);
    }

    return result;
};

PageStore.prototype.cacheableResults = new Set([
    'sub_frame',
]);

PageStore.prototype.collapsibleResources = new Set([
    'image',
    'media',
    'object',
    'sub_frame',
]);

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

PageStore.prototype.filterCSPReport = function(fctxt) {
    if (
        µb.sessionSwitches.evaluateZ(
            'no-csp-reports',
            fctxt.getHostname()
        )
    ) {
        if ( µb.logger.enabled ) {
            fctxt.filter = µb.sessionSwitches.toLogData();
        }
        return 1;
    }
    return 0;
};

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

PageStore.prototype.filterFont = function(fctxt) {
    if ( fctxt.type === 'font' ) {
        this.remoteFontCount += 1;
    }
    if (
        µb.sessionSwitches.evaluateZ(
            'no-remote-fonts',
            fctxt.getTabHostname()
        ) !== false
    ) {
        if ( µb.logger.enabled ) {
            fctxt.filter = µb.sessionSwitches.toLogData();
        }
        return 1;
    }
    return 0;
};

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

PageStore.prototype.filterScripting = function(fctxt, netFiltering) {
    fctxt.filter = undefined;
    if ( netFiltering === undefined ) {
        netFiltering = this.getNetFilteringSwitch();
    }
    if (
        netFiltering === false ||
        µb.sessionSwitches.evaluateZ(
            'no-scripting',
            fctxt.getTabHostname()
        ) === false
    ) {
        return 0;
    }
    if ( µb.logger.enabled ) {
        fctxt.filter = µb.sessionSwitches.toLogData();
    }
    return 1;
};

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

// The caller is responsible to check whether filtering is enabled or not.

PageStore.prototype.filterLargeMediaElement = function(fctxt, size) {
    fctxt.filter = undefined;

    if ( Date.now() < this.allowLargeMediaElementsUntil ) {
        return 0;
    }
    if (
        µb.sessionSwitches.evaluateZ(
            'no-large-media',
            fctxt.getTabHostname()
        ) !== true
    ) {
        return 0;
    }
    if ( (size >>> 10) < µb.userSettings.largeMediaSize ) {
        return 0;
    }

    this.largeMediaCount += 1;
    if ( this.largeMediaTimer === null ) {
        this.largeMediaTimer = vAPI.setTimeout(( ) => {
            this.largeMediaTimer = null;
            this.injectLargeMediaElementScriptlet();
        }, 500);
    }

    if ( µb.logger.enabled ) {
        fctxt.filter = µb.sessionSwitches.toLogData();
    }

    return 1;
};

// https://www.youtube.com/watch?v=drW8p_dTLD4

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

PageStore.prototype.getBlockedResources = function(request, response) {
    const normalURL = µb.normalizePageURL(this.tabId, request.frameURL);
    const resources = request.resources;
    const fctxt = µBlock.filteringContext;
    fctxt.fromTabId(this.tabId)
         .setDocOriginFromURL(normalURL);
    // Force some resources to go through the filtering engine in order to
    // populate the blocked-resources cache. This is required because for
    // some resources it's not possible to detect whether they were blocked
    // content script-side (i.e. `iframes` -- unlike `img`).
    if ( Array.isArray(resources) && resources.length !== 0 ) {
        for ( const resource of resources ) {
            this.filterRequest(
                fctxt.setType(resource.type)
                     .setURL(resource.url)
            );
        }
    }
    if ( this.netFilteringCache.hash === response.hash ) { return; }
    response.hash = this.netFilteringCache.hash;
    response.blockedResources =
        this.netFilteringCache.lookupAllBlocked(fctxt.getDocHostname());
};

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

return {
    factory: PageStore.factory
};

})();

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