// ==UserScript==
// @name CatChan
// @version 2023.01.22.1
// @description Cross domain catalog for imageboards
// @include http*://*krautchan.net/*
// @include http*://boards.4chan.org/*
// @include http*://boards.4channel.org/*
// @include http*://i.4cdn.org/*
// @include http*://8kun.top/*
// @include http*://lainchan.org/*
// @include http*://lainchan.jp/*
// @include http*://*meguca.org/*
// @include http*://rssnews.sakura.tv/*
// @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.1-beta.4/Chart.min.js
// @updateURL https://raw.github.com/Dogman8/CatChan/master/CatChan.meta.js
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// ==/UserScript==
//
//    Copyright 2014 DogMan8
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License as published by
//    the Free Software Foundation, version 3 of the License.
//
//    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 Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//


(function (){
//  if (window.top != window.self) return; //don't run on frames or iframes
//if (window.top != window.self && window.name!='KC' && window.name!='4chan' && window.name!='8chan') return; //don't run on frames or iframes
if (window.top != window.self && window.name==='') return; //don't run on frames or iframes
if (window.name==='post_tgt' && window.location.href.indexOf('localhost')!=-1) return;



  
//  http://stackoverflow.com/questions/9791489/greasemonkey-require-does-not-work-in-chrome
//  http://stackoverflow.com/questions/2246901/how-can-i-use-jquery-in-greasemonkey-scripts-in-google-chrome
//  http://stackoverflow.com/questions/17341122/link-and-execute-external-javascript-file-hosted-on-github

  var brwsr = {
    ff: (navigator.userAgent.indexOf("Firefox") != -1),
    sw_cache: true,
    JSON_parse: function(val){ // patch for Tampermonkey.
                       var retval = JSON.parse(val);
//                       if (GM_setValue)
//                       if (typeof(retval)==='object')
//                       for (var i in retval) if (typeof(retval[i])==='string' && retval[i].startsWith('[') && retval[i].endsWith(']')) retval[i] = brwsr.JSON_parse(retval[i]);}
                       for (var i in retval) if (typeof(retval[i])==='string' && retval[i].search(/^\[.*\]$/)!=-1) retval[i] = brwsr.JSON_parse(retval[i]);
                       return retval;
                     },
  };
  brwsr.innerText  = (!brwsr.ff)? 'innerText' : 'textContent';
  brwsr.Date_parse = (!brwsr.ff)? Date.parse : function(str){
    var time = Date.parse(str);
    if (isNaN(time)) {
      var idx = str.lastIndexOf('.');
      time = Date.parse(str.substr(0,idx),10) + parseInt(str.substr(idx+1,3),10);
    }
    return time;
  };
//  brwsr.Date_parse = (!brwsr.ff)? Date.parse : function(str){return Date.parse(str.replace(/ /,'T'));};
  brwsr.document_body = (!brwsr.ff)? document.body : document.documentElement;
  brwsr.mousewheel = (!brwsr.ff)? 'mousewheel' : 'DOMMouseScroll';
  brwsr.hasMessageChannel = MessageChannel;

  var DelayBuffer = function(tgt, delay){
    this.tgt = tgt;
    this.delay = delay;
    this.id = null;
    this.do_tgt = this.do_tgt.bind(this);
  };
  DelayBuffer.prototype = {
//    constructor: DelayBuffer,
    do_tgt: function(){this.id = null; this.tgt();}, 
    delayed_do: function(delay){
      if (this.id===null) {
        if (delay===undefined) delay = this.delay;
        this.id = setTimeout(this.do_tgt,
                             (typeof(delay)==='number')? delay : (this.hasFocus)? delay.fg : delay.bg);
      }
    },
    cancel: function(){if (this.id!==null) {clearTimeout(this.id);this.id=null;}},
    get_bound_func: function(){return this.delayed_do.bind(this);},
    hasFocus: true
  };
 
  var Watchdog = function(tgt, delay){
    DelayBuffer.call(this, tgt, delay);
  };
  Watchdog.prototype = {
//    constructor: Watchdog,
    start: function(delay){this.delayed_do(delay);},
    stop: function(){this.cancel();},
    restart: function(delay){this.cancel();this.delayed_do(delay);},
    get_bound_func: function(){return this.restart.bind(this);},
    __proto__: DelayBuffer.prototype
  };

////  var MutexWatchdog = function(name){ // watchdog for 8chan's unstability. // working code, @safe1069
////    this.mutex = true;
////    this.req = false;
////    this.name = name;
////    this.abort_req = false;
////    this.wdg = new Watchdog(this.fire.bind(this), 30000);
//////    this.wdg = new DelayBuffer(function(){ // working code.
//////      this.mutex = true;
//////      if (pref.debug_mode['7']) console.log('watchdog: '+this.name);}.bind(this), 30000);
////  }
////  MutexWatchdog.prototype = {
////    get: function(){
////      if (this.mutex && !this.abort_req) {
////        this.mutex = false;
////        this.wdg.start();
////        if (pref.debug_mode['5']) console.log('mutex: get: '+this.name);
////        this.req = false;
////        return true;
////      } else {
////        if (!this.abort_req) this.req = true;
////        if (pref.debug_mode['5']) console.log('mutex: fail: '+this.name);
////        this.abort_req = false;
////        return false;
////      }
////    },
////    restart: function(delay){
////      this.wdg.restart(delay);
////    },
////    stop: function(){
////      this.wdg.stop();
////      this.mutex = true;
////      if (pref.debug_mode['5']) console.log('mutex: release: '+this.name);
////    },
////    fire: function(){
////      this.mutex = true;
////      cataLog.scan_boards.scan_abort(this.name);
////      if (pref.debug_mode['7']) console.log('watchdog: '+this.name);
////    },
////    abort: function(){
////      this.abort_req = true;
////      this.stop();
////    },
////    query_req: function(){
////      return this.req;
////    }
////  }

  var comf = { // common_func
    Object_modifyDescriptor: function(obj,prop,desc){
      desc.__proto__ = Object.getOwnPropertyDescriptor(obj,prop);
      Object.defineProperty(obj,prop,desc);
    },
    Object_defProps: function(dst,src){
      for (var i in src) dst[i] = src[i];
      return src;
    },
    Array_toObj: function(src, val){
      var obj = {};
      for (var i=0;i<src.length;i++) obj[src[i]] = val;
      return obj;
    },
    rexps_remake: function(exact){ // for pref_default // test
      this.rexps = comf.kwd_prep_regexp(this, exact);
    },
    overwrite_prop: function overwrite_prop(dst,src){
      for (var i in src) {
        if (src[i]===undefined && dst[i]) delete dst[i];
        else if (typeof(dst[i])==='object' && dst[i]!==null && typeof(src[i])==='object' && src[i]!==null) overwrite_prop(dst[i],src[i]);
        else dst[i] = src[i];
      }
    },
  };

  var site0 = (function(){ // for less memory consumption and faster execution if background
    var href = window.location.href;
    var domain = null;
    var pref_default = null;
    var pref_default2 = null;
    var pref_default3 = null;
    var href = window.location.href;
    if (href.indexOf('/meguca.org/')!=-1) {
      domain = 'meguca';
      pref_default = {
        pref2:{ meguca:{utilize_boards_json:false, utilize_boards_json_domain:false}},
      // overwritten by easy2 default.
        thread:{ env:{ disp_offset:3}},
        scan:{max:100},
        proto:{env:{event_dynamic:true,}},
        style:{zIndex:300},
        tooltips:{zIndex:302},
      };
      pref_default2 = true;
    } else if (href.search(/(4chan(nel)*|4cdn).org/)!=-1) {
      domain = '4chan';
      pref_default = {
        catalog_expand_with_hr: true,
        page:{env:{disp_filler:'<hr>'}},
        patch: {delayed_invoke: {use: brwsr.ff}},
        catalog: {t2h_sel:'N', thumbnail:{hover:{popup_zIndex:100001}}, env:{popup_native:false,refresh_initial:false}},
        proto:{
          footer:{rOP:false, iOP:false},
          env:{event_dynamic: true}
        },
        easy2:{limits:2},
        style:{addSS:{str:'//s.4cdn.org/css/flags.689.css'}},
      };
    } else if (href.indexOf('lainchan.org')!==-1) {
      domain = 'lain';
      pref_default = {
        page:{ env:{ disp_offset:1}},
        catalog:{image_hover:true, board:{all_boards:true}, env:{disp_offset:1, disp_filler:'                                        '}},
        proto:{ env:{ localtime_native:false,
                      colorID_native: false}},
        stats:{ time_unit:3, patch_tm:true},
        chart: {inst:{show: {p:true}, time_sel:3}},
        easy2:{limits:1},
        thread_reader:{own_posts_tracker:true},
      };
    } else if (href.indexOf('lainchan.jp')!=-1) {
      domain = 'lainjp';
      pref_default = {
        page:{ env:{ disp_offset:1}},
        proto:{ env:{ localtime_native:false,
                      colorID_native: false}},
        stats:{ time_unit:3, patch_tm:true},
        chart: {inst:{show: {p:true}, time_sel:3}},
      // overwritten by easy2 default.
        catalog:{image_hover:true, board:{all_boards:true}, env:{disp_offset:1, disp_filler:' '},},
        thread: {auto_update:true},
        thread_reader:{own_posts_tracker:true},
        scan:{max:100},
        liveTag:{style:{urtm:'color:lime !important;font-weight:bold !important',
                        ur:'color:limegreen !important;font-weight:bold !important',
                        in:'color:red !important'}},
        virtualBoard:{scanDelay:1},
        easy2:{limits:1},
        style:{design:{post_new:{base:'border:2px solid red !important'}}},
      };
      pref_default2 = true;
    } else if (href.search(/8kun.top/)!=-1) { // 8chan.co|8ch.net
      domain = '8chan';
      pref_default = {
        catalog_expand_with_hr: true,
        page:{ env:{ disp_offset:1, disp_filler:'<hr>'}}};
    } else if (href.search(/krautchan.net/)!=-1) {
      domain = 'KC';
      pref_default = {
        thread_reader:{own_posts_tracker:true, check_num_of_children: false},
        proto:{
          env:{
            popup_native:false,
            expand_thumbnail_inline_native:false,
            image_hover_native:false,
            colorID_native:false,
            backlink_native:false,
            localtime_native:false,
            auto_update_native:false,
          },
        },
        page:{use_expander_always:true},
      };
    } else if (href.indexOf('//localhost/')!==-1 || href.indexOf('//192.168.')!==-1 || href.indexOf('//rssnews.sakura.tv/')!==-1) {
      domain = 'rssn'; // 'dist';
      pref_default = {cpfx:'',catalog_open_where:'CatChan_tgt',auto_watch:{style:true},catalog:{order:{ordering:1},env:{refresh_initial:false},board:{recommendation:false}},proto:{footer:{nrtm:false,nr:false,rp:false,im:false,board:false,page:false,design:'condensed'}},headline:{footer:{ctime:true,triage:true,triage_str:'NONE,\u25ef,background:lightgray,EXPAND,\u25b6,\u25c0,SPC, ,,KILL,x,,SPC, ,,NONE, H,background:lightcyan,NONE,R,opacity:0.7;background:gray,NONE,N,,SPC, ,'},max_letters:10000,subStyle:1},scan:{crawler:1},virtualBoard:{scanDelay:1,bl:{showActives:true}},float:{refresh_src:'bg',env:{refresh_initial:false}},page:{env:{refresh_initial:false}},chart:{inst:{scale_thread:1,time_sel:2,show:{np:false}}},settings:{indexing:10},notify:{desktop:{notify:false}}}; // ,virtualBoard:{scan:false,scan_domains:{rssn:'none'}}};
      pref_default2 = true;
      pref_default3 = {virtualBoard:{scan_domains:{lainjp:undefined, KC:undefined, dist:undefined, RSS:undefined}}};
    }
    var domains = ['meguca', '4chan', 'lain', 'lainjp', '8chan', 'KC', 'dist', 'rssn','RSS'];
    if (domain) domains.unshift(domains.splice(domains.indexOf(domain),1)[0]);
    var domains_others = ['8chan_live','vichan','4chan_s','4chan_i','4chanB','4chanR','imeguca']; // , 'CatChan_tgt']; // adding 'CatChan_tgt' with opening fixed-tab is good for testing the step window.
    if (pref_default2) {
      pref_default2 = {// new default patch
        catalog:{auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true},
        liveTag: {use:false, from:'op'},
        virtualBoard:{bl:{max:100}, scan:true, scan_domains:{}}, 
        network:{fetch_actively:false},
        easy2:{presets:1,VB:{local:1},catalog:{auto_update:false},page:{auto_update:false}},
      };
      pref_default2.virtualBoard.scan_domains[domain] = 'board';
    }
    return {
      domain: domain,
      domains: domains,
      pref_default: pref_default,
      pref_default2: pref_default2,
      pref_default3: pref_default3 || {virtualBoard:{scan_domains:{RSS:undefined}}},
      isStep: window.opener && (domains.indexOf(window.name)!=-1 || domains_others.indexOf(window.name)!=-1),
      add_domain: function(domain, no_scan){
        this.domains.push(domain);
        this.debug_domains.push(domain);
        if (!no_scan) pref.virtualBoard.scan_domains[domain] = 'none';
        pref.debug_mode.domains[domain] = true;
        pref.features.domains[domain] = true;
      },
      debug_domains: ['DEFAULT','4chan','vichan','lain','lainjp','8chan','8chan_live','KC','meguca','meguca1','meguca2','rssn','RSS','rss'],
      debug_types: ['','_json','_html'], // ,'_json_template','_html_template']; // '' for common
      site2keys: [],
    };
  })();

  var pref4 = { // dynamic, but not auto sync objects.
    funcs:{
      set_options_to_selector: function(sel,options){
        sel.length = options.length;
        for (var i=0;i<options.length;i++) sel.options[i].text = options[i];
      },
    },
    archive: {
      IDB_board_sel_gfunc_pre:function(p,k,v,n,f){
        pref4.funcs.set_options_to_selector(f,pref4.archive.IDB_board_sel_options || ['Wait...']);
      },
      IDB_board_sel_options: null,
      IDB_thread_sel_gfunc_pre:function(p,k,v,n,f){
        pref4.funcs.set_options_to_selector(f,pref4.archive.IDB_thread_sel_options || ['Wait...']);
      },
      IDB_thread_sel_options: null,
    },
    refresh:{ count:0},
    search_posts_active_once: false,
    scan: {
      get loosen(){return pref4.search_posts_active_once && pref.proto.searchAc.loosen;},
      get max(){return site.whreami==='archive'? pref.scan.max_archive : this.loosen? pref.proto.searchAc.max : pref.scan.max;},
      get max_threads(){return site.whreami==='archive'? pref.scan.max_archive : this.loosen? pref.proto.searchAc.max_threads : pref.scan.max_threads;},
      max_threads_at_refresh: function(clg){return this.loosen? pref.proto.searchAc.max_threads_at_refresh : clg.pref.INST.safety.max;},
    },
    mergeHist: {max:16,  proto:[]},
  };

  var pref0 = { // static shared defaults
    triage:{
      menu_str: 'KILL,Hide permanently,\n'+
                'TIME,Hide until new replies,\n'+
                'WATCH,Watch,\n'+
                'UNWATCH,Unwatch,\n'+
                'STICKY,Sticky,\n'+
                'UNSTICKY,Unsticky,\n'+
                'UNDO,Undo,',
      postMenu_str: 'PHIDE,hide,\n'+
                    'PHIDEAFTER,hide+,\n'+
                    'PUNHIDE,unhide,\n',
    },
    footer:{
      native:    '(ar )(U: nm/nr / )(<iB>)R: (lp/)(RP)(*sp)(+dp)(</iB>)( /<iI> I: IM</iI>)( / P: pg) (ni/nf )(ct )(cT )(bt )(bT )(pt )(pT )(pr )(dn)(bd)(no) ',
      condensed: '(ar )(nm/)(nr/)(lp/)(rp)(*sp)(+dp)(/im)(/pg) (ni/nf )(ct )(cT )(bt )(bT )(pt )(pT )(pr )(dn)(bd)(no) ',
      meguca:    '(ar )(dn)(bd)(no) (nm/)(nr/)(lp/)(rp)(*sp)(+dp)(/im)(/pg) (ni/nf )(ct )(cT )(bt )(bT )(pt )(pT )(pr ) ',
      custom2org:'(ar )(nm/)(nr/)(<iB>)(lp/)(RP)(*sp)(+dp)(</iB>)(/<iI>IM</iI>)(/pg) (ni/nf) (ct )(cT )(bt )(bT )(pt )(pT )(pr )(dn)(bd)(no) ',
      custom3org:'(ar )(nm/)(nr/)(lp/)(<iB style="color:orange">RP</iB>)(<u>*sp</u>)(<span style="opacity:0.6">+dp</span>)(/<iI style="color:orange">IM</iI>)(/pg) (<u>ni/nf</u>) (ct )(cT )(bt )(bT )(pt )(pT )(pr )(dn)(bd)(no) ',
    },
  };

  function pref_default(for_save) {
    var proto = {
      image_hover:false,
      image_prefetch:false,
      click: 'none',
      click_area: 'thumbnail',
      format: { // show:  {style:  true, contents:  true, layout:  true, posts: false, fileinfo: false, images_2nd: true},
                // hover: {style:  true, contents: false, layout: false, posts:  true, fileinfo:  true, images_2nd: true},
                // search:{style: false, contents: false, layout: false, posts:  true, fileinfo:  true, images_2nd: true},
               thumb: {
                 resize: false, size:'small',
                 vsmall:{w1:80, h1:80, w2:40, h2:40, min:16, s2:true, en:true}, 
                 small:{w1:150, h1:150, w2:75, h2:75, min:32, s2:true, en:true},
                 large:{w1:250, h1:250, w2:125, h2:125, min:64, s2:true, en:true},
                 custom:{w1:350, h1:350, w2:250, h2:250, min:64, s2:true, en:true},
                 custom2:{w1:80, h1:80, w2:40, h2:40, min:16, s2:true, en:true}},
               nc: {width:380, height:0, max:false},
               ec: {width:450, height:500, max:true},
               nc2:{width:165, height:0, max:false},
               ec2:{width:240, height:350, max:true},
        op:'on', subStyle:0, subStyle2:0,
      },
      footer: {use:true, nrtm:true, nr:true, rp:true, rp0:false, im:true, domain:false, board:true, no:false, flag:false, tag:true, page:true, design:'native', rOP:true, iOP:true, pgp:true,
               custom: pref0.footer.native, custom2:pref0.footer.custom2org, custom3:pref0.footer.custom3org,
               ctime:false, btime:false, ptime:false, rctime:false, rbtime:false, rptime:false, br:true, prate:false, archived:true, nd:false, nl:false, nf:false, nid:false, ns:false,
               triage:false, menu:true, triage_str:'KILL,x,,SPC, ,', menu_str:'SPC, ,,MENU,\u25b6,', merged_tag:'any',
               nr0:false, nr1:false, nr1_style:'color:limegreen;font-weight:bold', nrtm0:false, nrtm1:false, nrtm1_style:'color:lime;font-weight:bold'},
      triage_hover:true,
//      footer_br: true,
      draw_on_demand: true,
      load_on_demand: false, load_on_demand_timeout: 10,
      t2h_sel: 'page',
      t2h_L: 100,
      t2h_M: 50,
      get t2h_N(){return this.t2h_num_of_posts},
      t2h_num_of_posts : 5,
      save_board_list_sel: false,
      board_list_sel: 0,
      mark_new_posts: true,
      auto_update : false,
      auto_update_period : 10,
      auto_update_countdown: true,
      auto_update_shrink: 1,
      storePosts:'auto', sourceOfSP:'auto', searchAs:false, searchAsA:false, searchAc:{loosen:true, max:1000, max_threads:10000, max_threads_at_refresh:10000},

      popup:true, colorID:true, backlink:true, popup_truncated:true, popup_zIndex:100, backlink_all:false, backlink_all_cross:false,
      popup_hlt:true, bl_ec:false, bl_rm:true,  // popup_highlight, backlink_explicit_cross, baclink_remove_if_OP
      popup_delay:50, popdown_delay:500, popdown:'delay', popup_mMove:true,
      inline_post:'inline',
      link_show_op:true, link_show_cross:true,
      expand_thumbnail_inline:true, localtime:true,
      expand_thumbnail_inline_all_after:false,
//      expand_thumbnail_initial:false,
      hide_posts_without_images:false,
      thumbnail:{
        inline:{ondemandStop:true, stopHover:true, ref_height:150, // ondemand:true,
                limit_width: true, limit_height:false, margin_width: 40, margin_height: 0, webm: true, webm_mute:false, webm_loop:false},
        hover: {limit_width: true, limit_height: true, margin_width: 40, margin_height: 0, webm:false, webm_mute: true, webm_loop:false, webm_ctrl:true,
                popup_delay:50, popdown_delay:500, popdown:'delay', popup_mMove:true, popup_zIndex:101,
                dragfloat:true, df_dblC:true, df_mW:true, zoom_click:false, zoom_over:false, zoom_dblC:true, zoom_mW:true},
      },
      infoPv: {use:false, popup_delay:200, popdown_delay:0, popdown:'delay', popup_mMove:false, popup_zIndex:101},
      popupX: {popup_delay:500, popdown_delay:500, popdown:'delay', popup_mMove:true, popup_zIndex:101, use:false, ID:true, flag:true, name:false, anon:false, trip:false, ID2:false},
//      popupID: {popup_delay:500, popdown_delay:500, popdown:'delay', popup_mMove:true, popup_zIndex:101},
//      popupFlag: {popup_delay:500, popdown_delay:500, popdown:'delay', popup_mMove:true, popup_zIndex:101},
//      popupName: {popup_delay:500, popdown_delay:500, popdown:'delay', popup_mMove:true, popup_zIndex:101, anon:false},
//      popupTrip: {popup_delay:500, popdown_delay:500, popdown:'delay', popup_mMove:true, popup_zIndex:101},
      open_spoiler_text: false,
      open_spoiler_image: false,
      merge: false, merge_list: false, merge_list_str: '', merge_list_obj6: null, merge_truncated: false,
      merge_lv:true, merge_lv_str:'', merge_lv_obj6:null,
      merge_auto:false, // merge_auto_lv:2, merge_auto_lv_add:true,
      merge_op:{auto:false, fetch:false, hop:1, lv_add:true, lv_def:2, lv_inc:true, attr:true,  list:true},
      merge_mode:'list',   merge_btn:{add:true, lv_add:true, lv_def:1, lv_inc:true, attr:false, list:false},
      lazyDraw: {merge: true, merge_step: 16, step: 16},
      scroll_lock: false,
      use_expander_always: false,
//      popup2:'sr',
      popup2_sel:'auto', popup2_sel_tolerance:10, popup2_resize:false, popup2_resize_bw:8, popup2_resize_cw:20,
      popup3: {ww:'no', wn:'no', nw:'no', nn:'no', popup_delay:300, popdown_delay:500, popdown:'delay', popup_mMove:true, popup_zIndex:100, oX:10, oY:10}, // oX,oY is short for offsetX,Y
      posts_search_op: 'opaque',
      posts_search_op_opacity: 40,
      deleted_posts: {detect:'acc', store:'LS', merge:true, auto_clean:true},
      detect_sage: false, page_his: false,
//      auto_config_posts_search: true,
      promiscuous: false, accept_others_refresh:false,
      hide_unwatched: false, refresh_src:'bg', refresh_src_th:3, 
      postMenu:true,
      appearance: {
        titleBar: {filter: true, settings: true, refresh: true, boards_selector: true},
        initial: {state: 'float', width: 400, height: 400, left:0, top:0},
        orderOnTB: false,
        expand: {kwd:true, time:true, tag:false, list:false, ctrl:false},
      },
      imagesearch: {
        use: true,
        google: true,
        iqdb: true,
        saucenao: true,
        whatAnime: true,
        desustorage: true,
        exhentai: true,
      },
      safety: {hide:false, remove:false, max:10000}, // at refresh
      triage_place: 'topLeft',

      env: {
        disp_offset:0,
        disp_filler:'',
        popup_native:true,
        expand_thumbnail_inline_native:true,
        image_hover_native:false,
        colorID_native:true,
        backlink_native:true,
        localtime_native:true,
        auto_update_native:false,
        event_dynamic:false,
        popup_native_kill:true,
        refresh_initial:true,
//        thread_pos_static:true,
        kwd_filter_delay: 0, kwd_filter_delay_len: 5,
      },
    };
    var proto_search = Object.create(Object.prototype, {ci:{get:()=>pref.liveTag.ci}, rexps_remake:{value:comf.rexps_remake}}); // non-enumerables // needs prototype to save, uses hasOwnProperty in obj_delim_the_same
    var pref_new = {
      script_prefix: 'CatChan',
      cpfx: 'CatChan_', // class prefix
      tabID: 0, // prevent from opening new thread in the same tab between multiple CatChan instances, see open_new_thread(), saved by beforeunload in set_event_target // https://stackoverflow.com/questions/11896160/any-way-to-identify-browser-tab-in-javascript
      features: {
        page: true, graph: true, setting: true, setting2: true, postform: true, catalog: true, listener : true, uip_tracker: true, thread_reader: true, debug: false,
        notify:{desktop:true, sound:true, favicon:true},
        recovery: true, IDB:true,
        domains: comf.Array_toObj(site0.domains, true),
      },
      max_capture: 576,
      interval_found: 10,
      write_to_ls: true,
      aggregator: 'true', // radiobutton
      server: true,
      load_pref: true,
      load_data: true,
      check_page: false,
      check_post: false,
      check_thread: false,
      import_format: 'obj', // radiobutton
      max_graph: 576, // radiobutton
      scale_thread: 10,
      auto_start: true,
//      workaround_for_dollchan: false,
      prevent_redirection: false,
      graph_animation: false,
      autoconf: 'auto',
      info_server: false,
      info_client: false,
      wafd_tb: 'tb',
      wafd_open_spoiler: false,
      show_page_fraction : true,
      catalog_max_page: 5,
      catalog_max_page_auto: true,
      catalog_max_page_select: 'auto',
      catalog_snoop_refresh: true,
      catalog_auto_rollup_when_moving: true,
//      catalog_size_width: 240,
//      catalog_size_height: 350,
//      catalog_size_text_width: 400,
//      catalog_size_text_height: 16,
//      catalog_size_tn1_width: 240,
//      catalog_size_tn1_height: 240,
//      catalog_size_tn2_width: 80,
//      catalog_size_tn2_height: 80,
      catalog_size_frame0_width: 30,
      catalog_size_frame1_width: 69,
//      catalog_enable_cross_board: true,
//      catalog_enable_cross_domain: true,
      catalog_2nd_images_show: false,
      catalog_2nd_images_hover: true,
      catalog_2nd_images_search: true,
      catalog_posts_on_demand: true,
//      catalog_checkbox_deletion_show: false,
//      catalog_checkbox_deletion_hover: true,
//      catalog_checkbox_deletion_search: true,
//      catalog_popup: true,
//      catalog_popdown: 'delay',
//      catalog_popup_delay: 300,
//      catalog_popdown_delay: 500,
      catalog_popup_size_fix: true,
      catalog_open_last50: 'exist_watch',
      catalog_board_list_str: '',
//        '//sample of board group\n' +
////        '//board_name[,nickname+board_name[+thread No.] | \'*\'+up to X page | \'^\'+style]...\n'+
//        '//board_name[,nickname+board_name[+thread No.][\'*\'+up to X page]]..., [#Tag|##Tag]..., [!stats]\n'+
//        'Global/int/,8chan/int/,KC/int/,4chan/int/\n'+
//        'Global/b/,8chan/b/,KC/b/,4chan/b/\n'+
//        'v+gg,8chan/v/,8chan/gamergatehq/,4chan/v/\n'+
//        'Inter/pol/,8chan/pol/,4chan/pol/\n'+
//        'JapanShoppingMall,8chan/jpck/,8chan/japan2/\n'+
//        'tech,meguca/g/,lain/tech/,8chan/tech/,4chan/g/,KC/t/\n',
////        'ScriptHome,8chan/scriptcdc/,KC/jp/35003\n',
////        'script_home,8chan/scriptcdc/,KC/jp/35003,KC/kc/41434\n',
      catalog_board_list_obj: null,
      catalog_promiscuous: false,
//      catalog_board_list_sel: 0,
      get catalog_board_list_sel() {return (cataLog && cataLog.embed_mode && this[cataLog.embed_mode])? this[cataLog.embed_mode].board_list_sel : 0;},
      set catalog_board_list_sel(val) {if (cataLog && cataLog.embed_mode && this[cataLog.embed_mode]) this[cataLog.embed_mode].board_list_sel = val;},
//      catalog_sw_domain: 'https://8chan.co',
      localtime_offset : -(new Date().getTimezoneOffset()/60),
//      catalog_localtime : true,
//      catalog_border_show : false,
//      catalog_border : '1px solid',
//      catalog_enable_background : false,
      catalog_footer_ignore_my_own_posts : true,
      catalog_footer_tag_letters : 3,
      catalog_no_popup_at_expanded : true,
      catalog_open_in_new_tab : true,
      catalog_open_where : 'named',
      catalog_open_where_click : true,
//      catalog_use_named_window : true,
//      catalog_triage : true,
//      catalog_triage_place : 'topLeft',
      catalog_triage_hist : 16,
      triage:{
        popdown:true, popdown_delay:2000, hide_toggle:true, sync_ex:'bg', sync_attr:'bg', sync_watch:'bg', // zIndex:110,
        menu_str: pref0.triage.menu_str,
        postMenu_str: pref0.triage.postMenu_str,
      },
      catalog_triage_str: 'KILL,X,,TIME,v,,WATCH,W,,UNWATCH,UW,,UNDO,U,',
//      catalog_auto_update_countdown : true,
//      catalog_show_setting : false,
//      catalog_expand_at_initial : false,
//      catalog_expand_at_initial_embed : true,
      catalog_expand_with_hr : false,
      cli: {auto:false,
            json_str:'',
//            json_str:'//{"site2":{"KC":{"time_offset":2}}} // summer time for KC.\n'+
//                     '//{"site2":{"4chan":{"protocol":"https:"}}} // use https to 4chan.\n'+
//                     '//{"pref":{"patch":{"rm_404_blacklist":["8chan"]}}} // 8chan sends corrupted data.\n',
            eval_str: '', json_out_str:'', json_query_str:''
           },
//      show_tooltip : true,
      tooltips: {help: {show: true, popup_delay:2000, popdown_delay:1000},
                 info: {show: true, popup_delay:300,  popdown_delay:500},
                 zIndex:100},
      filter: {
//          show : false,
        kwd: {use:false, str:'', re:false, ci:true, match:0, sentence:false, ew:false, op:true, post:false, inPost:false,
              sub:true, name:true, trip:false, com:true, file:false, meta:false, flag:false, id:false, id2:false,   rexps:null},
        tag: false, tagBTgts:0, tagBFunc:0, tag_ci:false,
        time: false, time_creation: false, time_watch: false, time_watch_creation: false, time_str: '', time_obj6: null, // for legacy, this line will be removed.
        time: {use:false, func:'pn', time_val:Date.now()-86400000,    h:0, w:0}, // time_str is getter/setter // func[0]:hide, func[1]:watch, 'p':posted, 'c':created, 'n':none, h for hide, w for watch, see onchange_funcs['filter.time.*W/']
          list_time_scroll: true,
          list : true,
//          list_mark_time : true,
          list_str : '',
//          list_obj : [],
          list_obj2 : {},
          attr_list : true,
          attr_list_str : '',
          attr_list_obj2 : {},
          bookmark_list : true,
          bookmark_list_str : '',
          bookmark_list_obj2 : {},
          watch_list_str : '',
          watch_list_obj2 : {},
//          watch_list_mark_time : true,
//          tag_scansite : true,
          tag_search: {str:'', re:false, show_nof_boards:false, showActives:false,  get ci(){return pref.liveTag.ci;}, sentence:false, rexps:null},
        tag2bList: {label:'', by:'tag', rm_nvb:true},
          auto_list: {use:false, str:'', obj7:null, kill:false, watch:true, show:false, sticky:false, style:false, style_str:'', dtrm_str:''},
          disLWKA:true, disTWKA:false, // disable list/tag filter when keyword filter is active
          postFilter: {use:false, hide:false, boards:false, boards_str:'', style:true, style_str:'', str:'', obj7:null, obj7_old:null},
      },
      postFilter: {use:false, str:'', obj7:null, obj7_old:null},
      auto_watch: {watch:true, triage:false, triage_str:'', hide:false, hide_com:'TIME', style:false, style_str:'background:silver'},
      catalog: {
//        get filter(){console.log('WARNING: catalog.filter is accessed'); return pref.filter;}, // for safe
//        max_threads : 512,
        max_threads_at_refresh : 500,
        bookmark_list_rm404 : true,
        auto_load_filter : false,
        auto_save_filter : false,
        auto_save_filter_at_refresh : false,
        tag : {ignore:12, max:12},
        board: {recommendation: true, all_boards:false, board_tags: false, ex_list: false, ex_list_str: '', ex_list_obj2: null, board_tags_same: false},
        style_general_list : true,
        style_general_list_str: '',
        style_general_list_obj2: null,
        refresh : {except_bt : true, at_switch: true},
        design : 'auto',
        catalog_json : true,
        embed : true,
        embed_page: true,
        embed_frame : true,
        embed_archive: false,
//        order : {reply_to_me: true, reply: true, watch: 'dont_care', sticky:'dont_care', find_sage_in_8chan: false},
        order: {reply_to_me:true, reply:true, watch:'dont_care', sticky:'first', ordering:0},
//        health_indicator: {on: true, max:10},
        mimic_base_site: true,
//        text_mode: {mode:'graphic', sub:true, name:false, com:true},
        appearance: {titleBar:{__proto__:proto.appearance.titleBar}, expand:{__proto__:proto.appearance.expand}, __proto__:proto.appearance},
        t2h_sel: 'no',
        click: 'open',
        popupX: {__proto__:proto.popupX},
//        env:{thread_pos_static: false, __proto__:proto.env},
        footer: {__proto__:proto.footer}, // this is required for save, or refers proto directly and deletes them all.
//        view: 'catalog',
        env:Object.create(proto.env),
        format: {thumb: {__proto__:proto.format.thumb}, __proto__:proto.format},
        merge_lv:false,
        infoPv: {use: false, __proto__:proto.infoPv},
        safety: {__proto__:proto.safety},
        searchAs:true, accept_others_refresh:true,
        merge_op:{__proto__:proto.merge_op},
        merge_btn:{__proto__:proto.merge_btn},
        __proto__: proto},
      page: {
        get embed(){return pref.catalog.embed_page;},
        infinite: false, 
//        scan_tag:true,
        format: { // show:{style:false, contents:false, layout:false, posts:true, fileinfo:true, images_2nd:true},
          thumb: {size:'large', __proto__:proto.format.thumb}, __proto__:proto.format},
        image_hover:true,
//        get footer(){return pref.catalog.footer;},
//        footer_br: false,
        load_on_demand: true,
        popupX: {__proto__:proto.popupX},
        env: {kwd_filter_delay: 500, __proto__:proto.env},
//        view: 'page',
        appearance: {titleBar:{__proto__:proto.appearance.titleBar}, expand:{__proto__:proto.appearance.expand}, __proto__:proto.appearance},
        safety: {__proto__:proto.safety},
        footer: {__proto__:proto.footer},
        merge_op:{__proto__:proto.merge_op},
        merge_btn:{__proto__:proto.merge_btn},
        __proto__: proto},
      thread: {
        embed: false,
        format: { // show:{style:false, contents:false, layout:false, posts:true, fileinfo:true, images_2nd:true},
                 thumb: {size:'large', __proto__:proto.format.thumb}, __proto__:proto.format},
        image_hover:true,
//        get footer(){return pref.catalog.footer;},
//        footer_br: false,
        popupX: {__proto__:proto.popupX},
        env:{auto_update_native:true, refresh_initial:false, kwd_filter_delay:500, __proto__:proto.env},
//        t2h_num_of_posts : -1,
        auto_update: false,
//        use_expander_always: true, // for merge
//        view: 'thread',
        merge_mode:'all',
        triage_hover:false,
        appearance: {titleBar:{__proto__:proto.appearance.titleBar}, expand:{time:false, __proto__:proto.appearance.expand}, __proto__:proto.appearance},
        safety: {__proto__:proto.safety},
        footer: {__proto__:proto.footer}, // for safe
        merge_op:{__proto__:proto.merge_op},
        merge_btn:{__proto__:proto.merge_btn},
        __proto__: proto},
      float: {
        click: 'open',
        footer: {triage:true, __proto__:proto.footer},
//        get footer(){return pref.catalog.footer;},
        format: {thumb: {resize: true, __proto__:proto.format.thumb},
                 fc: {resize: false, width:240, height:350, max:true},
                 __proto__:proto.format},
        save_board_list_sel: true,
        popup3: {__proto__:proto.popup3},
        popupX: {__proto__:proto.popupX},
        env:Object.create(proto.env),
        order: {reply_to_me:true, reply:true, watch:'dont_care', sticky:'first', ordering:0},
        view: 'headline', hide_unwatched:false, refresh_src:'wl', catalog_size: 0, catalog_teaser: 0,
        catalog:{thumb:{__proto__:proto.format.thumb}, __proto__:proto.format},
        page:{thumb:{size:'large', __proto__:proto.format.thumb}, __proto__:proto.format},
        headline:{thumb:{__proto__:proto.format.thumb}, __proto__:proto.format},
        disable_kwd_filter_at_initial: true, 
        auto_launch: false, clone:true,
        appearance: {titleBar:{__proto__:proto.appearance.titleBar}, expand:{__proto__:proto.appearance.expand}, __proto__:proto.appearance},
        safety: {__proto__:proto.safety},
        merge_op:{__proto__:proto.merge_op},
        merge_btn:{__proto__:proto.merge_btn},
        __proto__: proto},
      headline:{
        triage_hover:false,
        triage_place:'topLeft',
        max_letters: 150,
        merge_truncated: true,
        merge_lv: false,
        subStyle: 0,
        format: {thumb: {__proto__:proto.format.thumb}, __proto__:proto.format},
        footer: {__proto__:proto.footer},
        __proto__: proto},
      common: {
        clear_at_manual_scan : false,
        consolidated_filter: false, // true,
        consolidated_watch_list: true,
        sync_watch_list: true,
        sync_watch_list_delay: 100,
        watch_list_rm404: 'no',
        merge_list_rm404: 'dead',
        blur_404: true,
//        watch_list_fromAutoKwd: true,
//        watch_list_fromTimeFilter: false,
      },

//      graph : {key: null, pipe: null},
      uip_tracker: {on : false, posts: true, deletion: true, interval: 10, adaptive: true, highlight_str:'color:red', annotate:true,
                    auto_open:false, auto_open_th:300, auto_open_kwd:'',
                    deletion: {show:true, link:true, name:true, name_str:'color:red', addName:true, addName_str:'DELETED_', post:false, post_str:'opacity:0.4'},
                    sage: {detect:true, name:true, name_str:'color:blue', addName:false, addName_str:'SAGE_', post:false, post_str:'opacity:0.4', tolerance:1,
                           patch_bug:false, patch_bug2:false, patch_bug2nth:8, patch_bug3:true, annotate:true, page:false, patch_bug4:true, patch_bug5:false, annotate_page:false}},
      thread_reader: {use: true, sync: true, triage: true, triage_close: true, check_num_of_children: true,
                      own_posts_tracker: false, show_own_post_by: 'anchor', show_reply_to_me_by: 'anchor', clean_up_own_posts: true, unmark_on_hover: true},
      IDinfo: {use:true, auto:true,
               ID:  {use:true,  nof:true, op:true, h:'', t:''},
               ID2: {use:false, nof:true, op:true, h:'', t:''},
               flag:{use:true,  nof:true, ID:true, h:'(', t:')'},
               trip:{use:false, nof:true, name:false, h:'(', t:')'},
               name:{use:false, nof:true, trip:false, h:'(', t:')'}},
//      IDinfo: {use:true, ID:true, flag:true, flagID:true, IDop:true, nofIDs:true, nofFlags:true, auto:true, name:false, trip:false, nameTrip:false, nofNames:true, nofTrips:true,
//               ID2:true, ID2op:true, nofID2s:true, tripName:false},
      settings: {indexing: 0, footer_sel:1},
      tag : {gen: false, gen_str:''}, // dummy for checkbox and textarea.
      cloudflare: {auto_reload: true, auto_reload_time: 5},
      scan: {max:10000, lifetime:20, crawler:50, max_threads:1000, crawler_adaptive:true, crawler_idle_time_to_spawn:100, max_archive:150},
      notify : {sound: {notify: false, src:'beep', beep_freq:1000, beep_length_f:0.2, beep_volume_f:1, reply_to_me: true, reply: true, new_thread: true, appear: true, supp_init:true,
                        file:null},
                desktop: {notify: true, reply_to_me: true, reply: true, new_thread:true, appear:true, lifetime:30, show_last:false, supp_init:true,
                          limit:(brwsr.ff)? 10 : 30, delay:(brwsr.ff)? 500 : 0},
                favicon: true,
                title: {notify:true, hide_zero: true},
               },
      liveTag: {
        use: true, max: 12, maxstr: 25, from:'post', lock_tags_in_op: true, ci: true,
        inherit_board_name: true, lock_board_name: true, inherit_board_tags: true, lock_board_tags: true,
        info: true, NC: true, // NotConsolidated
        style: {ex:'color:blue', exurtm:'color:cyan;font-weight:bold', exur:'color:deepskyblue;font-weight:bold',
                in:'color:red', inurtm:'color:yellow;font-weight:bold', inur:'color:orange;font-weight:bold',
                pk:'color:brown', pkurtm:'color:tan;font-weight:bold', pkur:'color:darkgoldenrod;font-weight:bold',
                use:true, urtm:'color:lime;font-weight:bold', ur:'color:limegreen;font-weight:bold'},
//        style_urtm_obj4:{},                           style_ur_obj4:{},                                style_in_obj4:{},
        pickup_interval: 10, rm_404:'imm', disp_delay:{fg:500, bg:5000}, click_func: 'in', click_func_bl: 'pkin', click_func_ctrl: 'ex',
        watch_all: true, lazy_each:150, lazy_delay:100,
        ex_list: true, ex_list_str:'8chan:#selection\n', ex_list_obj5:null,
        rm_list: true, rm_list_str:'http*\n', rm_list_obj5:null},
      virtualBoard: {
        scan:false, scanDelay:5,
        bl:{ show:true, max:20, p_board:'replace', p_remove:false, v_remove:true, dumb:false, showActives:false, activesWOF:true}, // activesWithOutFilter
        scan_domains: comf.Array_toObj(site0.domains,'none'),
//        scan_domains: {meguca:'none', lain:'none', '8chan':'none', '4chan':'none', KC:'none', lainjp:'none'},
        instant_scan: true, vtag:'no', vtagsel:true, vptagsel:true, ts:'cr',
        search: {show: true,  str:'', re:false,    sentence:false, rexps:null, __proto__:proto_search}, assb:true, // AutoShrinkSearchBar
      },
      tagSearch:{auto:false, th_b:20, th_t:1}, // for 8chan
      style:{
        design:{
          titleBar:{base:'background:#b5ccf9;border:1px solid blue;font-weight:normal', ov:false, sel:'body', comp:true},
          window:{base:'background:#e5ecf9;color:#000000;font-weight:normal', ov:false, sel:'body', comp:true},
          popUp:{base:'background:#e5f4f9;color:#000000;border:2px solid blue;font-weight:normal', ov:false, sel:'body', comp:true},
          post_new:{base:'border-left:3px solid red', ov:false, sel:'', comp:true},
          post_editing:{base:'background:#cec952 !important', ov:false, sel:'', comp:true},
        },
        zIndex:1,
//        sel: 'fix',
//        fix:{
//          titleBar_str:'background:#b5ccf9;border:1px solid blue;font-weight:normal',
//          window_str:'background:#e5ecf9;color:#000000;font-weight:normal',
//          popUp_str:'background:#e5f4f9;color:#000000;border:2px solid blue;font-weight:normal'
//        },
//        copy:{titleBar_str:'body', window_str:'body', popUp_str:'body', titleBar_comp:true, window_comp:true, popUp_comp:true},
//        post_editing: 'background:#cec952 !important',
//        post_new: 'border:2px solid red',
        userCSS: {use:false, str:''},
        tips:{
          merged:true, merged_cnt:'>>',
//          merged1:true, merged1_str:'merged1::before{float:left;content:">";}',
//          merged2:true, merged2_str:'merged2::before{float:left;content:">>";}',
//          merged3:true, merged3_str:'merged3::before{float:left;content:">>>";}',
        },
        addSS: {use:false, str:''},
      },
      recovery:{comment:true, interval:10, auto_clean:true},
      healthIndicator: {show:true, max:10, expand_running:false, dont_retire_running:true, cancel:true},
      network: {cross_domain:'indirect', fetch_actively:true, adaptive:true, th100:5, th100_delay:500, th20:10, th20_delay:100, timeout:15, overXFO:true, overCSPF:true},
      stats: {use:false, retain_404:true, draw_delay:10, estimate_posts:true,
              save:true, load:true, len_capture:1440, auto_acquisition:true, auto_acquisition_scan:true, auto_acquisition_scan_delay:120, auto_acquisition_all:false, 
              tolerant:true, tolerance:90, patch_tm:false},  // patch_tm is a patch for thread moving in lainchan or 4chan/bant/.
      chart: {off_anime_blur:true, window_width:400, window_height:400, instant_scan:true, load_on_demand:false,
              inst:{len:120, scale_thread:10, time_sel:1, board_sel:0, show_legend:true, // separate:false, 
                    show: {np:true, p:false, ep:false, nt:false, t:true, et:false}, clip_np:false, clip_np_val:0,
                    options: {bezierCurve: false, animation:false, pointDot:true, pointDotRadius:4}}},
      archive:{load_img:true, restore_auto:true, clear_threads:true, clear_files:false, format:'auto', domain:0, board:'',
               src:'shown', store_auto:false, dir_dled:null, open_local:false, fix_inconsistency:true, store_watched:false,
               oneshot: {post:true, tn:true, img:true,  webm:true , post_idb:true, tn_idb:true, img_idb:true,  webm_idb:true},
               live:    {post:true, tn:true, img:false, webm:false, post_idb:true, tn_idb:true, img_idb:false, webm_idb:false},
               deleted: {post:true, tn:true, img:false, webm:false, post_idb:true, tn_idb:true, img_idb:false, webm_idb:false},
               IDB:     {auto_clean:true, auto_clean_init:true, prune:168, prune_flush:false, nof_tr:10, nof_cl:20, nof_cl_max:80,
                         auto_restore:false, auto_restore_watch:false, auto_restore_remove:true,
                         watchdog:120, redirect_404: false, redirect_404_CSP: false}, // check_every:1, },
               kwd: {use:true, str:'', re:false, ci:true, match:0, op:true, post:false, sub:true, name:true, trip:false, com:true, file:false, meta:false, sentence:false, ew:false,   rexps:null},
               list:true,
               list_str:'', list_obj6:null, list_inherit:false,
               tar: true, tarsize: 100, sub_in_filename:true,
//               get list_str(){return pref3.archive.list_str_get();},
//               set list_str(val){pref3.archive.list_str_set(val);},
               files_sel:0,
               IDB_board_sel:0,  IDB_board_sel_gfunc_pre:pref4.archive.IDB_board_sel_gfunc_pre,
               IDB_thread_sel:0, IDB_thread_sel_gfunc_pre:pref4.archive.IDB_thread_sel_gfunc_pre,
               IDB_select_multiple: true,
               editing_timeout: true,
              },
      threadStats: {use:false, full:true, retry:false},
      RSS: {str:'', sec_auto:true, dnd:{nf:false, f_rec:true, f_vbd:false}},
      debug_mode : {unread_count:'', parse_error:false, site2func:'', site2func_expand:true, pfunc:'', pfunc_expand: true, pfunc_all:'', pfunc_all_expand: true, pfunc_all_site2: false,
                    pfunc_comp:'', pfunc_comp_expand_same:true, pfunc_comp_expand_diff:true, pfunc_comp_proto:'', domains: comf.Array_toObj(site0.debug_domains,true),
                    types: comf.Array_toObj(site0.debug_types, true)},
      test_mode: {tips:false, num:0, num_f:0, test_str:''},
      patch: {
        delayed_invoke: {use: false, sec: 10},
        rm_404_blacklist: [], //['8chan'], // 8chan sends corrupted data.
      },
      pref2: {
        KC: {summer_time: false},
        meguca: {
//          get remove_history_class(){return pref.catalog_open_where!=='_self';},
          historyAPI: true,
          utilize_boards_json:true, utilize_boards_json_domain:true,},
        '8chan': {utilize_boards_json:true,},
        BBC: {summer_time: true},
      },
      easy: {posts_ago:24, threads_ago:24, max_boards:50,},
      easy2: {
        reset: true, presets: 0, time_post:24, time_op:24, basics:true, // easy2 local parameters.
        auto_update:false, auto_update_period:10, // easy2 local parameters.
        VB: {local:0, global:0}, LTfrom: 0, limits:0, // easy2 local parameters.
        virtualBoard: {scan:true, bl:{show:true, max:100}},
        liveTag: {use:false, from:'op'},
        catalog: {embed:true, embed_page:true, max_threads_at_refresh:500,
                  auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true, auto_update:true, auto_update_period:10},
        filter: {time:{use:false, func:'pn', tv:24}}, // time_str is dropped intentionally for not overwriting when time.use===false
//        filter: {time_watch:false, time_watch_creation:false},
        page:{auto_update:true, auto_update_period:10},
        thread: {embed:false, auto_update:true, auto_update_period:10},
        notify:{desktop:{notify:true}},
        stats: {use:false},
        scan:{max:100, max_threads:1000},
        network:{fetch_actively:false},
        healthIndicator:{expand_running:false},
      },
      INST:{},
      dashboard: {rss_live:true, rss:null, rss_apply_global:false, rss_com:'show'},
      colorPicker: { bgbd:'bg', bds:'2px solid', txt:'', picker:'#ff0000', col:'#ff0000'},
      proto: proto, // for overwrite from pref_default.
    };
    for (var i=0;i<50;i++) pref_new.debug_mode[i] = false;
    for (var i=0;i<220;i++) pref_new.test_mode[i] = false;
    if (site0.pref_default2) pref_func.pref_overwrite(pref_new,site0.pref_default2);
    if (site0.pref_default ) pref_func.pref_overwrite(pref_new,site0.pref_default);
    if (site0.pref_default3) comf.overwrite_prop(pref_new,site0.pref_default3);
//    if (site) if (site2[site.nickname].pref_default) pref_func.pref_overwrite(pref_new,site2[site.nickname].pref_default);
    if (for_save) {
      pref_func.delete_null(pref_new);
//      Object.defineProperty(pref_new.page,  'footer',{get:function(){return pref_new.catalog.footer;}, enumerable:true, configurable:true}); // patch for refer to each object instead of global pref.
//      Object.defineProperty(pref_new.thread,'footer',{get:function(){return pref_new.catalog.footer;}, enumerable:true, configurable:true});
//      Object.defineProperty(pref_new.float, 'footer',{get:function(){return pref_new.catalog.footer;}, enumerable:true, configurable:true});
      delete pref_new.tabID;
    } else {
      Object.defineProperty(pref_new, 'samples', {get:function(){return pref_func.settings.samples;}});
      Object.defineProperty(pref_new.filter.time, 'time_str', {get: function(){return this.time_val>0x10000000? new Date(this.time_val).toLocaleString() : this.time_val;},
                                                               set: function(v){this.time_val = Date.parse(v) || 0;}}); // configurable:false for test
    }
//    comf.Object_modifyDescriptor(pref_new.style.userCSS,'sample',{writable:false});
    comf.Object_modifyDescriptor(pref_new.features.domains, site0.domain, {writable:false});
    Object.defineProperty(pref_new.triage, 'sampleLoader', {value:0, configurable:true});
    return pref_new;
  }
  var gGEH = {
    get_key_rec: function(e){return this.get_key_recursive(e.target, e.currentTarget);},
    get_key_recursive: function(pn, pn_end, allow_merged){
      var key;
      while (pn && !(key=this.pns_all_keys.get(pn)) && pn!==pn_end) pn = pn.parentNode;
      if (key) if (!allow_merged && typeof(key)!=='string') key = key.lths[0].key; // for merged thread
      return key;
    },
    pns_all_keys: new WeakMap(),
    pns_resize: new WeakMap(), // stores resize funcs
    triage_in: null, // patch for test_mode['127']
    triage_in2: null, // patch for test_mode['127']
    last_viewed: null,
    time_jumped: 0,

    drag: (function(){
      var sx = 0;
      var sy = 0;
      var effect = null;
      var callback = null;
      function dragover(e){ // http://www.html5rocks.com/ja/tutorials/dnd/basics/
        e.preventDefault();
        e.dataTransfer.dropEffect = effect;
      }
      function dragend(e){
        document.body.removeEventListener('dragover',dragover,false);
        var pn = e.currentTarget;
        pn.removeEventListener('dragend',dragend,false);
        var dx = e.screenX - sx;
        var dy = e.screenY - sy;
        if (effect==='move') {
          var s = pn.style;
          if (pref.catalog_popup_size_fix && pn.dataset.isPopup==='true') {
            s.width  = pn.offsetWidth + 'px';
            s.height = pn.offsetHeight + 'px';
          }
          if (s.left) s.left  = (parseInt(s.left ,10) + dx) + 'px';
          else        s.right = (parseInt(s.right,10) - dx) + 'px';
          if (s.top)  s.top = (parseInt(s.top   ,10) + dy) + 'px';
          else     s.bottom = (parseInt(s.bottom,10) - dy) + 'px';
        }
        if (callback) callback(e, dx, dy);
        callback = null;
        effect = null;
        e.stopPropagation();
      }
      return {
        started: function(e, callback_in, effect_in){
          sx = e.screenX;
          sy = e.screenY;
          document.body.addEventListener('dragover',dragover,false);
          e.currentTarget.addEventListener('dragend',dragend,false);
          effect = effect_in;
          callback = callback_in;
          e.stopPropagation();
        },
        get dragging(){return effect;}
      };
    })(),
  };

  var pref_func = {
      delete_null: function delete_null(dst){
        for (var i in dst) {
          if (dst[i]===null /*|| typeof(dst[i])==='function'*/) delete dst[i];
          else if (typeof(dst[i])==='object' && !Array.isArray(dst[i])) delete_null(dst[i]);
        }
      },
      pref_overwrite: function pref_overwrite(dst,src,strict,loose,   test_new_func){ // test_new_func for cosolidate with overwrite_prop, to delete DOMs of files, see 'load_default'
        for (var i in src) // if (!pref || !pref.test_mode['199'] || src.hasOwnProperty(i)) // test_mode['199'] should be tested later.
          if (loose || dst[i]!==undefined && (dst[i]!==null || pref && pref.test_mode['117'])) {
            if (strict && typeof(src[i])!==typeof(dst[i])) continue;
            if (test_new_func? dst[i]!==null && src[i]!==null && typeof(dst[i])==='object' && typeof(src[i])==='object'
                : typeof(src[i])==='object' && !Array.isArray(src[i])) pref_overwrite(dst[i],src[i],strict,loose, test_new_func); // CAUTION. SKIP NULL IMPLICITLY. SHOULD THIS BE FIXED?
//            else dst[i] = src[i];
            else if (dst[i]!==src[i] || !dst.hasOwnProperty(i) && (Object.getPrototypeOf(dst)||{})[i]!==(Object.getPrototypeOf(src)||{})[i]) dst[i] = src[i]; // for i15 case, see Debug.diff // utilize prototype
//            else if (dst[i]!==src[i]) dst[i] = src[i]; // utilize prototype
          }
        return dst;
      },
    parseJSONCs: (function(){
      function err_extra(str, src_str, allow){
        if (str.search(/^[\s\n]*$/)===0) return 0;
        if (!allow) console.log('WARNING: '+src_str+': There are extra characters in JSON: '+str);
        return 1;
      }
      return function(str, callback, src_str, allow_prefix, callback_args0, callback_args1){ // https://stackoverflow.com/questions/17638305/why-is-bind-slower-than-a-closure
        var result = {pass:0, fail:0, extra:0, prefix:0};
        var prefix = '';
        var json_str = '';
        var start = 0;
        var level = 0;
        var i=-1;
        while (++i<str.length) {
          var s_i = str[i];
          if (s_i==='"') while (++i<str.length) {if (str[i]==='\\') i++; if (str[i]==='"') break;}
//        if (s_i==='"') while (++i<str.length) if (str[i]==='"' && str[i-1]!=='\\') break;} // referencing back letter may be slow? // BUG at \\"
          else if (s_i==='{' || s_i==='[') {
            if (level++===0) {
              prefix = json_str + str.slice(start,i);
              if (prefix) result.prefix += err_extra(prefix, src_str, allow_prefix);
              json_str = '';
              start = i;
            }
          } else if (s_i==='}' || s_i===']') {
            if (--level<=0) {
              json_str += str.slice(start,i+1);
              var outer_end = i+1;
              if (level===0) {
                var obj = undefined;
                try {
                  obj = JSON.parse(json_str);
                  result.pass++;
                } catch (e) {
                  console.log('ERROR: '+src_str+': in parsing JSON strings: '+json_str);
                  console.log(e);
                  result.fail++;
                }
                if (obj!==undefined) {
                  var line_comment = callback.length>=7 && str.slice(outer_end).match(/^\s*(\/\/[^\n]*)*\n/);
                  if (line_comment) outer_end += line_comment[0].length;
                  callback(obj, prefix.replace(/^[\s\n]*/,''), callback_args0, callback_args1, start, i+1, start-prefix.length, outer_end); // call even if obj===null
                }
              } else if (level<0) {result.extra += err_extra(json_str, src_str); level = 0;}
              json_str = '';
              start = outer_end;
            }
          } else if (s_i==='/') {
            if (str[i+1]==='/')      {json_str += str.slice(start,i); i++; while (++i<str.length) if (str[i]==='\n') break; start = i+1;} // remove comments of //
            else if (str[i+1]==='*') {json_str += str.slice(start,i); i++; while (++i<str.length) if (str[i]==='/' && str[i-1]==='*') break; start = i+1;} // remove comments of /* */
          } // else if (s_i==='\\') i++; // skip an escaped letter
        }
        if (start!==str.length || json_str!=='') result.extra += err_extra(json_str + str.slice(start), src_str);
        return result;
      };
    })(),
    parseJSONCs_out: function(pn, result, allow_prefix){
      var extra = (allow_prefix? 0 : result.prefix) + result.extra;
      var suc = result.fail===0 && extra===0;
      pn.style.color = (suc)? '' : 'red';
      pn.textContent = (suc? 'OK' : (result.fail>0? 'Failed:'+result.fail : '')+(extra>0? (result.fail>0? ', ':'')+'Extra strings:'+extra : ''))+', Passed:'+result.pass +
                        ' @'+ new Date().toLocaleString();
      return suc;
    },
    queryOrSet_params_by_JSON: (function(){
      var str_out;
      function query_or_overwrite(obj, prefix, query, enables){
        for (var i in obj) {
//          var API = i==='add_rss' && site2 && site2['rss'];
//          var root_obj = i==='site2'? site2 : i==='pref'? pref : i==='pref_func'? pref_func : i=='liveTag'? liveTag : i==='site3'? site3 : API || null; // remove eval(i)
          var root_obj = i==='site2'? site2 : i==='pref'? pref : i==='pref_func'? pref_func : i=='liveTag'? liveTag : i==='site3'? site3 : i==='site'? site : null; // remove eval(i)
//          if (i==='site2' || i==='pref' || i==='pref_func' || i=='liveTag' || i==='site3')
          if (root_obj && (!enables || enables.indexOf(i)!=-1))
//            if (!query) pref_func.pref_overwrite(root_obj, API? obj : obj[i], undefined, i==='site2');
            if (!query) pref_func.pref_overwrite(root_obj, obj[i], undefined, i==='site2');
            else str_out = '{"' + i + '":' + pref_func.pref_query(query===true? root_obj : query, obj[i]) + '}\n';
        }
      }
      return function(query, str_in, enables, pn_out){
        if (!str_in) return;
        str_out = '';
        var result = this.parseJSONCs(str_in, query_or_overwrite, 'JSON_CLI', false, query, enables);
        if (pn_out) var suc = this.parseJSONCs_out(pn_out, result);
        if (query && suc) return str_out;
      };
    })(),
    site2_json: function(enables){
      this.queryOrSet_params_by_JSON(false, pref.cli.json_str, enables);
    },
    RSS_json: (function(){
      function fmt_and_copy(dst, src, func_fmt){ // fmt_ane_copy is faster than copy_and_fmt if dic is accumulated.
        func_fmt(src);
        for (var t in src) dst[t] = src[t];
      }
      function RSS_cfg(obj, prefix, done_init, remake){
        var pfunc = site2['rss'].parse_funcs['page_html'];
        if (!prefix) {if (site2['rss']) site2['rss'].add_rss_append(obj, done_init, remake);}
        else if (prefix.indexOf('DEFAULT')!==-1) for (var i in obj) {
          if (i==='dic_tags' || i==='dic_funcs') RSS_cfg(obj[i], i);
//          if (i==='dic_tags') fmt_and_copy(pfunc.dic_tags.always, obj[i], pfunc.fmt_dic_tags);
//          else if (i==='dic_funcs') for (var j in obj[i]) pfunc.dic_funcs[j] = pfunc.eval_func(obj[i][j]);
          else pfunc.opt_def[i] = obj[i];
        } else if (prefix.indexOf('dic_tags')!=-1) {
          if (('incase' in obj) || ('always' in obj)) for (var j in obj) fmt_and_copy(pfunc.dic_tags[j], obj[j], pfunc.fmt_dic_tags);
          else fmt_and_copy(pfunc.dic_tags.always, obj, pfunc.fmt_dic_tags);
        } else if (prefix.indexOf('dic_funcs')!=-1) for (var j in obj) pfunc.dic_funcs[j] = pfunc.eval_func(obj[j]);
          
//        var tgt = prefix.indexOf('dic_tags')!=-1? pfunc.dic_tags : prefix.indexOf('dic_funcs')!=-1? pfunc.dic_funcs : null;
//        if (tgt) {
//          if (tgt===pfunc.dic_tags) for (var i in obj) pfunc.fmt_dic_tags(obj[i]);
//          else if (tgt===pfunc.dic_funcs) for (var i in obj) obj[i] = pfunc.eval_func(obj[i]);
//          pref_func.pref_overwrite(tgt, obj, undefined, true);
//        } else if (site2['rss']) site2['rss'].add_rss_append(obj);
      }
      return function(init, remake){
        if (!init) {
          var pfunc = site2['rss'].parse_funcs['page_html'];
          var tgts = [pfunc.opt_def, pfunc.dic_tags.incase, pfunc.dic_tags.always, pfunc.dic_funcs];
          for (var i=0;i<tgts.length;i++) for (var j in tgts[i]) if (tgts[i].hasOwnProperty(j)) delete tgts[i][j];
        }
        var done_init = {};
        return [this.parseJSONCs(pref.RSS.str, RSS_cfg, 'RSS_json', true, done_init, remake), done_init];
      };
    })(),
//      site2_json: function(query, dumb, enables){ // working code // obsolete
//        var pn_out = document.getElementsByName('JSON_result')[0];
//        if (pn_out) pn_out.style = {};
//        try { 
//          if (pref.cli.json_str!=='') {
//            var fields = pref.cli.json_str.split('"'); // can't handle \"
//            var i=0;
//            while (i<fields.length) {
//              if (fields[i].search(/\/\/.*/)!=-1) {
//                while (i+1<fields.length && fields[i].search(/\/\/[^\n]*\n/)==-1) fields[i] += fields.splice(i+1,1);
//                fields[i] = fields[i].replace(/\/\/[^\n]*(\n|$)/,'');
//              } else i+=2;
//            }
//            var str = fields.join('"');
//            var ex_count = 0;
//            var str_out = '';
//            var count = 0;
//            var start = 0;
//            for (var j=0;j<str.length;j++) { // can handle multiple JSON.
//              if (count==0 && str[j]!=='{' && str[j]!==' ' && str[j]!=='\n') ex_count++;
//              if (str[j]==='{') count++;
//              if (str[j]==='}' && --count==0) {
//                var obj = JSON.parse(str.substr(start,j-start+1));
//                for (var i in obj)
//                  if (i==='site2' || i==='pref' || i==='pref_func' || i=='liveTag' || i==='site3')
//                    if (!enables || enables.indexOf(i)!=-1)
////                  if ((!disable_others && (i==='site2' || i==='pref' || i==='pref_func' || i=='liveTag')) || (!disable_site3 && i==='site3'))
//                      if (!query) pref_func.pref_overwrite(eval(i),obj[i]);
//                      else str_out = '{"' + i + '":' + pref_func.pref_query(query===true? eval(i) : query, obj[i]) + '}';
//                start = j+1;
//              }
//            }
//
////            if (str!=='') { // working code.
////            if (str.indexOf('{')!=-1) {
////              var obj = JSON.parse(str);
////              for (var i in obj)
////                if (i==='site2' || i==='pref' || i==='pref_func') pref_func.pref_overwrite(eval(i),obj[i]);
////            }
//////          if (pref.overwrite_site2_json_str!=='') { // working code
////////            var str = pref.overwrite_site2_json_str.replace(/\/\/.*/mg,'').replace(/\n/g,'');
//////            var fields = pref.overwrite_site2_json_str.split('"');
//////            for (var i=0;i<fields.length;i+=2) {
//////              if (fields[i].search(/\/\/.*/)!=-1) {
//////                if (fields[i].search(/\n/)!=-1 || i+1==fields.length) fields[i] = fields[i].replace(/\/\/[^\n]*(\n|$)/,'');
//////                else {
////////                  while (i+1<fields.length && fields[i].search(/\n/)==-1) fields[i++]='';
//////                  while (i+1<fields.length && fields[i].search(/\n/)==-1) fields.splice(i,1);
//////                  if (i<fields.length) fields[i] = fields[i].replace(/[^\n]*(\n|$)/,'');
//////                }
//////              }
//////            }
//////            var str = fields.join('"');
//////            var count = 0;
//////            var start = 0;
//////            for (var j=0;j<str.length;j++) {
//////              if (str[j]==='{') count++;
//////              if (str[j]==='}') {
//////                count--;
//////                if (count==0) {
//////                  var str_tmp = str.substr(start,j-start+1);
//////                  if (str_tmp!=='') {
//////                    var obj = JSON.parse(str_tmp);
//////                    for (var i in obj)
//////                      if (i==='site2' || i==='pref' || i==='pref_func') pref_func.pref_overwrite(eval(i),obj[i]);
//////                  }
//////                  start = j+1;
//////                }
//////              }
//////            }
////////            if (str!=='') pref_func.pref_overwrite(site2,JSON.parse(str));
////////            if (str!=='') {
////////              var obj = JSON.parse(str);
////////              for (var i in obj)
////////                if (i==='site2' || i==='pref' || i==='pref_func') pref_func.pref_overwrite(eval(i),obj[i]);
////////            }
//            if (pn_out) {
//              pn_out.textContent = ((ex_count!=0)? 'OK, but there are extra useless strings, ' :
//                                    (count!=0)? 'OK, but there are non-closed brackets, ' :
//                                    'OK, ') + new Date().toLocaleString();
//              pn_out.style.color = '';
//              if (str_out!=='') {
//                document.getElementsByName('cli.json_str')[0].value = str_out;
//                pref.cli.json_str = str_out;
//              }
//            }
//          }
//        } catch (e) {
//          if (pn_out) {
//            pn_out.textContent = 'ERROR!!!, ' + new Date().toLocaleString();
//            pn_out.style.color = 'red';
//          }
//          console.log('ERROR in overwtite strings:');
//          console.log(pref.cli.json_str);
//          console.log(e);
//        }
//      },
      sanitize: function(str){
        return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
      },
    report_error: function(str){
      console.log(str);
      var pn = this.settings && this.settings.pn13 && this.settings.pn13.getElementsByClassName(pref.cpfx+'errors')[0];
      if (pn) pn.appendChild(cnst.dom('<div>'+str+'</div>'));
    },
    add_to_list_2: null, // inserted later
    fmt4str2_2: null, // inserted later
  };

  var pref = pref_default(); // pref_default is loaded.
  (function(){
    function pref_overwriteAll(storage){
      var str = storage && storage[pref.script_prefix+'.pref'];
      if (str) pref_func.pref_overwrite(pref, JSON.parse(str), true);
//      if (!src_str) return;
//      var src = JSON.parse(src_str);
//      if (src.proto) {pref_func.pref_overwrite(pref.proto, src.proto, true); delete src.proto;}
//      pref_func.pref_overwrite(pref, src, true);
    }
    pref_overwriteAll(localStorage);
  //  if (  localStorage &&   localStorage[pref.script_prefix+'.pref']) pref_func.pref_overwrite(pref,JSON.parse(  localStorage[pref.script_prefix+'.pref']),true);
    for (var i in {catalog:null, page:null, float:null, thread:null}) if (!pref[i].save_board_list_sel) pref[i].board_list_sel = 0;
    pref_overwriteAll(sessionStorage);
  //  if (sessionStorage && sessionStorage[pref.script_prefix+'.pref']) pref_func.pref_overwrite(pref,JSON.parse(sessionStorage[pref.script_prefix+'.pref']),true);
    if (window.name) for (var i in {catalog:null, page:null, float:null, thread:null}) pref[i].board_list_sel = 0;
    if (pref.cli.auto) pref_func.site2_json(['pref']);
    pref.script_prefix = pref_func.sanitize(pref.script_prefix);
    if (site0.isStep) {
  //    for (var i in site.features) site.features[i] = false;
      for (var i in pref.features) if (i!=='domains') pref.features[i] = false;
      for (var i in pref.features.domains) if (i!==site0.domain) pref.features.domains[i] = false;
      brwsr.sw_cache = null;
      pref_func.pref_overwrite(pref,{
        catalog:{auto_update:false},
        thread:{auto_update:false},
        page:{auto_update:false},
        float:{auto_update:false},
        virtualBoard:{scan:false},
        stats:{auto_acquisition_scan:false},
        cloudflare:{auto_reload:false},
      });
    }
    if (pref.pref2.KC.summer_time) site2['KC'].time_offset = 2;
    if (!window.SharedWorker) {
      pref.info_server = false;
      pref.info_client = false;
    }
  })();
  var pref3 = { // auto sync objects.
    stats: {use:null, estimate_posts:null},
    reload_required: false,
//      order:{ordering_old: 0},
//    filter: {
//        time_str: function(parent,key,val, parent_src){parent_src['time_obj6'] = Date.parse(val);},
////        time_str: function(parent,key,val){parent[key.replace(/str$/,'obj')] = Date.parse(val);},
////        time_obj6: 0,
//    },
//    notify:{sound:{file:null}},
    archive: {
      working: false,
      list_str: function(parent,key,val){parent[key.replace(/str$/,'obj2')] = pref_func.str2obj2(val);},
      list_obj2: null,
//      list_obj3: null,
      jsons: null,
      imgs: null,
      dir: null,
//      list_str_set: function(val){
//        pref3.archive.list_str = val;
//        pref_func.str2obj2(pref3.archive,'list_obj2',val);
//      },
//      list_str_get: function(){
//        var lm = liveTag.mems;
//        for (var name in pref3.archive.list_obj2) {
//          var dbt = comf.name2domainboardthread(name);
//          if (!dbt[2]) continue;
//          if (lm[dbt[0]] && lm[dbt[0]][dbt[1]]) var lth = lm[dbt[0]][dbt[1]][dbt[2]];
//          if (lth && lth.archived && lth.time_checked !== pref3.archive.list_obj2[name].time) pref3.archive.list_str = list_func_1(pref3.archive.list_str, name, lth.time_checked); // BUG. NOT implemented yet.
//        }
//        return pref3.archive.list_str;
//        function list_func_1(str,name,datetime){ // refer 'triage_exe'
//          var key = new RegExp('(^|,)'+name.replace(/\+/,'\\+')+'([\\^@!][^,\n]*)*(,|\n|$)','mg');
////          var millisec = datetime%1000;
////          var time_str = '@' + new Date(datetime).toLocaleString() + ((datetime%1000==0)? '' : '.'+millisec);
////          str = str.value.replace(key,',') + ',' + name + time_str + '\n';
//          str = str.value.replace(key,',') + ',' + name + '\n'; // remove time_str
//          return str.replace(/,,+/g,',').replace(/^,/g,'').replace(/\n,/g,'\n').replace(/\n\n+/g,'\n').replace(/^\n/,'');
//        }
//      },
    },
    proto: {
      merge_list_str: function(parent,key,val){
        if (!this.merge_list_obj2_old) this.merge_list_obj2_old = this.merge_list_obj2;
        var head = null;
        var all_fullname = true;
        this.merge_list_obj2 = pref_func.str2obj2(val.replace(/\s*\+\s*/g,',+'), 'merge', null, function(field, tgt){
          var add = field[0]==='+'; // .search(/^\s*\+\s*/)!=-1;
          var name = (add? field.slice(1) : field) || 'DEFAULT'; // field.replace(/^\s*\+\s*/,'') || 'DEFAULT';
          var dbt = comf.name2domainboardthread(name,true);
          if (!dbt[0] || !dbt[1] || !dbt[2]) all_fullname = false;
          if (add && head) head[head.length] = name;
          else head = [name];
          tgt[name] = head;
        });
        if (all_fullname) this.merge_list_obj2[':F'] = true;
//        if (pref.test_mode['177']) { // DOESN"T WORK
//          this.merge_list_obj2c = {};
//          if (gClg) for (var name in gClg.AllThreads) this.merge_list_obj2c_update(name, true);
//        }
      },
      merge_list_obj2: null,
//      merge_list_obj2c: {}, // cache
      merge_list_obj2_old: null,
      merge_lv_str: function(parent,key,val){
        if (!this.merge_lv_obj2_old) this.merge_lv_obj2_old = this.merge_lv_obj2;
        parent['merge_lv_obj2'] = pref_func.str2obj2(val, 'merge_lv', null, 'val');
      },
      merge_lv_obj2: null,
      merge_lv_obj2_old: null,
//      merge_lv_obj3: null,
    },
    test_mode: {js_file:null},
  };
//  Object.defineProperty(pref3.proto, 'merge_list_obj2c_update', {value:function(name, single){ // DOESN'T WORK
//    var arr = pref_func.merge_obj5a_sc(name, this.merge_list_obj2, null);
//    if (arr) {
//      if (single) this.merge_list_obj2c[name] = arr;
//      else for (var i=0;i<arr.length;i++) this.merge_list_obj2c[arr[i]] = arr;
//    }
//  }, writable:true, configurable:true});

//  Object.defineProperties(pref3.filter.kwd, {
//    'clear': {value:pref3.filter.kwd.clear, configurable:true, enumerable:false, writable:true},
//    'init':  {value:pref3.filter.kwd.init,  configurable:true, enumerable:false, writable:true}});

  var gClg; // global catalog
  var pClg; // primary catalog
  var debugWindow;
  var cataLog = {
    threads: null,
    scan_boards: null,
    scan_init: null,
//    catalog_filter_query: null,
    embed_mode: null,
////    catalog_refresh_watch: null,
    format_html: null,
    show_catalog: null,
    get_ref_height: null,
    catalog_obj2: null,
    healthIndicator: null,
    parent: null, // triage_parent
//    show_catalog_scroll_lock: null,
    general_event_handler: null,
    catalog_refresh_end: null,
//    catalog_filter_changed: null,
    image_hover_reentry: null,
    image_hover_remove: null,
    scan_boards_keyword_callback2: null,
    scan_boards_keyword_callback2_default_args: null,
    catalog_filter_query_keyword: null,
    event_func: null,
    restore_th_from_IDB: null,
    components: null,
    triage: null,
    GEH: null,
    insert_myself: null,
    DIH: null,
    auto_update: null,
    gClg: null,
    InfoPv: null, 
  };

  pref_func = {
      __proto__: pref_func,
      mirror_targets: {
        pn12_0_2: null,
        pn12_0_4: null,
        get pn13_1(){return pref_func.settings.pn13_1;},
        boardlist: null,
      },
      mirror_names: (function(){
        var dic = {
          'virtualBoard.bl.max': null,
          'virtualBoard.bl.dumb': null,
          'virtualBoard.bl.showActives': null,
          '*.merge': null,
          '*.merge_list': null,
          '*.refresh_src': null,
          '*.refresh_src_th': null,
//          '*.appearance.expand.*': null, // needs so many lines of code, omitted.
        };
        return function(name){
          var hier = name.split('.');
          if (hier[0]==='catalog' || hier[0]==='page' || hier[0]==='thread' || hier[0]==='float') hier[0] = '*';
          return (hier.join('.') in dic);
        };
      })(),
////////      style_sheet: null,
      apply_prep: function(pn,set,propagate, mirror, make_obj, pref_obj, no_save){
//        if (!pn) return; // reject mirroring to null, // patch for attr_changed.
        var fm = (Array.isArray(pn))? pn
            : (pn instanceof HTMLCollection || pn instanceof NodeList)? Array.prototype.slice.call(pn)
            : (fm = pn.querySelectorAll('input,textarea,select'+ ((set)? '' : ',span[data-show_value]')), fm.length>0? Array.prototype.slice.call(fm) : [pn]);
//        var fm; // working code
//        if (Array.isArray(pn) || pn instanceof HTMLCollection || pn instanceof NodeList) fm = pn;
//        else {
//          var query_str = 'input,textarea,select'+ ((set)? '' : ',span[data-class=show_value]');
//          fm = Array.prototype.slice.call(pn.querySelectorAll(query_str));
//          if (fm.length==0) fm = [pn];
//        }
        this.apply_prep_2_all(fm, set, propagate, false, make_obj, pref_obj);
        if (!no_save) this.apply_prep_save(pn,set,propagate, false, make_obj, pref_obj);
        if (mirror!=='never') this.apply_prep_mirror(fm[0],set,propagate, mirror, make_obj, pref_obj); // will be removed.
      },
      apply_prep_1n: function(name,set,propagate, mirror, make_obj, pref_obj){
        this.apply_prep_1(pref_func.settings.pnOrDummy(name),set,propagate, mirror, make_obj, pref_obj);
      },
      apply_prep_1: function(pn,set,propagate, mirror, make_obj, pref_obj){
        var old_val = this.apply_prep_2(pn,set,propagate, false, make_obj, pref_obj);
        this.apply_prep_save(pn,set,propagate, false, make_obj, pref_obj);
        this.apply_prep_mirror(pn,set,propagate, mirror, make_obj, pref_obj);
        return old_val;
      },
      apply_prep_mirror: function(pn, set,propagate, mirror, make_obj, pref_obj){
        var name = pn.name || pn.getAttribute('name'); // getAttribute for span, pn.name for pnOrDummy
        if (name && this.mirror_names(name)) mirror = true;
        if (mirror) {
          var fms = [];
          for (var i in this.mirror_targets) if (this.mirror_targets[i]) {
            var pns = this.mirror_targets[i].querySelectorAll(pn.tagName+'[name="'+name+'"]');
            for (var j=0;j<pns.length;j++) fms[fms.length] = pns[j];
          }
//            fms = fms.concat(Array.prototype.slice.call(this.mirror_targets[j].getElementsByTagName(pn.tagName)).filter(function(v){return v.getAttribute('name')===name;}));
          for (var j=0;j<fms.length;j++) if (pn!==fms[j])
            if (fms[j].tagName!=='INPUT' || fms[j].type!=='radio' || fms[j].value===pn.value) this.apply_prep_2(fms[j], false, false, false, false, pref_obj);
        }
//          for (var j in this.mirror_targets) { // working code
//            if (this.mirror_targets[j]) {
//              fm = fm.concat(Array.prototype.slice.call(this.mirror_targets[j].getElementsByTagName('input')).filter(function(v){return v.name===pn.name;}));
//              var fm_tmp = this.mirror_targets[j].getElementsByTagName('input')[pn.name]; // working code, but this always returns A dom.
//              if (fm_tmp)
//                if (Array.isArray(fm_tmp)) fm = fm.concat(Array.prototype.slice.call(fm_tmp));
//                else fm[fm.length] = fm_tmp;
//            }
//          }
//          for (var j=fm.length-1;j>=0;j--) if (fm[j]===pn) fm.splice(j,1);
//          if (fm.length==0) return;
//          this.apply_prep_2(fm,set,propagate, mirror, make_obj, pref_obj);
      },
      apply_prep_save: function(fms,set,propagate, mirror, make_obj, pref_obj){
        if (pref.test_mode['117']) {if (sessionStorage && set) sessionStorage[pref.script_prefix+'.pref']=JSON.stringify(pref_func.site2_json_ex_remove());}
        else {if (sessionStorage && (set||make_obj)) sessionStorage[pref.script_prefix+'.pref'] = JSON.stringify(pref);}
      },
      apply_prep_2_all: function(fms,set,propagate, mirror, make_obj, pref_obj){
        for (var i=0;i<fms.length;i++) this.apply_prep_2(fms[i], set, propagate, false, make_obj, pref_obj);
      },
      apply_prep_2: function(fm,set,propagate, mirror, make_obj, pref_obj){
        var name = fm.name || fm.getAttribute('name') || fm.dataset['show_value']; // fm.name for pnOrDummy
        if (!name) return; // for <span> //  if (!fm.name) continue;
        if (fm.type=='button') return;
        var target_hier = pref_func.get_tgt(name, /*fm.type==='file'? pref3 :*/ pref_obj || set!==true && set || null); // set(==this['.']) for each instance
        var parent = target_hier[0];
        var tgt    = target_hier[1];
        if (!parent) return;
        var old_val = set? parent[tgt] : undefined; // don't invoke getters if !set
        var tagName = fm.tagName;
        if (tagName==='INPUT') {
          if (fm.type==='file') {
            if (!set && parent[tgt] && parent[tgt]!==fm) fm.parentNode.replaceChild(parent[tgt],fm);
            else parent[tgt] = fm; // set also when set===false, keep tracking the DomNode.
          } else if (set) {
//            if (parent[tgt+'_old']!==undefined) parent[tgt+'_old'] = parent[tgt]; // not used
            this.apply_prep_set(parent, tgt, fm, name);
//            if (!mirror && fm.type!=='radio') this.apply_prep(fm,false,false,true); // !mirror for cutting infinite loop. // radio has multiple elements.
          } else this.apply_prep_load(fm, parent, tgt);
          if (set || make_obj)
            if (typeof(parent[tgt])==='string' )
              if (fm.type==='text') {
//                if (tgt.substr(-3,3)==='str' && parent[tgt.substr(0,tgt.length-3)+'obj4']) pref_func.str2obj4(parent,tgt.substr(0,tgt.length-3)+'obj4',fm.value);
                if (tgt.substr(-3,3)==='str' && parent[tgt.slice(0,-3)+'obj6']) pref_func.str2obj6(parent,tgt,fm.value,name);
              }
        } else if (tagName==='TEXTAREA') {
          if (set) parent[tgt] = fm.value;
          else fm.value = parent[tgt];
          if (set || make_obj) {
            if (tgt.slice(-3)==='str') {
              var stgt = tgt.slice(0,-3);
              if (parent[stgt+'obj']) pref_func.str2obj(tgt);
              else if (parent[stgt+'obj2']) parent[stgt+'obj2'] = pref_func.str2obj2(fm.value);
//              if (tgt.substr(-3,3)==='str' && parent[tgt.substr(0,tgt.length-3)+'obj4']) pref_func.str2obj4(parent,tgt.substr(0,tgt.length-3)+'obj4',fm.value);
              else if (parent[stgt+'obj5']!==undefined) pref_func.str2obj5(parent,stgt+'obj5',fm.value, tgt==='rm_list_str');
              else if (parent[stgt+'obj6']===null) pref_func.str2obj6(parent,tgt,fm.value,name); // obj6===null always, obj6 is located in pref3, 
              else if (parent[stgt+'obj7']!==undefined) {
                if (parent[stgt+'obj7_old']!==undefined) parent[stgt+'obj7_old'] = parent[stgt+'obj7'];
                parent[stgt+'obj7'] = pref_func.str2obj7.s2o({}, fm.value, name);
              }
            }
          }
        } else if (tagName==='SELECT') {
          if (set) this.apply_prep_set(parent, tgt, fm, name); // if (parent[tgt]!==undefined) parent[tgt] = fm.selectedIndex;,
          else {
//            var tgt_obj = (tgt.search(/sel/)!=-1)? tgt.replace(/sel/,'obj') : null;
//            if (parent[tgt_obj]) {
//              fm.length=0;
//              for (var j=0;j<parent[tgt_obj].length;j++) {
//                fm.length++;
//                fm.options[fm.length-1].text = parent[tgt_obj][j][0]['key'];
//              }
//            }
            if (parent[tgt+'_gfunc_pre']) parent[tgt+'_gfunc_pre'](parent,tgt,parent[tgt],name,fm);
            this.apply_prep_load(fm, parent, tgt);
//            if (parent[tgt]>=fm.length) parent[tgt] = 0;
//            fm.selectedIndex = parent[tgt];
          }
        } else if (tagName==='SPAN') {
          if (!set && fm.dataset['show_value']) {
            var val = parent[tgt];
            var func_str = this.apply_prep_2_str[tgt+':'+parent[tgt]];
            if (func_str) val = (typeof(func_str)==='string')? func_str : func_str(parent);
//            if (fm.getAttribute('name').substr(-7,7)==='t2h_sel' && ['L','M','N'].indexOf(parent[tgt])!=-1) val = parent['t2h_'+parent[tgt]];
            fm.textContent = val.toString().replace(/^./,function(v){return v.toUpperCase();});
          }
        }
        if (propagate) {
          if (fm.oninput)  fm.oninput.call(fm, {currentTarget:fm, target:fm}, true);
          if (fm.onchange) fm.onchange.call(fm, {currentTarget:fm, target:fm}, true);
        }
        return old_val;
      },
      apply_prep_set: function(parent, tgt, fm, name){ // don't set if parent[tgt]===undefined
        var type = typeof(parent[tgt]);
        if (type==='number') {
          var num_str = fm.value.replace(/[\uff10-\uff19]/g,function(m){return String.fromCharCode(m.charCodeAt(0)-65248);}); // full width numbers -> half width numbers.
          var val;
          parent[tgt] = (fm.tagName==='SELECT')? fm.selectedIndex
                      : (val = (name && name.substr(-2)==='_f')? parseFloat(num_str) : parseInt(num_str,10), isNaN(val)? 0 : val)
        } else if (type==='boolean') parent[tgt] = fm.checked;
        else if (type==='string') if (fm.type==='text' || fm.type==='color' || fm.tagName==='SELECT' || fm.checked) parent[tgt] = fm.value;
      },
      apply_prep_load: function(fm, parent, tgt){
        var val = parent[tgt];
        if (fm.tagName==='SELECT') {
          fm.selectedIndex = (typeof(val)==='number')? (val>=fm.length? parent[tgt]=0:0, parent[tgt])
            : Array.prototype.slice.call(fm.options).map(function(v){return v.value;}).indexOf(parent[tgt]);
//          } else fm.selectedOptions = parent[tgt]; // selectedOptions is read only
        } else { // INPUT
          var type = typeof(val);
          if      (type=='number' ) fm.value = val;
          else if (type=='boolean') fm.checked = val;
          else if (type=='string' ) if (fm.type==='text') fm.value = val;
                                    else if (val === fm.value) fm.checked = true;
        }
      },
      apply_prep_2_str: {
        't2h_sel:L': function(parent){return parent['t2h_L'];},
        't2h_sel:M': function(parent){return parent['t2h_M'];},
        't2h_sel:N': function(parent){return parent['t2h_N'];},
        't2h_sel:N_unread': function(parent){return parent['t2h_N']+'+unread';},
        't2h_sel:unread': 'All unread',
        'posts_search_op:opaque': function(parent){return 'opaque '+parent['posts_search_op_opacity']+'%';},
//        'popup2:sr': 'SearchResult',
//        'popup2:srpv': 'SearchResult/Preview',
//        'popup2:pv': 'Preview',
//        'popup2:chart': 'PostRateChart',
//        'popup2:dp': 'DeletedPosts',
      },
//      make_pref_obj : function(name){ // copy of part of apply_prep.
//        var target_hier = pref_func.get_tgt(name);
//        var parent = target_hier[0];
//        var tgt    = target_hier[1];
//        if (tgt.search(/str$/)!=-1 && parent[tgt.replace(/str$/,'obj')]) pref_func.str2obj(tgt);
//        if (tgt.search(/str$/)!=-1 && parent[tgt.replace(/str$/,'obj2')]) pref_func.str2obj2(parent,tgt.replace(/str$/,'obj2'),parent[tgt]);
//      },
      catalog_board_list_str_or: '',
      catalog_board_list_str_bt: '',
      catalog_board_list_str_bt_same: '',
      add_onchange: function(pn,func_obj, func_obj2){ // obj2 for oninput
        return this.set_event_target(pn, this.add_onchange_entry_func.bind(func_obj), func_obj2, func_obj);
      },
      add_onchange_entry_func: function(e, propagated){ // always used after binded.
        var pn_name = e.currentTarget.getAttribute('name') || e.currentTarget.dataset.name; // getAttribute for <span>, but using dataset is right.
//        var pn_name = e.currentTarget.getAttribute('name'); // for <span>
        if (!propagated && ['INPUT','TEXTAREA','SELECT'].indexOf(e.currentTarget.tagName)!=-1) var old_val = pref_func.apply_prep_1(e.currentTarget, this['.'] && this['.'].pref || true); // this['.'] for each instance
        if (this[pn_name]) this[pn_name].call(this,e); // e.currentTarget,e); // exact match
        else if (this[pn_name+' w/']) this[pn_name+' w/'].call(this,e,pn_name);
        else if (this[pn_name+'/O']) this[pn_name+'/O'].call(this,e,pn_name,old_val);
//        else if (this[pn_name+'/t']) this[pn_name+'/t'].call(this,e); // all should be this.
        else {
          var idx = pn_name.indexOf('.');
          if (idx!=-1) {
            var name = pn_name.substr(idx); // *.XXX
            if (this['*'+name]) this['*'+name].call(this,e); // e.currentTarget,e);
            else if (this['*w/'+name]) this['*w/'+name].call(this,e,pn_name); // e.currentTarget,e,pn_name);
            else {
              var idx_l = pn_name.lastIndexOf('.');
              if (idx_l!=-1) {
                var arr = pn_name.split('.');
                var name = pn_name.substr(0,idx_l+1)+'*'; // XXX.YYY.*
                if (this[name]) this[name].call(this,e); // e.currentTarget,e);
                else if (this[name+'W/']) this[name+'W/'].call(this,e,arr); // arr version
                else if (this[name+'w/']) this[name+'w/'].call(this,e,pn_name); // e.currentTarget,e,pn_name); // XXX.YYY.* with source name
                else {
//                  var arr = pn_name.split('.');
                  var name02 = arr.slice(0,-2).join('.')+'.*.'+arr.slice(-1)[0]; // XXX.*.ZZZ
                  if (this[name02]) this[name02].call(this,e);
                  else if (this[name02+'/O']) this[name02+'/O'].call(this,e,pn_name,old_val);
                  else {
                    var name0 = arr.slice(0,-2).join('.')+'.*.*W/'; // XXX.*.*
                    if (this[name0]) this[name0].call(this,e,arr);
                    else if (arr[0]==='catalog' || arr[0]==='page' || arr[0]==='headline' || arr[0]==='float' || arr[0]==='thread') { // for these, hierarchical approach is PROHIBITED.
                      var nameMV = 'MODEVIEW.'+arr.slice(1,-2).join('.')+'.*.*W/'; // MODEVIEW.XXX.*.*
                      if (this[nameMV]) this[nameMV].call(this,e,arr);
//                else {
//                  var name_middle = pn_name.split('.').slice(1,-1).join('.');
//                  var func;
//                  if (name_middle && func = this['*.'+name_middle+'.*']) func.call(this,e);
//                  else if (name_middle && func = this['*.'+name_middle+'.*w/']) func.call(this,e,pn_name); // *.YYY.*
                    } else if (idx!=-1) { // hierarchical approach,
                      var name_0 = arr[0]; // XXX.YYY 
                      var name_1 = pn_name.slice(idx+1);
                      if (this[name_0] && this[name_0][name_1]) this[name_0][name_1](e); // NO EMULATION of 'this' value. should consolidate to func.call(this,e).
                    }
                  }
                }
              }
            }
          }
        }
//        var pn = e.target.parentNode; // 'Filter' doesn't have stopper.
//        while (pn && pn!==site.script_body) {
//          if (pn.getAttribute('name')==='SUB') this['SUB'].call(pn,{currentTarget:pn});
//          pn = pn.parentNode;
//        }
      },
      add_onchange_format: function(func_obj){
        var keys = Object.keys(func_obj);
        for (var i=0;i<keys.length;i++) if (typeof(func_obj[keys[i]])==='string') func_obj[keys[i]] = func_obj[func_obj[keys[i]]];
        return func_obj;
      },
////      add_onchange: function(pn,func_obj, func_obj2){ // obj2 for oninput // working code.
////        var call_tgt = func_obj;
////        if (typeof(func_obj)!=='function') { // object
////          for (var i in func_obj) if (typeof(func_obj[i])==='string') func_obj[i] = func_obj[func_obj[i]];
////          if (func_obj2) for (var i in func_obj2) if (typeof(func_obj2[i])==='string') func_obj2[i] = func_obj2[func_obj2[i]];
//////          func_obj.func_default = function(){ // cause EventListener leak.
//////            pref_func.apply_prep(this,true);
//////            if (func_obj[this.name]) func_obj[this.name](this);
//////          };
//////          call_tgt = func_obj.func_default;
////          call_tgt = func_obj.entry_func;
////        }
////        this.set_event_target(pn,call_tgt, func_obj2);
////      },
      set_event_target: (function(){
        var inst_while_input = null;
        function prep_beforeunload(e){
          inst_while_input = (e)? e.target : null;
        }
        function set_val_beforeunload(){
          if (inst_while_input) pref_func.apply_prep_1(inst_while_input,true, (inst_while_input.getAttribute('name')||'').indexOf('dashboard.rss')===0); // patch for dashboard.rss.arr.*.db
//          if (inst_while_input) pref_func.apply_prep_1(inst_while_input,true);
          else pref_func.apply_prep_save(null,true); // apply_prep_1 calls apply_prep_save, and this conflicts with native script at test_mode['208']
        }
        window.addEventListener('beforeunload', set_val_beforeunload, false);
//        function eventfunc_factory(tgt, check_func){ // dynamic approach, but this may conflict with native.
//          return function(e){
//            var et = e.target;
//            do {
//              var name = et.getAttribute('name');
//              if (name && check_func(et, name)) tgt.call(this, {target:et, currentTarget:et, __proto__:e}); // emulation of static approach
//              et = et.parentNode;
//            } while (et!==e.currentTarget);
//          };
//        }
//        function end_event(e){e.stopPropagation();} // e.preventDefault();}
        return function(pn,tgt, func_obj2, func_obj){
          var tgt_wrapped = function(e, propagated){inst_while_input=null;tgt.call(this,e, propagated);};
//          pn.oninput  = eventfunc_factory(tgt_wrapped, function(et, name){
//            var r = (et.tagName==='INPUT' || et.tagName==='TEXTAREA' || et.tagName==='SELECT') &&  (func_obj2 && func_obj2[name]);
//            if (!r && ((et.tagName==='INPUT' && et.type==='text') || et.tagName==='TEXTAREA')) inst_while_input = et; // prep_beforeunload
//            return r;});
//          pn.onchange = eventfunc_factory(tgt_wrapped, function(et, name){
//            inst_while_input = null;
//            return  (et.tagName==='INPUT' || et.tagName==='TEXTAREA' || et.tagName==='SELECT') && !(func_obj2 && func_obj2[name]) ||
//              (et.tagName==='SPAN' || et.tagName==='DIV') && name==='SUB';});
//          pn.onclick  = eventfunc_factory(tgt, function(et, name){
//            return et.tagName==='BUTTON' || et.tagName==='A' || (et.tagName==='SPAN' || et.tagName==='DIV') && name!=='SUB';});
          var fm = pn.getElementsByTagName('*'); // working code, static approach
          if (fm.length==0) fm = [pn];
          for (var i=0;i<fm.length;i++) {
            var name = fm[i].getAttribute('name') || fm[i].dataset && fm[i].dataset.name; // svg doesn't have dataset.
            if (!name) continue; // for span // if (!fm[i].name) continue;
            var tagName = fm[i].tagName;
            if (tagName==='INPUT' || tagName==='TEXTAREA' || tagName==='SELECT') {
              if (func_obj2 && func_obj2[fm[i].name]) fm[i].oninput = tgt_wrapped;
//              if (func_obj2 && (func_obj2[fm[i].name] || func_obj2[fm[i].name+'/t'])) fm[i].oninput = tgt_wrapped;
              else {
                fm[i].onchange = tgt_wrapped;
                if ((tagName==='INPUT' && fm[i].type==='text') || tagName==='TEXTAREA') fm[i].oninput = prep_beforeunload;
              }
            } else if (tagName==='BUTTON') fm[i].onclick = tgt_wrapped; // OK.
            else if (tagName==='A') {
              /*if (func_obj[name])*/ fm[i].onclick = tgt;
//              if (name==='SHOW' || name==='SHOWALL' || name==='HIDEALL' || name==='SHOW2' || name==='SHOW3' || name==='HIDE3' || name==='SHOW5' || name==='HIDE5') fm[i].onclick = tgt;
            } else if (tagName==='SPAN' || tagName==='DIV') {
//              if (name==='NEXT') fm[i].onclick = tgt;
              if (name==='SUB') fm[i].onchange = tgt; // called twice times when INPUT in SUB is changed.
              if (name==='SHOW2_I' || name==='SHOW4' || name==='HIDE4') fm[i].onclick = tgt;
              if (fm[i].dataset.name) fm[i].onclick = tgt;
            }
          }
//          pn.oninput = end_event;
//          pn.onchange = end_event;
//          pn.onclick = end_event; // conflict with 'tb_clicks' which is set outer than this.
          return tgt_wrapped;
        };
      })(),
//      remove_onchange: function(pn){this.set_event_target(pn,null);},
//      invoke_onchange: function(pn){
//        var evt = document.createEvent('UIEvents');
//        evt.initUIEvent('change', false, true, window, 1);
//        pn.dispatchEvent(evt);
//      },
      str2obj: function(key) {
//        var tgt = key+'_obj';
        var tgt = pref[key.replace(/str$/,'obj')] = [];
        var bg_str = site.board + ((site.whereami==='thread')? site.no : '');
        bg_str += ',' + bg_str + '\n' + pref[key] + '\n';
        if (pref.catalog.board.recommendation) {
//          var blotter = document.getElementsByClassName('blotter')[0];
//          if (blotter) {
//            or_str = document.getElementsByClassName('blotter')[0][brwsr.innerText];
//            var kwd = 'Recommendation: ';
//            var idx = or_str.indexOf(kwd);
//            if (idx!=-1) {
//              var or = or_str.substr(idx+kwd.length);
//              pref_func.catalog_board_list_str_or = or;
//              lines.push(or);
//            }
//          }
          var or = site2[site.nickname].get_owners_recommendation();
          pref_func.catalog_board_list_str_or = or;
//          lines.push(or);
          bg_str = bg_str + or + '\n';
        }
//        if (pref.catalog.board.board_tags && pref_func.catalog_board_list_str_bt!=='') lines.push(pref_func.catalog_board_list_str_bt);
//        if (pref.catalog.board.board_tags_same && pref_func.catalog_board_list_str_bt_same!=='') lines.push(pref_func.catalog_board_list_str_bt_same);
        if (pref.catalog.board.board_tags && pref_func.catalog_board_list_str_bt!=='') bg_str = bg_str + pref_func.catalog_board_list_str_bt + '\n';
        if (pref.catalog.board.board_tags_same && pref_func.catalog_board_list_str_bt_same!=='') bg_str = bg_str + pref_func.catalog_board_list_str_bt_same + '\n';
        var lines = bg_str.split('\n');
        for (var i=0;i<lines.length;i++) {
          var fields = lines[i].replace(/\s*\/\/.*/,'').replace(/, +/g,',').split(',');
          var j=0;
          while (j<fields.length && fields[j]=='') j++;
          if (fields[j]) {
            var tn0 = {key:fields[j]};
            Object.defineProperty(tn0,'ts',{value:{}, configurable:true, writable:true});
            var tn = tgt[tgt.length] = [tn0];
            while (++j<fields.length) if (fields[j]!==''){
              var fj = fields[j].replace(/\s/g,'');
              if (fj.search(/^[!\^]?#/)!==-1) {
                var tn0t = tn0.tags || (tn0.tags=[]);
                tn0t[tn0t.length] = fj;
              } else if (fj.search(/^!/)!==-1) {
                var tn0c = tn0.cmds || (tn0.cmds=[]);
                tn0c[tn0c.length] = fj.slice(1);
              } else tn[tn.length] = {key:fj.replace(/[\*\^!].*/,'')}; // replace is for safe, old spec allows this. (revised 2022.08.31, remove this later.)
//              } else {
//                var o = tn[tn.length] = {key:fj.replace(/[\*\^!].*/,'')};
//                if (fj.search(/\*/)!=-1) o.max_page = parseInt(fj.replace(/[^\*\^!]*[\*\^!]/,'').replace(/[\^!].*/,''),10); // this is implemented in make_refresh_list.
//                if (fj.search(/\^/)!=-1) o.style    = fields[j].replace(/[^\*\^!]*[\*\^!]/,'').replace(/[\*!].*/,''); // style may contain spaces. // this is NOT implemented.
////                if (fj.search(/\!/)!=-1) o['search'] = fj.replace(/[^\*\^!]*[\*\^!]/,'').replace(/[\*\^].*/,'');
////                o.key = fj.replace(/[\*%!].*/,''); // working code..., but '%' is used in URL...
////                if (fj.search(/\*/)!=-1) o['num'   ] = fj.replace(/[^\*%!]*[\*%!]/,'').replace(/[%!].*/,'');
////                if (fj.search(/\%/)!=-1) o['style' ] = fj.replace(/[^\*%!]*[\*%!]/,'').replace(/[\*!].*/,'');
////                if (fj.search(/\!/)!=-1) o['search'] = fj.replace(/[^\*%!]*[\*%!]/,'').replace(/[\*%].*/,'');
//              }
            }
          }
        }
        if (pref.catalog.board.all_boards) tgt[tgt.length] = [{key:'ALL'},{key:site.nickname}];
//        for (var i=0;i<tgt.length;i++) { // working code, but this is NOT used since safe307 probably. See insert_thread_from_page
//          for (var j=1;j<tgt[i].length;j++) {
//            var dm = tgt[i][j].key.replace(/\/.*/,'');
//            if (dm=='') dm=site.nickname;
//            var bd = '/'+ tgt[i][j].key.replace(/[^\/]*\//,'').replace(/\/.*/,'') +'/';
//            if (j==1) {
//              tgt[i][0]['domain']= dm;
//              tgt[i][0]['board' ]= bd;
//            } else {
//              if (tgt[i][0]['domain']!=dm) tgt[i][0]['domain']=null;
//              if (tgt[i][0]['board' ]!=bd) tgt[i][0]['board' ]=null;
//            }
//          }
//        }
        for (var i=0;i<tgt.length;i++)
          for (var j=1;j<tgt[i].length;j++) {
            var dbt = comf.name2domainboardthread(tgt[i][j].key,false);
            tgt[i][j].key = comf.name2domainboardthread(tgt[i][j].key,true).join(''); // patch
            if (dbt[1]==='' && dbt[2]==='') {
              if (!tgt[i][0].domains_for_all_boards) tgt[i][0].domains_for_all_boards = [];
              tgt[i][0].domains_for_all_boards[tgt[i][0].domains_for_all_boards.length] = dbt[0];
              tgt[i].splice(j--,1);
            }
          }
      },
      str2obj_replace0: function(boards) {
        pref.catalog_board_list_obj[0].splice(1,pref.catalog_board_list_obj[0].length-1);
        for (var i=0;i<boards.length;i++) pref.catalog_board_list_obj[0].push({key: comf.name2domainboardthread(boards[i],true).join('')});
      },
      get_tgt: function(name, parent, exact){
        if (!parent) parent = (name[0]==='#')? liveTag.tags : pref; // this might be redundant anymore, because tags are drawn on demand.
        var tgts = name.split('.');
        for (var j=0;j<tgts.length;j++)
          if (!exact || parent && parent[tgts[j]]!==undefined) {
            if (j!=tgts.length-1) parent = parent[tgts[j]];
            else return [parent,tgts[j]];
          } else return null;
      },
      str2obj2: (function(){
        function func_org(str, tgt){
          var name = str.replace(/[@;].*/,'').replace(/^\s*/,'');
          func_org_1(name||'DEFAULT', str.slice(name.length), tgt);
//          var fields = str.slice(name.length).replace(/@/g,';@').split(';'); // better than match(/(^.|[@;])[^@;]*)/g);
////          var idx = str.search(/@;/);
////          var name = idx>0? str.slice(0,idx) : 'DEFAULT';
////          var fields = str.substring(idx).replace(/@/g,';@').replace(/!/g,';').split(';').filter(function(v){return v;}); // better than match(/(^.|[@;])[^@;]*)/g);
//          if (!name) name = 'DEFAULT';
        }
        function func_org_1(name, str, tgt){
          var fields = str.replace(/@/g,';@').split(';'); // better than match(/(^.|[@;])[^@;]*)/g);
          var obj = tgt[name] || (tgt[name] = {});
          for (var i=0;i<fields.length;i++) { // if (fields[i]) set_val(obj, fields[i]);
            var val = fields[i];
            if (!val) continue; 
//        }
//        function set_val(obj, val){
            if (val[0]==='@') {
              var val_1 = val.slice(1);
              obj.time = val_1.search(/^\d+$/)===0? parseInt(val_1,10) : brwsr.Date_parse(val_1);
  //          if (val[0]==='@') obj.time = (val[1]==0)? 0 : brwsr.Date_parse(val.slice(1)); // Date.parse(0) returns 2000/1/1 0:00. '==0' is intended, 0 is string in this case.
            } else {
              var fs = val.split(':');
              if (val.search(/^[A-Z]/)===0) {
                if (fs[1]==='null' || fs[1]==='') {if (obj.cmd) delete obj.cmd[fs[0]];}
                else (obj.cmd  ||(obj.cmd  ={}))[fs[0]] = (fs.length===1 || fs[1]==='true')? true : (fs[1]==='false')? false : fs[1];
              } else (obj.style||(obj.style={}))[fs[0]] = fs[1];
//              var tgt = val.search(/^[A-Z]/)===0? 'cmd' : 'style'; // working code
//              var fs = val.split(':');
//              if (!obj[tgt]) obj[tgt] = {};
//              if (tgt==='cmd' && (fs[1]==='null' || fs[1]==='')) delete obj[tgt][fs[0]];
//              else obj[tgt][fs[0]] = (tgt!=='cmd')? fs[1] : (fs.length===1 || fs[1]==='true')? true : (fs[1]==='false')? false : fs[1];
            }
          }
        }
//        function set_vals(tgt,key,vals_in,completion){ // working code.
//          if (vals_in) {
//            if (!tgt[key]) tgt[key] = {};
//            var vals = vals_in[0].substr(1).split(';');
//            for (var j=0;j<vals.length;j++) {
//              var val = vals[j].split(':');
//              if (completion) {
//                if (val.length===1 || val[1]==='true') val[1] = true;
//                else if (val[1]==='false') val[1] = false;
//              }
//              tgt[key][val[0]] = val[1];
//            }
//          }
//        }
//        function func_org(field, tgt){
//          var name = field.replace(/[@\^!].*/,'') || 'DEFAULT';
//          if (tgt[name]===undefined) tgt[name] = {};
////          var time = field.match(/@[^\^!]*/);
////          if (time) tgt[name].time = Date.parse(time[0].replace(/@/,''));
//          var time = field.match(/@([^\^!]*)/);
//          if (time) tgt[name].time = (time[1]==0)? 0 : brwsr.Date_parse(time[1]); // Date.parse(0) returns 2000/1/1 0:00. '==0' is intended, 0 is string in this case.
//          set_vals(tgt[name],'style',field.match(/\^[^@\^!]*/g));
//          set_vals(tgt[name],'cmd'  ,field.match(/![^@\^!]*/g),true);
//        }
        function func_rev(field, tgt){
          func_rev_1((field[0]==='+'? field.slice(1):field).replace(/[@;:].*/,'') || 'DEFAULT', tgt);
//          if (field[0]==='+') field = field.slice(1);
//          var name = field.replace(/[@;:].*/,'') || 'DEFAULT';
        }
        function func_rev_1(name, tgt){
          var idx = name.indexOf('/');
          var idxl = name.lastIndexOf('/');
          if (idx>0 && idx!==idxl && idxl!=name.length-1) { // domain/board/thread
            var db = name.slice(0,idxl+1);
            if (!tgt[db]) tgt[db] = {};
            tgt[db][name.slice(idxl+1)] = null;
          }
        }
        function func_for_val(field, tgt){
          field = field.replace(/\s*/g,'');
          var idx = field.indexOf(':');
          var name = (idx>=1)? field.substr(0,idx) : 'DEFAULT';
          tgt[name] = field.substr(idx+1);
        }
        var obj2proto = {
          _revise: function(name, str_add, str_removed, force){
            if (!pref.test_mode['190']) this._reviseObj(name, str_add, force);
            return fmt4str2_2(str_removed, (str_add || force) && (name+str_add));
          },
          _reviseObj: function(name, str, force){
//            if (str) str = str.replace(/\s*\/\/.*/mg,'').replace(/\n/g,',').replace(/\s*$/,'').split(/\s*,\s*/)[0]; // for safety
            var rm = !str && !force;
            var exist = (name in this);
            if (exist) {
              if (rm) obj3_rm(this[':REV'], name);
              delete this[name]; // force remake for not accumulating
            }
            if (rm) return;
            func_org_1(name, str, this);
            if (!exist) func_rev_1(name,this[':REV']);
          },
        };
        var obj2proto_merge = {
          _revise: function(dst, src, no_insert,    str, old){ // dst and src must be fullname
            var darr = this[dst];
            var sarr = this[src];
//if (!pref.test_mode['192']) { // working code
////            if (!old) old = {__proto__:this}; // emulation of _old, WILL BE REMOVED
////            if (darr) {
////              var dold = darr.slice();
////              for (var i=0;i<darr.length;i++) if (!old.hasOwnProperty(darr[i])) old[darr[i]] = dold;
////            } else if (dst && !old.hasOwnProperty(dst)) old[dst] = undefined;
////            if (sarr) {
////              var sold = sarr.slice();
////              for (var i=0;i<sarr.length;i++) if (!old.hasOwnProperty(sarr[i])) old[sarr[i]] = sold;
////            } else if (!old.hasOwnProperty(src)) old[src] = undefined;
//            this._str = str;
//}
            var tgts;
//            var sarr_old = sarr && sarr.slice();
            var obj3 = this[':REV'];
            var str = add_to_list_2(dst, src, this._str, no_insert);
            this._str = (sarr && (!dst || darr || no_insert))? rm1(sarr, src, obj3, this, str) : str;
//            if (sarr && (!dst || darr || no_insert)) {
//              sarr.splice(sarr.indexOf(src),1);
//              if (sarr && sarr.length==1) if (obj3_rm(obj3,sarr[0],this)) str = add_to_list_2(null, sarr[0], str);
//            }
            if (dst) {
              if (!sarr) func_rev(src,obj3);
              if (darr) {darr.splice(darr.indexOf(dst)+1,0,src); this[src] = darr; tgts = [src];} // members of sarr_old may bump down
              else {
                func_rev(dst,obj3);
                if (sarr && !no_insert) {sarr.splice(sarr.indexOf(src),0,dst); this[dst] = sarr; tgts = [dst];} // members of darr may bump down
                else {this[dst] = this[src] = tgts = [dst,src];} // member of both sarr_old and darr may bump down
              }
            } else {if (sarr) obj3_rm(obj3,src,this); tgts = [src];} // member of sarr_old may bump down
            return /*(!pref.test_mode['192'])? {str:this._str, tgts:tgts, old:old} :*/ tgts;
          },
          _rm: function(name,obj3_db,no,str){
            str = rm1(this[name], name, this[':REV'], this, str);
            return rm_merge.call(this,name,obj3_db,no,str);
          },
          get _str(){return pref.proto.merge_list_str;},
          set _str(s){pref.proto.merge_list_str = s;
                      setPnValue('proto.merge_list_str',s);
                     },
        };
        var setPnValue = (function(){ // for reducing call of getElementsByTagName()
          var reqs = {};
          var go = DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(function(){
            for (var i in reqs) {
              var pn = pref_func.settings.pn13 && pref_func.settings.pn13.getElementsByTagName('TEXTAREA')[i];
              if (pn) pn.value = reqs[i];
            }
            reqs = {};
          },100));
          return function(name, value){reqs[name] = value; go();};})();
        function rm1(arr,name,obj3,obj2,str){
          if (arr.length>1) {
            arr.splice(arr.indexOf(name),1);
            if (arr.length===1) if (obj3_rm(obj3,arr[0],obj2)) // checking if fullname, and obj3_db can't be used since arr[0] may be in other boards.
              str = add_to_list_2(null, arr[0], str);
          }
          return str;
        }
        function rm_merge(name,obj3_db,no,str){
          delete this[name];
          delete obj3_db[no];
          var key = name.replace(/\+/,'\\+')+'([\\^@;:][^,\n]*)*'; // see 'triage_exe'
          return str.replace(new RegExp('(^|,)'+key+'([,\n]+\\s*\\+*|$|\\+)','mg'),'$1').replace(new RegExp('\\+\\s*'+key+'(,|\n|$|\\s*\\+)','mg'),'$2');
        }
        var obj2proto_merge_lv = {
          _revise: function(name, lv){ // name must be fullname
            var str = this._str;
            var changed = {};
            if (typeof(name)==='string') str = this._reviseObj(name, lv, str, changed);
            else for (var i=0;i<name.length;i++) str = this._reviseObj(name[i], lv, str, changed);
            this._str = str;
            return changed;
          },
          _reviseObj: function(name, lv, str, changed){ // name must be fullname
            var exist = (name in this);
            var lv_old = this[name] || null;
            var lv_str = lv!==null? lv.toString() : null; // can accept lv==='' as end mark for merge_op.auto
            if ((lv_str||null)!==lv_old) changed[name] = lv_old;
            var str_add = lv!==null && (name+':'+lv);
            if (str_add) {
              this[name] = lv_str; // stores string value for '0'
              if (!exist) func_rev_1(name,this[':REV']);
            } else if (exist) obj3_rm(this[':REV'], name, this);
            return fmt4str2_2(str.replace(new RegExp('(^|,)\\s*'+name+'(:[^,\n]*)*(,|\n|$)','mg'),''), str_add);
          },
//          _revise: function(name, lv, rm, str){ // name must be fullname
//            var idxl = name.lastIndexOf('/');
//            var db = name.slice(0,idxl+1);
//            var obj3_db = this[':REV'][db];
//            var no = name.slice(idxl+1);
//            if (rm) {
//              delete this[name];
//              delete obj3_db[no];
//            } else {
//              this[name] = lv;
//              if (!obj3_db) obj3_db = this[':REV'][db] = {};
//              obj3_db[no] = null;
//            }
//            return pref_func.fmt4str2_2(str.replace(new RegExp('(^|,)\\s*'+name+'(:[^,\n]*)*(,|\n|$)','mg'),''), rm? '' : name+':'+lv);
//          },
          _rm: rm_merge,
          get _str(){return pref.proto.merge_lv_str;},
          set _str(s){pref.proto.merge_lv_str = s;
                      setPnValue('proto.merge_lv_str',s);
                     },
        };
        function obj3_rm(obj3, name, obj2){
          var idx = name.indexOf('/');
          var idxl = name.lastIndexOf('/');
          if (idx>0 && idx!==idxl && idxl!=name.length-1) { // domain/board/thread
            var db = name.slice(0,idxl+1);
            if (obj3[db]) delete obj3[db][name.slice(idxl+1)];
            else console.log('Error: an inconsistency was found in str2obj2.obj3_rm: '+name);
            if (obj2) delete obj2[name];
            return true;
          }
        }
        function add_to_list_2(tgt, name, str, no_insert){
          var dst = tgt && existIn(str,tgt);
          var src = existIn(str,name);
          if (src && (!tgt || dst || no_insert)) {
            str = src[1]==='+'? str.slice(0,src.index)              +str.slice(src.index+src[0].length) // eliminates '+' preferentially
                              : str.slice(0,src.index+src[1].length)+str.slice(src.index+src[0].length+src[3].length); // remove
            var len_erased = src[0].length + (src[1]==='+'? 0 : -src[1].length+src[3].length);
          }
          if (tgt) { // if (dst || tgt===null) {
            if (dst) { // if (tgt!==null) {
              var idx = (src && dst.index>src.index? dst.index - len_erased : dst.index) + dst[0].length;
              str = str.slice(0,idx)+'+'+name+str.slice(idx); // add
            } else if (src && !no_insert) str = str.slice(0,src.index+src[1].length)+tgt+'+'+str.slice(src.index+src[1].length); // insert
            else var str_add = tgt+'+'+name; // add to last
          }
          return fmt4str2_2(str, str_add);
        }
        function existIn(str, key_in){
          var key = key_in.replace(/\+/,'\\+');
          return new RegExp('(^|,|\\+)\\s*'+key+'(\s*)(?=(,|\n|$|\\+))','mg').exec(str);
//          return new RegExp('(^|,|\\+)\\s*'+key+'([^,\n+]*)(?=(,|\n|$|\\+))','mg').exec(str); // (?<=) requires chrome 62 // BUG, key isn't terminated
        }
        function fmt4str2_2(str, str_add){
          str = str_add? (str + (str.length===0 || str[str.length-1]==='\n'? '':'\n')+str_add+'\n') : str; // add to last, '\n' for faster execution(no replacement)
          if (pref.debug_mode['40'] && pref_func.fmt4str2(str)!==str) {
            console.log('ERROR: fmt4str2_2');
            console.trace();
          }
          return str;
        }
        pref_func.add_to_list_2 = add_to_list_2;
        pref_func.fmt4str2_2 = fmt4str2_2;
        return function(str, src, obj2_only, func){
          var tgt = src==='merge'? {__proto__:obj2proto_merge} : src==='merge_lv'? {__proto__:obj2proto_merge_lv} : src || {__proto__:obj2proto};
          var fields = str.replace(/\s*\/\/.*/mg,'').replace(/\n/g,',').replace(/\s*$/,'').split(/\s*,\s*/);
          var f2 = (func==='val')? func_for_val : func || func_org;
          for (var i=0;i<fields.length;i++) if (fields[i]) f2(fields[i],tgt);
//          if (!src) parent[key] = tgt;
          if (obj2_only) return tgt;
          var obj3 = {};
//          if (!pref.test_mode['134']) {
          for (var i=0;i<fields.length;i++) if (fields[i]) func_rev(fields[i],obj3);
          tgt[':REV'] = obj3;
//          } else {
//            for (var i in tgt) obj3[i.replace(/\/[0-9]+$/,'/')] = true;
//            parent[key.slice(0,-1)+'3'] = obj3;
//          }
//          var key3 = key.replace(/_obj2/,'_obj3');
//          parent[key3] = {};
//          var tgt3 = parent[key3];
//          for (var i in tgt) tgt3[i.replace(/\/[0-9]+$/,'/')] = true;
          return tgt;
        };
      })(),
      fmt4str2: function(str){
        return str.replace(/,,+/g,',').replace(/^,/g,'').replace(/\n,/g,'\n').replace(/\n\n+/g,'\n').replace(/^\n/,'');
      },
////      str2obj2: function(parent,key,str){ // working code.
////        parent[key] = {};
////        tgt = parent[key];
////        var fields = str.replace(/\/\/.*/mg,'').replace(/\n/g,',').split(',');
////        for (var i=0;i<fields.length;i++) {
////          if (fields[i]=='') continue;
////          var name  = fields[i].replace(/[@%].*/,'');
////          if (name=='') name = 'DEFAULT';
////          var attr  = fields[i].match(/[@%].*/);
////          var time  = (attr!=null)? attr[0].replace(/%[^@]*(@|$)/,'').replace(/@/,'') : null;
////          var style = (attr!=null)? attr[0].replace(/@[^%]*(%|$)/,'').replace(/%/,'') : null;
////          if (tgt[name]===undefined) tgt[name] = {};
////          if (time ) tgt[name]['time']  = Date.parse(time);
//////          if (style) tgt[name]['style'] = style;
////          if (style) {
////            if (!tgt[name].style) tgt[name].style = {};
////            var styles = style.split(';');
////            for (var j=0;j<styles.length;j++) {
////              var stl = styles[j].split(':');
////              tgt[name].style[stl[0]] = stl[1];
////            }
//////            tgt[name]['style'] = true;
//////            var styles = style.split(';');
//////            for (var j=0;j<styles.length;j++) {
//////              var stl = styles[j].split(':');
//////              tgt[name]['style.'+stl[0]] = stl[1];
//////            }
////          }
////        }
////        var key3 = key.replace(/_obj2/,'_obj3');
////        parent[key3] = {};
////        var tgt3 = parent[key3];
////        for (var i in tgt) tgt3[i.replace(/\/[0-9]+$/,'/')] = true;
////      },
//      str2obj4: function(parent,key,str) {
//        parent[key] = this.style2obj(str);
//      },
//      style2obj: function(str) {
//        var fields  = str.split(';');
//        for (var i=fields.length-1;i>=0;i--) {
//          if (fields[i]) {
//            var coms = fields[i].replace(/\s/g,'').split(':');
//            fields[i] = '"'+coms[0]+'":"'+coms[1]+'"';
//          } else fields.splice(i,1);
//        }
//        return JSON.parse('{' + fields.join(',') + '}');
//      },
      str2obj5: function(parent,key,str_in, flag_g){
        parent[key] = {};
        var tgt = parent[key];
        var fields = str_in.replace(/\s*\/\/.*/mg,'').replace(/\n/g,',').split(',');
        for (var i=0;i<fields.length;i++) {
          var fd = fields[i].replace(/\s*:\s*/,':').replace(/\s+$/,'');
          if (fd=='') continue;
          var idx = fd.indexOf(':'); // tags never contains ':'
          var name = (idx!=-1)? fd.substr(0,idx) : 'DEFAULT';
          var idx2;
          var rx = fd[idx+1]==='/' && (idx2 = fd.lastIndexOf('/'), idx2>idx+2);
          var str = (rx)? fd.slice(idx+2,idx2) : fd.slice(idx+1);
          var flags = (flag_g? 'g':'') + (rx? fd.slice(idx2+1).replace(/g/,'') : '');
          if (tgt[name]===undefined) tgt[name] = [];
          try {
            tgt[name][tgt[name].length] = new RegExp((rx)? str : this.str2rstr(str, flag_g), flags);
          } catch(e) {
            console.log('ERROR at str2obj5 in string of: '+fields[i]);
          }
//          tgt[name][tgt[name].length] = (fields[i][idx+1]==='/')? new RegExp(fields[i].substr(idx+2,fields[i].lastIndexOf('/')-idx-2),fields[i].substr(fields[i].lastIndexOf('/')+1)) :
//                                        (key==='ex_list_obj5')? new RegExp(fields[i].substr(idx+1).replace(/\*/g,'.*')) :
//                                                                new RegExp(fields[i].substr(idx+1).replace(/\*/g,'\\S*(\\s|$)'),'g');
//          tgt[name][tgt[name].length] = new RegExp('#'+(fields[i][idx]==='/')? fields[i].substr(idx+1) :
//                                                                               fields[i].substr(idx+1).replace(/\*/,'.*'));
        }
      },
      str2rstr: function(str,flag_g){
        return str.replace(/[\.\(\)\[\]\+\^\$\{\}]/g,'\\$&').replace(/\*/g,flag_g?'\\S*(\\s|\\n|$)':'.*').replace(/\?/g,'.');
      },
//      str2obj8: (function(){ // not tested
//        var idx;
//        var proto = {re:false, ci:false, match:0, sentence:false, op:true, post:false,
//                     sub:true, name:true, trip:false, com:true, file:false, meta:false, flag:false, id:false,    rexps:null};
//        return {
//          str2obj: function(parent,key,str_pref){
//            parent[key] = {};
//            tgt = parent[key];
//            var fields = str_pref.replace(/\s*\/\/.*/mg,'').replace(/\n/g,',').split(',');
//            var flag_g = key==='rm_list_obj5';
//            var json = key==='auto_list_obj5';
//            for (var i=0;i<fields.length;i++) {
//              var fd = fields[i].replace(/\s*:\s*/,':').replace(/\s+$/,'');
//              if (fd=='') continue;
//              var idx = fd.indexOf(':'); // tags never contains ':'
//              try {
//                var val = json? func_json(fd) : func_str(fd, flag_g); // must be preceded, this may change idx
//              } catch(e) {
//                console.log('ERROR at str2obj5 in string of: '+fd);
//              }
//              var name = (idx!=-1)? fd.substr(0,idx) : 'DEFAULT';
//              if (tgt[name]===undefined) tgt[name] = [];
//              tgt[name][tgt[name].length] = val;
//            }
//          },
//          obj2str: function(obj){
//            var tgt = comf.deep_copy(obj);
//            pref_func.obj_elim_the_same(tgt,proto);
//            return JSON.stringify(tgt);
//          }
//        };
//        function func_str(fd, flag_g){
//          var idx2;
//          var rx = fd[idx+1]==='/' && (idx2 = fd.lastIndexOf('/'), idx2>idx+2);
//          var str = (rx)? fd.slice(idx+2,idx2) : fd.slice(idx+1);
//          var flags = (flag_g? 'g':'') + (rx? fd.slice(idx2+1).replace(/g/g,'') : '');
//          return new RegExp((rx)? str : this.str2rstr(str, flag_g), flags);
//        }
//        function func_json(fd){
//          var idx2 = fd.indexOf('{');
//          if (idx2<idx) idx = -1;
//          var obj = JSON.parse(fd.slice(idx+1));
//          obj.__proto__ = proto;
//          obj.rexps = comf.kwd_prep_regexp(obj);
//          return obj;
//        }
//      })(),
      str2obj7: (function(){
        var proto = {use:true, str:'', re:false, ci:true, match:0, sentence:false, ew:false, op:true, post:false,
                     sub:true, name:true, trip:false, com:true, file:false, meta:false, flag:false, id:false, id2:false, // rexps is not stringified.
                     cmds:'', hide:false, boards:null, style:''};
        var proto_post = {post:true, __proto__:proto};
        function prep(obj, prefix, tgt, proto){
          obj.__proto__ = proto;
          obj.rexps = comf.kwd_prep_regexp(obj);
          var name = prefix.trim().replace(/\s*[;:]\s*/,'') || 'DEFAULT';
          if (tgt[name]===undefined) tgt[name] = [obj];
          else tgt[name][tgt[name].length] = obj;
        }
        return {
          s2o: function(tgt, str_in, fm_name){
            pref_func.parseJSONCs(str_in, prep, fm_name, true, tgt, (fm_name.indexOf('postFilter')!=-1)? proto_post : proto);
            return tgt;
          },
//          s2o: function(tgt, str_in, fm_name){ // working code
//            var fields = str_in.split(/\s*\n\s*/); // no comments are allowed
//            for (var i=0;i<fields.length;i++) {
//              var fd = fields[i];
//              var idx = fd.indexOf('{');
//              if (idx===-1) continue;
//              var name = fd.substr(0,idx).trim().replace(/\s*:\s*/,'') || 'DEFAULT';
//              try {
//                var obj = JSON.parse(fd.slice(idx).trim());
//                obj.__proto__ = (fm_name==='postFilter.str')? proto_post : proto;
//                obj.rexps = comf.kwd_prep_regexp(obj);
//                if (tgt[name]===undefined) tgt[name] = [obj];
//                else tgt[name][tgt[name].length] = obj;
//              } catch(e) {
//                console.log('ERROR at str2obj7 in string of: '+fields[i]);
//              }
//            }
//            return tgt;
//          },
          o2s: function(obj, from_post){
            var tgt = {};
            var p = from_post? proto_post : proto;
            for (var i in p) if (p[i]!=obj[i]) tgt[i] = obj[i];
            delete tgt['use'];
            if (obj.sentence && obj.str.indexOf(' ')===-1) delete tgt['sentence'];
            return JSON.stringify(tgt);
          },
        };
      })(),
      merge_obj5: function(name,obj,val){
        return this.merge_obj5_all(name,obj,val,this.merge_obj5_1);
      },
      merge_obj5a: function(th,obj,val){
        return this.merge_obj5_all(th.key,obj,val,this.merge_obj5_1a);
      },
      merge_obj5a_sc: function(name,obj,val){ // short cut logic.
        if (':F' in obj) return obj[name];
//        if (!obj) return val;
        var dbt = comf.fullname2dbt(name);
        return obj[name] ||
               obj[dbt[1]+dbt[2]] || // board+thread
               obj[dbt[0]+dbt[2]] || // domain+thread
               obj[dbt[2]] || // thread
               obj[dbt[0]+dbt[1]] || // domain+board
               obj[dbt[1]] || // board
               obj[dbt[0]] || // domain
               obj['DEFAULT'] || val;
      },
      merge_obj5_all: function(name,obj,val,func){
        var dbt = comf.fullname2dbt(name);
        val = func(val,obj,'DEFAULT');
        val = func(val,obj,dbt[0]); // domain
        val = func(val,obj,dbt[1]); // board
        val = func(val,obj,dbt[0]+dbt[1]); // domain+board
        val = func(val,obj,dbt[2]); // thread
        val = func(val,obj,dbt[0]+dbt[2]); // domain+thread
        val = func(val,obj,dbt[1]+dbt[2]); // board+thread
        val = func(val,obj,name); // CAUTION, direct access is used in some points, search 'CAUTION' in comments.
        return val;
      },
      merge_obj5_1: function(val,obj,key){
        if (obj[key]) {
          var obj_key = obj[key];
//          if (Array.isArray(obj_key)) return obj_key.concat(val || []);
          if (!val) val = {};
//          if (val.hit!==undefined) val.hit = true;
          for (var i in obj_key)
            if (typeof(obj_key[i])!=='object') val[i] = obj_key[i];
            else {
              if (val[i]===undefined) val[i]={};
              for (var j in obj_key[i]) val[i][j] = obj_key[i][j]; // 2nd level.
            }
        }
        return val;
      },
      merge_obj5_1a: function(val,obj,key){
        var tgt = obj[key];
        return tgt? (val? val.concat(tgt) : tgt) : val; // add after for postFilter
//        return tgt? (val? tgt.concat(val) : tgt) : val;
      },
      str2obj6: function(parent,key,val,name){
        var obj_hier  = pref_func.get_tgt(name, pref3);
        obj_hier[0][obj_hier[1]](obj_hier[0],key,val, parent);
      },
      obj_init_hier: function(dst, src){
        for (var i in dst) {
          if (typeof(dst[i])==='object' && src[i]) this.obj_init_hier(dst[i], src[i]); // stopped by null.
          else if (typeof(dst[i])==='function') dst[i](dst, i, src[i], src);
        }
      },
      obj_init: function(){
        pref_func.str2obj('catalog_board_list_str');
        pref.catalog['style_general_list_obj2'] = pref_func.str2obj2(pref.catalog.style_general_list_str);
        pref.catalog.board['ex_list_obj2'] = pref_func.str2obj2(pref.catalog.board.ex_list_str);
//        pref_func.str2obj4(pref.liveTag, 'style_urtm_obj4', pref.liveTag.style_urtm_str);
//        pref_func.str2obj4(pref.liveTag, 'style_ur_obj4',   pref.liveTag.style_ur_str);
//        pref_func.str2obj4(pref.liveTag, 'style_in_obj4',   pref.liveTag.style_in_str);
        pref_func.str2obj5(pref.liveTag, 'ex_list_obj5',    pref.liveTag.ex_list_str);
        pref_func.str2obj5(pref.liveTag, 'rm_list_obj5',    pref.liveTag.rm_list_str);
        pref_func.settings.onchange_funcs.archive['kwd.*'](); // archiver.event_funcs['kwd.*'](); can't use this because it doesn't exist at this time.
        this.obj_init_hier(pref3, pref);
        pref.postFilter.obj7 = pref_func.str2obj7.s2o({}, pref.postFilter.str, 'postFilter.str');
      },
      diff_objs: function diff_objs(dst,src, hier_str){
        var diff = {};
        var flag = false;
        for (var i in dst) if (dst.hasOwnProperty(i) && !Object.getOwnPropertyDescriptor(dst,i).get || src.hasOwnProperty(i) && !Object.getOwnPropertyDescriptor(src,i).get) {
          if (dst[i]===null || src[i]===null) continue; // eliminates objects made by program
          else if (typeof(dst[i])==='object' && typeof(src[i])==='object') {
            var result_lower_hier = diff_objs(dst[i],src[i], (hier_str||'')+'.'+i);
            if (result_lower_hier) {
              flag = true;
              diff[i] = (Array.isArray(dst[i]) || Array.isArray(src[i]))? src[i] : result_lower_hier;
            }
          } else {
            var proto;
            if (dst[i]!==src[i] && (dst.hasOwnProperty(i) || (proto = Object.getPrototypeOf(src), !proto || proto===Object.getPrototypeOf({}) || src[i]!==proto[i])) // to minimize, uncomment this. remove i14 case, see Debug.diff
                || !dst.hasOwnProperty(i) && (proto = Object.getPrototypeOf(src), (Object.getPrototypeOf(dst)||{})[i]!==(proto||{})[i] && src[i]!==proto[i])) {
              flag = true;
              diff[i] = src[i];
            }
          }
        }
        if ((Array.isArray(dst) || Array.isArray(src)) && dst.length!==src.length) flag = true;
        return flag? diff : null;
      },
      get obj_elim_the_same(){return pref.test_mode['200']? this.obj_elim_the_same____ : this.diff_objs;},
      obj_elim_the_same____: function obj_elim_the_same(dst,src, no_copy, not_root){ // working but BUG, hit i13 case, see Debug.diff.
        var flag = false;
        for (var i in dst) if (dst.hasOwnProperty(i) && !Object.getOwnPropertyDescriptor(dst,i).get || src.hasOwnProperty(i)) {
          if (typeof(dst[i])==='object' && src[i] && typeof(src[i])==='object') { // eliminates objects made by program automatically because original object are vacant.
            if (!obj_elim_the_same(dst[i],src[i], no_copy, i.toString())) delete dst[i]; // toString() for being true always
            else {
              flag = true;
              if (!no_copy) if (Array.isArray(dst[i]) || Array.isArray(src[i])) dst[i] = src[i];
            }
          } else {
            if (dst[i]===src[i]) {
              var proto;
              if (dst.hasOwnProperty(i) || (proto = Object.getPrototypeOf(src), !proto || proto===Object.getPrototypeOf({}) || src[i]===proto[i])) delete dst[i];
            } else {
              flag = true;
              if (!no_copy) dst[i]=src[i];
            }
          }
        }
        if ((Array.isArray(dst) || Array.isArray(src)) && dst.length!==src.length) flag = false;
        return (not_root)? flag : dst;
      },
//      obj_elim_the_same: function(dst,src, not_root, no_copy){ // working code
//        return this.obj_elim_the_same_1(new Set(), dst,src, not_root, no_copy);
//      },
//      obj_elim_the_same_1: function(done_list, dst,src, not_root, no_copy){
//        var flag = false;
//        for (var i in dst) {
//          if (typeof(dst[i])==='object' && src[i] && typeof(src[i])==='object') { // eliminates objects made by program automatically because original object are vacant.
//            if (!dst.hasOwnProperty(i)) continue;
//            if (done_list.has(dst[i])) continue; else done_list.add(dst[i]); // blocks getters
//            if (!pref_func.obj_elim_the_same_1(done_list, dst[i],src[i], true, no_copy)) {delete dst[i]; done_list.add(src[i]);}
//            else {
//              flag = true;
//              if (!no_copy) if (Array.isArray(dst[i]) || Array.isArray(src[i])) dst[i] = src[i];
//            }
//          } else {
//            if (dst[i]===src[i]) delete dst[i];
//            else {
//              flag = true;
//              if (!no_copy) dst[i]=src[i];
//            }
//          }
//        }
//        if ((Array.isArray(dst) || Array.isArray(src)) && dst.length!==src.length) flag = false;
//        return (not_root)? flag : dst;
//      },
      site2_json_ex_remove: function(){
        var pref_test = pref_default(true);
        delete pref_test.cli.json_out_str;
        delete pref_test.INST;
//        if (pref_test.settings) delete pref_test.settings;
//        if (pref_test.graph) delete pref_test.graph;
        var pref_diff = pref_func.obj_elim_the_same(pref_test,pref); // proto might be removed
//if (!pref.test_mode['199']) {
//        var proto = pref_default().proto;
//        ['catalog','page','thread','float'].forEach(function(v){ // 'v => {' can use 'this' instead of 'pref_func'.
//          if (!pref_func.obj_elim_the_same(pref_test[v], proto, true)) delete pref_test[v];});
//}
////        delete pref_test.page.footer;
////        delete pref_test.thread.footer;
////        delete pref_test.float.footer;
        if (pref.test_mode['201'] && Debug) Debug.diff.verify(pref_func.pref_overwrite(pref_default(),pref_diff), pref, '');
        if (pref.test_mode['198'] && Debug) Debug.diff.run_test();
        return pref_diff;
      },
      site2_json_ex: function(full, filter){
        var pref_test = this.site2_json_ex_remove();
        if (!filter) if (pref_test.filter) delete pref_test.filter;
        if (!full) if (pref_test.catalog_board_list_str) delete pref_test.catalog_board_list_str;
//        if (pref_test.cli) delete pref_test.cli.json_str;
        return '{"pref":' + (!filter? JSON.stringify(pref_test) : '{"filter":'+JSON.stringify(pref_test.filter)+'}') + '}';
//        if (!filter) pref.cli.json_str = '{"pref":' + JSON.stringify(pref_test) + '}';
//        else pref.cli.json_str = '{"pref":{"filter":' + JSON.stringify(pref_test.filter) + '}}';
      },
      pref_query: function pref_query(ref,src){
        var str = '';
        for (var i in src) {
          if (ref[i]!==undefined) {
            str += '"' + i + '":' +
                   ((typeof(src[i])==='object' && !Array.isArray(src[i]))? pref_query(ref[i],src[i]) :
                                                                           JSON.stringify(ref[i]))
                 + ',';
          }
        }
        return '{' + str.slice(0,-1) + '}';
      },
      site2_eval: function(){
//        try { 
//          if (pref.cli.eval_str!=='') eval(pref.cli.eval_str);
//        } catch (e) {
//          console.log('ERROR in overwtite strings:');
//          console.log(pref.cli.eval_str);
//          console.log(e);
//        }
      },
//      get tooltips(){return Tooltips;}, // code was moved.
      pref_samples: (site0.isStep)? null : {
        simple: {
          catalog_triage_str: 'KILL,X,',
          catalog : {style_general_list_str:'%border:4px solid #d6daf0\n%margin:4px',
//                     format : {show:{posts:true}}
                    },
        },
        backwash: {
          catalog_triage_str :
            'NONE,O,width:;height:,NONE,O,border:4px solid #ff0000;width:100px;height:100px,NONE,O,border:4px solid #00ff00;width:100px;height:100px,'+
            'NONE,O,border:4px solid #0000ff;width:100px;height:100px,NONE,O,border:4px solid #ffff00;width:100px;height:100px,'+
            'NONE,O,border:4px solid #ff00ff;width:100px;height:100px,NONE,O,border:4px solid #00ffff;width:100px;height:100px',
//          catalog_expand_at_initial : true,
          page:{click:'expand'},
//          catalog:{format: {show:  {style:  true, contents:  true, layout:  true, posts: true, fileinfo: true, images_2nd: true}}},
        },
        recommend: {
          max_threads_at_refresh : 500,
          catalog: {
            auto_load_filter: true,
            auto_save_filter: true,
            auto_save_filter_at_refresh: true,
            order: {
              sticky:'first',
//              find_sage_in_8chan: true
            },
            board: {board_tags_same: true},
            auto_update : true,
            auto_update_period : 1,
          },
          page:{auto_update:true, auto_update_period:1},
          float:{auto_update:true, auto_update_period:1},
        },
        get easy2(){return pref.easy2;},
        'easy2.presets': (function(){
          var presets = [
            {liveTag: {use:false}, virtualBoard:{scan:false}, LTfrom:0, VB:{local:0, global:0}, network:{fetch_actively:false}},
            {liveTag: {use:false}, virtualBoard:{scan:true }, LTfrom:0, VB:{local:1, global:0}, network:{fetch_actively:false}},
            {liveTag: {use:false}, virtualBoard:{scan:true }, LTfrom:0, VB:{local:1, global:1}, network:{fetch_actively:false}},
            {liveTag: {use:true }, virtualBoard:{scan:true }, LTfrom:0, VB:{local:2, global:0}, network:{fetch_actively:true }},
            {liveTag: {use:true }, virtualBoard:{scan:true }, LTfrom:0, VB:{local:2, global:1}, network:{fetch_actively:true }},
            {liveTag: {use:true }, virtualBoard:{scan:true }, LTfrom:0, VB:{local:2, global:2}, network:{fetch_actively:true }},
            {liveTag: {use:true }, virtualBoard:{scan:true }, LTfrom:1, VB:{local:2, global:0}, network:{fetch_actively:true }},
            {liveTag: {use:true }, virtualBoard:{scan:true }, LTfrom:1, VB:{local:2, global:1}, network:{fetch_actively:true }},
            {liveTag: {use:true }, virtualBoard:{scan:true }, LTfrom:1, VB:{local:2, global:2}, network:{fetch_actively:true }},
          ];
          var vb_kwd = ['none','board','thread'];
          var basics = {
            catalog:{auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true},
          };
          return function(idx){
            var easy2 = pref.easy2;
            if (idx!==undefined) pref_func.pref_overwrite(easy2, presets[idx]);
            easy2.virtualBoard.scan_domains = {};
            for (var i in pref.virtualBoard.scan_domains) easy2.virtualBoard.scan_domains[i] = vb_kwd[((i===site.nickname)? easy2.VB.local : easy2.VB.global)];
            easy2.liveTag.from = (easy2.LTfrom===0)? 'op' : 'post';
            var auto_update = {auto_update:pref.easy2.auto_update, auto_update_period:pref.easy2.auto_update_period};
            if (easy2.catalog.embed)      pref_func.pref_overwrite(easy2,{catalog:auto_update});
            if (easy2.catalog.embed_page) pref_func.pref_overwrite(easy2,{page:   auto_update});
            if (easy2.thread.embed)       pref_func.pref_overwrite(easy2,{thread: auto_update});
            if (easy2.filter.time.use) easy2.filter.time.time_str = new Date(Date.now()-easy2.filter.time.tv*3600000).toLocaleString();
//            if (easy2.filter.time_watch || easy2.filter.time_watch_creation)
//              easy2.filter.time_str = new Date(Date.now()-((easy2.filter.time_watch)?easy2.time_post:easy2.time_op)*3600000).toLocaleString();
            if (pref.easy2.basics) pref_func.pref_overwrite(easy2, basics);
            return easy2;
          };
        })(),
        'easy2.limits': (function(){
          var limits = [
            {virtualBoard:{bl:{max:100}}, scan:{max:  100, max_threads:  1000}, catalog:{max_threads_at_refresh:  500}}, // general
            {virtualBoard:{bl:{max: 50}}, scan:{max:  100, max_threads:  1500}, catalog:{max_threads_at_refresh: 1500}}, // lain, meguca
            {virtualBoard:{bl:{max:150}}, scan:{max:  100, max_threads: 15000}, catalog:{max_threads_at_refresh:15000}}, // 4chan
            {virtualBoard:{bl:{max:100}}, scan:{max:   50, max_threads: 15000}, catalog:{max_threads_at_refresh:15000}}, // 8chan, top 50
            {virtualBoard:{bl:{max:150}}, scan:{max:  100, max_threads: 30000}, catalog:{max_threads_at_refresh:15000}}, // 8chan, top 100
            {virtualBoard:{bl:{max:200}}, scan:{max:  500, max_threads:100000}, catalog:{max_threads_at_refresh:15000}}, // 8chan, top 500
            {virtualBoard:{bl:{max:200}}, scan:{max:10000, max_threads:100000}, catalog:{max_threads_at_refresh:15000}}, // 8chan, all
            {virtualBoard:{bl:{max:150}}, scan:{max:  100, max_threads: 15000}, catalog:{max_threads_at_refresh:15000}}, // KC
          ];
          return function(){
            pref_func.pref_overwrite(pref.easy2, limits[pref.easy2.limits]);
          }
        })(),
        'easy.virtualBoard_10_passive': {
          virtualBoard: {bl:{show: true, max:20}, scan: true},
          catalog: {auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true, auto_update:true, auto_update_period:10},
          page:{auto_update:true, auto_update_period:10},
          float:{auto_update:true, auto_update_period:10},
          func: function(){for (var i in pref.virtualBoard.scan_domains) pref.virtualBoard.scan_domains[i] = (i===site.nickname)? 'board' : 'none';},
        },
        'easy.virtualBoard_10': {
          virtualBoard: {bl:{show: true, max:20}, scan: true},
          catalog: {auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true, auto_update:true, auto_update_period:10},
          page:{auto_update:true, auto_update_period:10},
          float:{auto_update:true, auto_update_period:10},
          func: function(){for (var i in pref.virtualBoard.scan_domains) pref.virtualBoard.scan_domains[i] = (i===site.nickname)? 'thread' : 'none';},
        },
        'easy.virtualBoard_1': {
          virtualBoard: {bl:{show: true, max:100}, scan: true, scanDelay: 20},
          catalog: {auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true, auto_update:true, auto_update_period:1},
          page:{auto_update:true, auto_update_period:1},
          float:{auto_update:true, auto_update_period:1},
          func: function(){for (var i in pref.virtualBoard.scan_domains) pref.virtualBoard.scan_domains[i] = (i===site.nickname)? 'thread' : 'none';},
        },
        'easy.virtualBoard_8_50': {
          virtualBoard: {bl:{show: true, max:100}, scan: true, scanDelay: 20},
//          scan:{max:50},
          catalog: {auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true, auto_update:true, auto_update_period:5},
          page:{auto_update:true, auto_update_period:5},
          float:{auto_update:true, auto_update_period:5},
          func: function(){
            pref.scan.max = pref.easy.max_boards;
            for (var i in pref.virtualBoard.scan_domains) pref.virtualBoard.scan_domains[i] = (i===site.nickname)? 'thread' : 'none';
          },
        },
////        'easy.virtualBoard_8_100': {
////          catalog_auto_update : true,
////          catalog_auto_update_period : 5,
////          virtualBoard: {bl:{show: true, max:100}, scan: true, scanDelay: 20},
////          scan:{max:100},
////          catalog: {auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true},
////          func: function(){for (var i in pref.virtualBoard.scan_domains) pref.virtualBoard.scan_domains[i] = (i===site.nickname)? 'thread' : 'none';},
////        },
////        'easy.virtualBoard_8_500': {
////          catalog_auto_update : true,
////          catalog_auto_update_period : 5,
////          virtualBoard: {bl:{show: true, max:100}, scan: true, scanDelay: 20},
////          scan:{max:500},
////          catalog: {auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true},
////          func: function(){for (var i in pref.virtualBoard.scan_domains) pref.virtualBoard.scan_domains[i] = (i===site.nickname)? 'thread' : 'none';},
////        },
        'easy.virtualBoard_8_all': {
          virtualBoard: {bl:{show: true, max:100}, scan: true, scanDelay: 20},
          scan:{max:10000},
          catalog: {auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true, auto_update:true, auto_update_period:5},
          page:{auto_update:true, auto_update_period:5},
          float:{auto_update:true, auto_update_period:5},
          func: function(){for (var i in pref.virtualBoard.scan_domains) pref.virtualBoard.scan_domains[i] = (i===site.nickname)? 'thread' : 'none';},
        },
        'easy.virtualBoard_interSite': {
          virtualBoard: {bl:{show: true, max:100}, scan: true, scanDelay: 20},
          catalog: {auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true, auto_update:true, auto_update_period:10},
          page:{auto_update:true, auto_update_period:10},
          float:{auto_update:true, auto_update_period:10},
          func: function(){for (var i in pref.virtualBoard.scan_domains) pref.virtualBoard.scan_domains[i] = (i===site.nickname)? 'thread' : 'board';},
//          func: function(){for (var i in pref.virtualBoard.scan_domains) if (i!==site.nickname && pref.virtualBoard.scan_domains[i]==='none') pref.virtualBoard.scan_domains[i] = 'board';},
        },
        'easy.posts_0h': {
          filter:{time:{use:true, func:'np', quick_sel:1}},
        },
        'easy.posts_24h': {
          filter:{time:{use:true, func:'pp', get time_str(){return new Date(Date.now()-pref.easy.posts_ago*3600000).toLocaleString();}}}
        },
        'easy.threads_24h': {
          filter:{time:{use:true, func:'cc', get time_str(){return new Date(Date.now()-pref.easy.threads_ago*3600000).toLocaleString();}}}
        },
////        'easy.posts_48h': {
////          filter:{time_str:null, time_watch:true},
////          func: function(){pref_func.pref_samples['easy.posts_48h'].filter.time_str = new Date(Date.now()-48*3600000).toLocaleString();}
////        },
        'easy.embed_index': {
          catalog:{embed_page:true, auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true},
        },
        'easy.embed_index_lazy': {
          auto_watch:{watch:false},
          catalog:{embed_page:true, auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true},
          liveTag:{use:false},
          notify:{desktop:{notify:false}},
          network:{fetch_actively:false},
//          page:{scan_tag:false},
        },
        'easy.light': {
          catalog:{embed_page:true, auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true},
          filter:{time:{use:false}},
          liveTag:{use:false},
          stats:{use:false},
          network:{fetch_actively:false},
//          page:{scan_tag:false},
        },
        'easy.embed_index_infinite': {
          catalog_max_page_auto:true,
          catalog:{embed_page:true, auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true},
        },
        'easy.embed_index_backwash': {
          catalog_max_page_auto:true,
          catalog:{embed_page:true, auto_load_filter:true, auto_save_filter:true, auto_save_filter_at_refresh:true, auto_update:true, auto_update_period:0},
          page:{auto_update:true, auto_update_period:0},
          float:{auto_update:true, auto_update_period:0},
        },
        'easy.stat_activate': {
          stats:{use:true},
          virtualBoard:{scan:true, scan_domains:{}},
          liveTag:{use:true, from:'post'},
          func: function(){this.virtualBoard.scan_domains[site.nickname] = 'board';},
        },
        pn_samples : null,
        init : function(){
          if (pref_func.pref_samples.pn_samples) return;
          var html = pref_func.format_html_str('General samples:<br>'+
                     '<BTN"simple,simple"><br>'+
                     '<BTN"backwash,backwash"><br>'+
                     '<BTN"recommend,recommend for desktop/note"><br>'+
                     'Triages:<br>'+
                     '<BTN"triage.sample.simple_kill"><br>'+
                     '<BTN"triage.sample.simple"><br>'+
                     '<BTN"triage.sample.basic"><br>'+
                     '<BTN"triage.sample.basic_color"><br>'+
                     '<BTN"triage.sample.colorful"><br>'+
                     '<BTN"triage.sample.borders"><br>'+
                     '<BTN"triage.sample.samples"><br>'+
                     '<BTN"triage.sample.test"><br>');
          var onchange_funcs = { // patch
            simple: this.onclick_event,
            backwash: this.onclick_event,
            recommend: this.onclick_event,
            __proto__:pref_func.settings.onchange_funcs
          };
          cnst.make_popup(pref_func.pref_samples,'pn_samples',html,onchange_funcs);
//          pref_func.pref_samples.pn_samples = cnst.init('left:0px:tile:get:bottom:Show:tb',cnst.void_func,cnst.void_func,pref_func.pref_samples.destroy,cnst.void_func)[0];
//          var pn_smpl = pref_func.pref_samples.pn_samples;
//          pn_smpl.childNodes[1].innerHTML = '<button name="backwash">backwash</button>';
//          var buttons = pn_smpl.childNodes[1].getElementsByTagName('BUTTON');
//          for (var i=0;i<buttons.length;i++) buttons[i].onclick = pref_func.pref_samples.onclick_event;
        },
//        destroy : function() {
//          pref_func.pref_samples.pn_samples = cnst.div_destroy(pref_func.pref_samples.pn_samples, true);
//        },
        get_name: function(src,names,str){
          for (var i in src) {
            if (typeof(src[i])!=='object') names.push(str+i);
            else pref_func.pref_samples.get_name(src[i],names,str+i+'.');
          }
          return names;
        },
        onclick_event : function(e, src) {
          if (!src) src = pref_func.pref_samples[this.name || e.target.name];
          if (src.func && typeof(src.func)==='function') src.func();
          pref_func.pref_overwrite(pref,src);
//          pref_func.pref_overwrite(Tooltips.str,src);
          var names = pref_func.pref_samples.get_name(src,[],'');
          var pns = Array.prototype.slice.call(document.querySelectorAll('*[name]'));
          for (var i=pns.length-1;i>=0;i--) if (names.indexOf(pns[i].name)==-1) pns.splice(i,1);
          if (pns.length>0) pref_func.apply_prep(pns, false, true, null, true);  // refresh appearance, make object, trigger event.
//          for (var i=0;i<names.length;i++) { // worknig code, but has a bug...
//            var pn = document.getElementsByName(names[i])[0];
//            if (pn) pref_func.apply_prep(document.getElementsByName(names[i])[0], false, true, null, true);  // refresh appearance, make object, trigger event.
//          }
////          for (var i=0;i<names.length;i++) { // working code.
////            var pn = document.getElementsByName(names[i])[0];
////            if (pn) {
////              pref_func.apply_prep(pn,false);  // refresh appearance.
//////            pref_func.apply_prep(pn,true);   // make obj.
//////              pref_func.invoke_onchange(pn,true);   // make obj.
////            }
////            pref_func.make_pref_obj(names[i]); // make obj.
//////            if (pref_func.settings.onchange_funcs[names[i]]) pref_func.settings.onchange_funcs[names[i]].call(pn);
////            if (pn && pref_func.settings.onchange_funcs[names[i]]) pref_func.settings.onchange_funcs[names[i]].call(pn);
////          }
////          if (sessionStorage) sessionStorage.pref = JSON.stringify(pref);
        }
      },
      settings: {
        pn13 : null,
        get pn13_1(){return this.pn13 && this.pn13.childNodes[1] || null;},
        pnOrDummy: (function(){
          var dummy = {name:'', tagName:'TEXTAREA', get onchange(){return cataLog.event_func}, getAttribute:function(prop){return this[prop];}};
          return function(name){return this.pn13 && this.pn13.getElementsByTagName('*')[name] || (dummy.name = name, dummy);};})(),
//        pnOrDummy: function(name){return this.pn13 && this.pn13.getElementsByTagName('*')[name] || {name:name, tagName:'TEXTAREA', onchange:cataLog.event_func, getAttribute: function(prop){return this[prop];}};},
        show_hide : function(e){
          if (pref_func.settings.pn13===null) {
            var pos = ((e.clientX*2>window.innerWidth)? 'right':'left') + ':0px:' + ((e.clientY*2>window.innerHeight)? 'bottom:0':'top:'+site.header_height())+'px';
            var pn13 = cnst.init(pos+':Show:tb',cnst.void_func,cnst.void_func,pref_func.settings.show_hide,cnst.void_func);
            var pn13_0_2 = cnst.add_to_tb(pn13,
              '<select name="settings.indexing">'+
                '<option>' + pref_func.settings.options.join('</option><option>') + '</option>'+
              '</select>');
            if (pref.settings.indexing >= pref_func.settings.options.length) pref.settings.indexing = 0;
            pn13_0_2.getElementsByTagName('select')['settings.indexing'].selectedIndex = pref.settings.indexing;
            pref_func.settings.pn13 = pn13; // for onchange func.
//            pref_func.mirror_targets.pn13_1 = pn13.childNodes[1]; // for mirror.
            pref_func.settings.onchange_funcs['settings.*']();
            pref_func.add_onchange(pn13_0_2,pref_func.settings.onchange_funcs_formatted);
            Tooltips.add_root(pref_func.settings.pn13);
            cnst.bottom_top(pn13);
          } else {
            Tooltips.remove_root(pref_func.settings.pn13);
//            pref_func.settings.files_store();
////            pref_func.mirror_targets.pn13_1 = null;
            pref_func.settings.pn13 = cnst.div_destroy(pref_func.settings.pn13, true); // returns null
//            pref.dashboard.rss = null;
          }
        },
        apply_pn13_1: function(set,propagate){
          pref_func.apply_prep(pref_func.settings.pn13_1,set,propagate);
          pref_func.settings.onchange_funcs['pn13_1_warning']();
        },
//        files_store: function(){
////          var files_archive = pref_func.mirror_targets.pn13_1.querySelectorAll('span[name="FILES_ARCHIVE0"]')[0]; // out of w3c, but works in chrome and FF.
////          if (files_archive) site.script_body.firstChild.appendChild(files_archive); // called also at initial. // firstChild is 'display:none'.
//////          if (files_archive) site.script_body.appendChild(files_archive); // called also at initial.
//          var files = pref_func.settings.pn13.querySelectorAll('input[type=file]');
//          if (files.length>0) pref_func.apply_prep_2_all(files,true);
//        },
        samples: {
          catalog: {
            get style_general_list_str(){return Tooltips.str['catalog.style_general_list_str']().replace(/^/mg,'// ')+
              '\n\n;background:#e5ecf9 // example for all threads\n'+
                ';border:1px solid black\n'+
                '8chan;background:#eef2ff;border:1px solid #d6daf0\n'+
                '8chan;background:#eef2ff\n'+
                'KC;background:#e0e0fc;border:1px solid #aaaacc\n'+
                '4chan;background:#ffffee;border:1px solid #f0e0d6\n'+
                '/a/;background:#eef2ff // color /a/ blue\n\n';},
            board:{ get ex_list_str(){return '// Identifier [, Identifier ...]\n//\n' + pref.samples.GeneralRules;}}
          },
          postFilter:{get str(){return '// [Identifier:]JSONC for post filter\n'+
            '// comments are started wich // or enclosed by /*...*/\n'+
            '// You can edit this directly,\n'+
            '//   but I recommend you to copy-paste from local postFilter whose JSONs are generated by GUI.\n'+
            '// You can add identifier to each head of JSONC. If no indentifier, it will be applied to all.\n\n'+
            '{"str":"cute","style":"border-left:10px solid pink"}\n\n'+Tooltips.str.Identifier.replace(/^[^\n]/mg,'// $&')+
            '// JSONC: is JSON but allows C-type comments in it.';}},
          style:{userCSS:{str:'/* userCSS will be a <style> elemnt directly. You can use all CSS notations.*/\n'+
            '/* comments must be enclosed by shash-aster, like this */\n'+
            '\n/* levelized merge */\n'+
            '.CatChan_merged1::before{float:left;content:">";}\n'+
            '.CatChan_merged2::before{float:left;content:">>";}\n'+
            '.CatChan_merged3::before{float:left;content:">>>";}\n'+
            '.CatChan_merged4::before{float:left;content:">>>>";}\n'+
            '.CatChan_merged5::before{float:left;content:">>>>>";}\n'+
            '/* colored merge */\n'+
            '.CatChan_merged1{border-left:10px solid red;}\n'+
            '.CatChan_merged2{border-left:10px solid orange;}\n'+
            '.CatChan_merged2{border-left:10px solid yellow;}\n'+
            '.CatChan_merged2{border-left:10px solid green;}\n'+
            '.CatChan_merged3{border-left:10px solid blue;}\n'+
            '\n/* remove buttons in titleBar */\n'+
            '.CatChan_titleBar button[name^="op"]{display:none} /* remove transparency buttons */\n'+
            '.CatChan_titleBar button[name="roll_toggle"]{display:none} /* remove toggle button */\n'+
            '.CatChan_titleBar button[name="max"]{display:none} /* remove maximize button */\n'+
            '.CatChan_titleBar button[name="top"]{display:none} /* remove embed to top button */\n'+
            '.CatChan_titleBar button[name="bottom"]{display:none} /* remove embed to bottom button */\n'+
            '.CatChan_titleBar button[name="autoHeight"]{display:none} /* remove auto height button */\n'+
            '/* give transparency to titlebar of watcher, add these 2 lines */\n'+
            '.CatChan_FC>*:first-child{position:absolute;width:100%;opacity:0;top:-2em}\n'+
            '.CatChan_FC>*:first-child:hover{opacity:1}\n'+
            '/* remove titlebar, add these 5 lines */\n'+
            '.CatChan_titleBar>*:first-child{position:absolute;width:100%;opacity:0}\n'+
            '.CatChan_titleBar>*:first-child:hover{opacity:1}\n'+
            '.CatChan_titleBar>*:first-child{pointer-events:none}\n'+
            '.CatChan_titleBar>*:first-child>*{pointer-events:auto}\n'+
            '.CatChan_titleBar>*:first-child>*:nth-child(4){pointer-events:none}\n'+
            '\n/* patches */\n'+
            '.CatChan_search_miss>*{opacity:inherit} /* patch for chrome in 4chan */\n'}}, // https://mevius.5ch.net/test/read.cgi/hp/1544452458/346
          get how_to_triage(){return Triage.prototype.howto;},
          RSS:{get str(){return '// JSONCs for RSS boards configuration(JSONC is JSON but allows C-type comments in it.)\n'+
            '// comments are started with // or enclosed by /*...*/\n'+
            '// You can edit this directly,\n'+
            '//   but I recommend you to use dashboard to edit this automatically,\n'+
            '//   though dashboard just gives small set of full functions.\n'+
            '//\n'+
            '// Each RSS board definition must be an array.\n'+
            '// [0]: name of board(mount point), or empty to be made from domain automatically.\n'+
            '// [1]: url(text) or urls(array of text).\n'+
            '// [2]: option object, omit this if you don\'t have any options.\n'+
            '//   "overrideMimeType":mimetype,    to fix char code for non-utf8 rss without appropriate mime-header.\n'+
            '//   "directConnection":true,    to connect directly(without site\'s proxy). The header "Access-Control-Allow-Origin: *" must be given by the site.\n'+
            '//   "responseType":"text",    to force response type to text, this is usually used to apply xhrPatchFunc\n'+
            '//   "xhrPatchFunc":"function....",    to apply patch function before parsing\n'+
            '//   "isTrusted":level,    to specify trust level.\n'+
            '//     0: No. All tags are sanitized to text.(default)\n'+
            '//     1: Allow tags other than <script>, but all attributes are removed.(probably safe)\n'+
            '//     2: Allow tags other than <script>, but style or event attributes are removed.(probably safe)\n'+
            '//     3: Allow tags other than <script>.\n'+
            '//       This is dangerous because malicious sites can fake the appearance.\n'+
            '//     4: Allow all tags including <script>.\n'+
            '//       THIS IS SUPER DANGEROUS, Don\'t use this when you aren\'t familiar with the site.\n'+
            '//   "dic_tags":{"#dialectalTag":"#normalTag",...}    to translate dialectal tag.\n'+
            '//     "#tagA":"#tagB",    to change #tagA to #tagB.\n'+
            '//     "#tag":"",    to delete #tag.\n'+
            '//     "#tag":["#tagA","#tagB"],    to expand #tag to #tagA and #tagB.\n'+
            '//     You can set global dictionaries by giving prefix "dic_tags:" at the toplevel.\n'+
            '//     There are two global dictionaries, "always" is applied always, but "incase" is applied only when its local dic_tags is given.\n'+
            '//     "incase" dictionary may be useful for internationalization.\n'+
            '//   "sub","com","no","sticky" ...    to override parse function. "sub" is the title, "com" is the content.\n'+
            '//     Choose a preset function(for famous rss) or give codes of javascript.\n'+
            '//     "sub":"sub_news24",    to use preset function sub_news24 at parsing "sub".\n'+
            '//     "no":"function(th){return parseInt(th.link.slice(th.link.lastIndexOf(\'/\')+1,th.link.lastIndexOf(\'.\')),10);}"    to give parse function at parsing "no".\n'+
            '["ntv","https://news.ntv.co.jp/rss/index.rdf",{"responseType":"text","xhrPatchFunc":"xhr_ntv"}] // ntv needs patch to fix RSS.\n'+
            '["ntv","https://news.ntv.co.jp/rss/index.rdf",{"responseType":"text","xhrPatchFunc":"xhr_ntv","directConnection":true}] // ntv can be accessed directly.\n'+
            '["ntv","https://news.ntv.co.jp/rss/index.rdf",{"overrideMimeType":"application/xml"}] // Or ntv needs overrideMimeType to fix mimeType.\n'+
            '["sputnik","https://sputniknews.jp/export/rss2/archive/index.xml"] // sputniknews is too long, mount as sputnik\n'+
//            '["news24","https://www.news24.jp/rss/index.rdf",{"overrideMimeType":"application/xml; charset=SJIS","sub":"sub_news24","no":"no_news24"}] // news24 needs overrideMimeType to fix char code, and use preset functions\n'+
//            '["sputnik","https://jp.sputniknews.com/export/rss2/archive/index.xml"] // sputniknews is too long, mount as sputnik\n'+
//            '[null,"https://jp.sputniknews.com/export/rss2/archive/index.xml",{directConnection:true}] // sputniknews can be accessed directly.\n'+
            '\n'+
            '// Global dictionaries can be set by giving prefix.\n'+
            '//   dic_tags:    to set global tag dictionaries.\n'+
            '//   dic_funcs:    to set global preset parse functions, which can be used to override, like "sub":"mySub".\n'+
            '//   Prefix is NOT parsed as JSON strings, enclosing by "" is not required.\n'+
            '//   These global dictionaries can be written as many lines as you want.\n'+
            'dic_tags:{"incase":{"#diaTag":"#tag"},"always":{"#otherDiaTag":"#tag"}} // set global tag dictionaries\n'+
            'dic_tags:{"#otherDiaTagA":"#tagA"} // "always" dictionary is created when the specifier("always" or "incase") is omitted\n'+
            'dic_tags:{"always":{"#tagA":"#tagAA"},"incase":{"#tagB""#tagBB"}} // example of usage of dic_tags\n'+
            '["","https://example.com/feed"] // translates #tagA to #tagAA.\n'+
            '["","https://example.com/feed", {dic_tags:{}}] // translates #tagA and #tagB to #tagAA and #tagBB respectively.\n'+
            '["","https://example.com/feed", {dic_tags:{"#tagC":"#tagCC"}}] // translate #tagA, #tagB and #tagC to #tagAA, #tagBB and #tagCC respectively.\n\n'+
            'dic_funcs:{"myCom":"function(th){return th.com_def.replace(/^\u3010[^\u3011]*\u3011/,\'\');}"} // th.com_def has default com value, and replace it\n'+
            'dic_funcs:{"xhr_ntv":"function(str){return str.replace(/<title>([^<]*)<\\\\/title>/g,function(m){return m.replace(/&/g,\'&amp;\');});}"} // gives patch for ntv, where & in title isn\'t encoded properly. This function is registerd as xhr_ntv.\n'+
            '// NOTE: this is input for JSON. Special characters(") must be escaped by backslash, and backslash itself needs to be escaped like \\\\\n'+
            '["/sputniknews/","https://sputniknews.jp/export/rss2/archive/index.xml",{"sticky":"function(th){return parseInt(th.pn_org.getElementsByTagNameNS(\'https://sputniknews.com/\', \'priority\')[0].textContent,10)<=1;}"}] // sticky function for sputnik, pn_org is the <item>\n'+
            '\n// Preset functions: (escaped for copy & paste)\n'+this.preset_functions()+
            '// For writing parse functions:\n'+
            '//   Argument:\n'+
            '//     th: thread object:\n'+
            '//       th.pn_org: <item> DOM in rss\n'+
            '//       th.com_def: default com (description)\n'+
            '//       th.sub_def: default sub (title)\n'+
            '//       th.xxx_def: default xxx, usually for replace them. com_def, sub_def, ...\n'+
            '//   Return value:\n'+
            '//     Each function must return value of the prop.\n'+
            '//   Functions in dic_funcs can be referred by this.dic_funcs[name]\n';},
            preset_functions: function(){
              var str = '';
              var dic = site2['rss'].parse_funcs['page_html'].dic_funcs.__proto__;
              for (var f in dic) str += '//   "'+f+'":'+JSON.stringify(dic[f].toString())+'\n';
//              for (var f in dic) str += '//   '+f+': '+dic[f].toString()+'\n';
              return str;
            }},
          cli:{json_str:
            '// JSONCs for overwriting preference or script itself.(JSONC: is JSON but allows C-type comments in it.)\n'+
            '// comments are started with // or enclosed by /*...*/\n'+
            '// press "apply" button to apply and test, result will be shown to right side of the button,\n'+
            '// or check "auto apply at startup" to apply them automatically at startup.\n\n'+
            '{"site2":{"KC":{"time_offset":2}}} // summer time for KC.\n'+
            '{"site2":{"4chan":{"protocol":"https:"}}} // use https to 4chan.\n'+
            '{"pref":{"patch":{"rm_404_blacklist":["8chan"]}}} // 8chan sends corrupted data.\n'
//            '{"site2":{"RSS":{"parse_funcs":{"page_html":{"isTrusted":2}}}}} // set default value of isTrusted for RSS to 2.\n',+
//            '{"site2":{"RSS":{"parse_funcs":{"page_html":{"directConnection":true}}}}} // set default value of directConnction for RSS to true.',
          },
          get GeneralRules(){return Tooltips.str.GeneralRules.replace(/^(.)/mg,'// $1');},
          get catalog_board_list_str(){return '// NameOfBoardGroup,[Identifier, ...][Commands, ...]\n//\n'+
            '// Each line becomes each board group, one line per one board group.\n'+
            '// First column is name of board group. The name is shown on board selector, and key for localStorage or statistics.\n'+
            '// Second or later columns are identifiers or commands.\n//\n'+
            this.GeneralRules+
            '//   To specify page No., use p* or c* as follows. (This is useful for RSS)\n'+
            '//       rss/someBoard/p0,   // fetch page 0 url as index page\n'+
            '//       rss/someBoard/p1,   // fetch page 1 url as index page\n'+
            '//       rss/someBoard/c0,   // fetch page 0 url as catalog, (and cleaning will be occur)\n'+
            '//       rss/someBoard/c1,   // fetch page 1 url as catalog, (and cleaning will be occur)\n'+
            '//     Cleaning will be occur after catalog page is fetched. If you want not to be cleaned, fetch index page(p*) instead of catalog(c*)\n'+
            '// Commands: \n'+
            '//   #xx: include filter for the tag #xx\n'+
            '//   ##xx: pickup and include filter for the tag #xx\n'+
            '//   ^#xx: pickup for the tag #xx\n'+
            '//   !#xx: exclude filter for the tag #xx\n'+
            '//   !stats: take statistics if the function is enabled.\n//\n'+
            '// Examples:\n'+
            'CC, lain/\u03bb/, #CatChan  // Load lain/\u03bb/ and filter by #CatChan. This shows threads for CatChan in lain/\u03bb/.\n'+
            'lain#lainchan, lain, #lainchan  // Load all boards from lain and filter by #lainchan. This shows all threads which have the tag(#lainchan) in the site.\n'+
            '#tech, ##tech  // Load all boards which have #tech from sites which are set in VirtualBoard, and filter by #tech. This shows all threads which have the tag(#tech) from all sites.\n'+
            '/news/, /news/, !stats // Load /news/ and take statistics.\n';},
//          get catalog_board_list_str(){return Tooltips.str.catalog_board_list_str();},
          liveTag: {
            get rm_list_str(){return this.TagExLists()+'//\n// This list is applied BEFORE extraction; all comments will be checked using this list, so this list COSTS MUCH.\n// Instead of this list, consider to use tag list which is applied AFTER extraction and costs less.\n\nhttp* // exclude all urls from extraction, prevent anchors in url from being a tag.\n';},
            get ex_list_str(){return this.TagExLists()+'//\n// This list is applied AFTER extraction and costs less.\n// Use this list inistead of string list which is applied BEFORE extraction and costs much.\n\n8chan:#selection // removes #selection tag in 8chan\n';},
            TagExLists: function(){return '// [Identifier:] SearchExpression, ...\n//\n// SearchExpression:\n//   Each searchExpression can be a string or a regular expression.\n//   Regular expression must be enclosed by shashes like /regexp/, and can accept i flag for case insensitive.\n//   IDENTIFIER MUST BE FOLLOWED BY :(A COLON).\n//     First colon is always interpreted as a delimiter,\n//     put a colon at the head when you want to use colons in search strings for global search(without any identifiers)\n'+pref_func.settings.samples.GeneralRules;},
          },
          dashboard:{rss:{get auto(){return site2['rss'].rss_appended_get_str();}}},
          proto:{footer:{design:
            'How to customize footer design\n\n'+
            '** are replaced by the parameter above.\n'+
            '() forms a replace group, all parameters must be in ().\n'+
//            '4,(IB) and (Ib) are italics start/end at bumplimit, must be used in pair.\n'+
//              '4,(II) and (Ii) are italics start/end at imagelimit, must be used in pair.\n'+
            '<b>, <i>, <u>, <s>, <span> can be used for styling.\n'+
            '<bB>, <iB>, <uB>, <sB>, and <spanB> are pseudo tags which are activated if the thread reaches bumplimit.\n'+
            '<bI>, <iI>, <uI>, <sI>, and <spanI> are for imagelimit as well.\n'+
            '<bZ>, <iZ>, <uZ>, <sZ>, and <spanZ> are pseudo tags which are activated if the content>0.\n'+
            'All tags can accept style attribute, like <span style=""></span>\n\n'+
            'Samples: (the format CANNOT contain comments)\n'+
            'U: */* / R: * / I: * / P: (this is copied to "custom")\n'+pref0.footer.native+'\n'+
            '*/*/*/*/*:\n'+pref0.footer.condensed+'\n'+
            '/bd/ */*/*/*/*:\n'+pref0.footer.meguca+'\n'+
            'custom2 default value:\n'+pref0.footer.custom2org+'\n'+
            'custom3 default value:\n'+pref0.footer.custom3org+'\n'}},
        },
        html_funcs: null,
        htmls:{ __proto__:{ // html_funcs is set to this
          show_hide_all_to_header: function(str){
            return '<span>' + str + '<a name="SHOWALL" style="float:right;cursor:pointer" data-str="[all\u25b2]">[all\u25bc]</a></span>';
          },
//          show_hide_all_to_header: function(str){
//            return '<span>' + str + '<span style="float:right">' + this.show_hide_all('all\u25bc', 'all\u25b2') + '</span></span>';
//          },
          show_hide_all: function(str_show, str_hide){
            return '<a name="SHOWALL" style="cursor:pointer">[' +str_show+ ']</a><a name="HIDEALL" style="cursor:pointer">[' +str_hide+ ']</a>';
          },
          show_hide: function(show, hide, inner, header, style){
            return '<a name="SHOW" style="cursor:pointer'+(style?';'+style:'')+'" data-str="' +hide+ '">' +show+ '</a><span style="display:none">' +(header||'')+ '<br>' +inner+ '</span>';
          },
          show_hide4: function(html, right2left, initial_show){
            return '<span '+(initial_show?'':'style="display:none"')+'>'+ (right2left?html:'') +
                '<span name="HIDE4" style="cursor:pointer">'+(right2left?'\u25b6':'\u25c0')+'</span>' + (right2left?'':html) +
              '</span>'+
              '<span name="SHOW4" style="cursor:pointer'+(initial_show?';display:none':'')+'">'+(right2left?'\u25c0':'\u25b6')+'</span>';
          },
//          show_hide4: function(header, inner, str_show, str_hide){
//            return '<span style="display:none">'+ header +
//                '<span name="HIDE4" style="cursor:pointer">'+(str_hide||'\u25c0')+'</span>'+ inner +
//              '</span>'+
//              '<span name="SHOW4" style="cursor:pointer">'+(str_show||'\u25b6')+'</span>';
//          },
          popup: function(mode){
            return '2,<IC"'+mode+'.popup">Pop up reply' + this.rollup(
              this.popup_delay('proto.')+'<br>'+
              '3,<IC"proto.popup_hlt">Highlight the post if it is visible')+'<br>'+
              '2,'+ this.rollup_radio(mode+'.inline_post', ' :Click posts to', 3, '',
                ['no:No','inline:Inline the post, shift-click to jump', 'jump:Jump to the post'])+ '<br>';
          },
          popup_delay: function(pf, imm_down){
            return '4,<IR"'+pf+'popdown,imm">immediately<br>'+
              '4,<IR"'+pf+'popdown,delay">delayed: up:<ITB4"'+pf+'popup_delay">'+(imm_down? '':', down:<ITB4"'+pf+'popdown_delay">')+' ms<br>'+
              '5,<IC"'+pf+'popup_mMove">Don\'t popup while mouse is moving<br>'+
              '4,zIndex: <ITB3"'+pf+'popup_zIndex">';
          },
//          popupX: function(mode, prop, caption){
//            return '2,<IC"'+mode+'.popup">'+caption+this.rollup(
//              this.popup_delay(prop))+'<br>';
//          },
          expand_thumbnail_inline: function(mode){
            var pf = mode+'.thumbnail.inline.';
            return '2,<IC"' +mode+ '.expand_thumbnail_inline">Image expansion at click'+ this.rollup(
              this.limit_webm(pf)+
//              '3,<IC"' +mode+ '.expand_thumbnail_initial">Expand all images at initial<br>'+
              '3,<IC"' +mode+ '.expand_thumbnail_inline_all_after">Expand all images after clicked one automatically<br>'+
              '4,On demand loading: <ITB3"' +pf+ 'ref_height">% of screen height<br>'+
//              '4,<IC"' +pf+ 'ondemand">On demand loading: <ITB3"' +pf+ 'ref_height">% of height<br>'+
              '4,<IC"' +pf+ 'ondemandStop">Stop expanding when any expanded image is shrinked<br>'+
              '3,<IC"' +pf+ 'stopHover">Stop image hover on expanded image')+ '<br>';
          },
          expand_thumbnail_hover: function(mode){
            var pf = mode+'.thumbnail.hover.';
            return '2,<IC"' +mode+ '.image_hover">Image hover'+ this.rollup(
              this.popup_delay(pf, true)+'<br>'+
              this.limit_webm(pf, true)+
              '3,<IC"' +mode+ '.image_prefetch">Prefetch next image<br>'+
              '3,<IC"' +pf+ 'dragfloat">Drag thumbnail to make it window<br>'+
              '4,<IC"' +pf+ 'df_dblC">Double click to close<br>'+
              '4,<IC"' +pf+ 'df_mW">Wheel to scroll<br>'+
              '3,<IC"' +pf+ 'zoom_click">Click to zoom mode if limited<br>'+
              '3,<IC"' +pf+ 'zoom_over">Enter to zoom mode if limited<br>'+
              '4,<IC"' +pf+ 'zoom_dblC">Double click to close<br>'+
              '4,<IC"' +pf+ 'zoom_mW">Wheel to scroll<br>'+
              '4,Tips for zoom mode: Limit max width.')+'<br>';
          },
          resize_thumbnails: function(mode){
            return '2,Size of thumbnails'+this.rollup(
              '4,<IC"'+mode+'.format.thumb.resize">Resize thumbnails always<br>'+
              this.resize_thumbnails_size(mode, 'vsmall', 'Very small')+
              this.resize_thumbnails_size(mode, 'small', 'Small')+
              this.resize_thumbnails_size(mode, 'large', 'Large')+
              this.resize_thumbnails_size(mode, 'custom', 'Custom')+
              (mode==='catalog'? this.resize_thread('c') : '')+
              this.resize_thumbnails_size(mode, 'custom2', 'Custom2')+
              (mode==='catalog'? this.resize_thread('c2') : '').slice(0,-4))+'<br>';
//              '3,<IC"'+mode+'.format.thumb.nativeIfSmall">Keep native size for small images'
          },
          resize_thumbnails_size: function(mode, size, caption){
            var pf = mode+'.format.thumb.';
            var pfs = pf + size;
            return '3,<IR"'+pf+'size,'+size+'">'+caption+'<br>'+
              '4,1st thumbnail: <ITB4"'+pfs+'.w1"> x <ITB4"'+pfs+'.h1"><br>'+
              '4,<IC"'+pfs+'.s2">Show 2nd and later: <ITB4"'+pfs+'.w2"> x <ITB4"'+pfs+'.h2"><br>'+
              '4,<IC"'+pfs+'.en">Enlarge thumbnails &lt; <ITB4"'+pfs+'.min"> px<br>';
          },
          resize_thread: function(size){
            return '4,Outer size:<br>'+
              '5,With OP'+this.design_of_fc_1('catalog','e'+size)+'<br>'+
              '5,Without OP'+this.design_of_fc_1('catalog','n'+size)+'<br>';
          },
          limit_webm: function(pf, webm_ctrl){
            return '4,<IC"' +pf+ 'limit_width">Limit to browser\'s width - <ITB3"' +pf+ 'margin_width"> px<br>'+
              '4,<IC"' +pf+ 'limit_height">Limit to browser\'s height - <ITB3"' +pf+ 'margin_height"> px<br>'+
              '4,<IC"' +pf+ 'webm">Play webm/mp4'+(webm_ctrl? ', or <IC"' +pf+ 'webm_ctrl">play if ctrlKey':'')+'<br>'+
              '5,<IC"' +pf+ 'webm_loop">Loop<br>'+
              '5,<IC"' +pf+ 'webm_mute">Mute<br>';
//              '3,<ICBX"' +pref+ 'limit_width"> Limit to browser\'s width - <ITB3"' +pref+ 'margin_width"> px<br>'+
//              '3,<ICBX"' +pref+ 'limit_height"> Limit to browser\'s height - <ITB3"' +pref+ 'margin_height"> px';
          },
          env: function(mode, str_inner){
            return '1,Environment values:(advanced option)'+ this.rollup(
              '2,You are using native function of:<br>'+
              '3,<IC"' +mode+ '.popup_native">Pop up reply<br>'+
              '3,<IC"' +mode+ '.expand_thumbnail_inline_native">Image expansion<br>'+
              '3,<IC"' +mode+ '.image_hover_native">Image hover<br>'+
              '3,<IC"' +mode+ '.colorID_native">Color ID<br>'+
              '3,<IC"' +mode+ '.backlink_native">Backlink<br>'+
              '3,<IC"' +mode+ '.localtime_native">Local time'+
              str_inner + '<br>'+
              '2,<IC"' +mode+ '.popup_native_kill">Suppress native static popup<br>'+
              '2,<IC"' +mode+ '.refresh_initial">Refresh at initial<br>'+
              '2,Delay for posts search:<br>'+
              '3,Incremental posts search may cause massive delay when keyword is short.<br>'+
              '3,<ITB3"'+mode+'.kwd_filter_delay">ms if &lt;<ITB2"'+mode+'.kwd_filter_delay_len">letters') +'<br>';
//              this.native_html_config(mode)) + '<br>';
          },
//          native_html_config: function(mode){
//            return '2,Native HTML configuration<br>'+
//              '3,<IC"' +mode+ '.thread_pos_static">Thread position is static<br>';
//          },
          posts_for_search: function(mode){
            return '2,'+ this.rollup_radio(mode+'.storePosts',' :Num of posts to store for search', 3, '',
              ['no:No additional posts are stored for search.<br>5,Search will be done only from posts for preview.',
               'ALL:All posts are stored always.<br>5,Search will be done from all posts without preparation scan.',
               'auto:All posts in all scanned threads are stored after the first activation of search.<br>5,Need (automatic) scans prior to search. (usual usage)',
               'ALL_agg:All posts in all scanned threads are stored.<br>5,Search will be done from all scanned threads.<br>5,This is an option for search from entire domains.<br>'+
               '3,<IC"proto.searchAc.loosen">Auto loosen limits at the first activation'+this.rollup(
                 '6,Max boards of a scan: <ITB6"proto.searchAc.max"><br>'+
                 '6,Max threads of a scan: <ITB6"proto.searchAc.max_threads"><br>'+
                 '6,Max threads at refresh (auto deletion): <ITB6"proto.searchAc.max_threads_at_refresh"><br>'+
                 '5,<BTN"tighten_loosed_limits,Tighten loosed limits">')+'<br>'+
               '3,<IC"'+mode+'.searchAs">Auto scan from this (virtual) board if needed <BTN"scanBoardIf,Scan"><BTN"scanBoard,Rescan"><br>'+
               '3,<IC"'+mode+'.searchAsA">Auto scan from all (virtual) boards if needed <BTN"scanSiteIf,Scan"><BTN"scanSite,Rescan">']) +'<br>'+
              '2,'+ this.rollup_radio(mode+'.sourceOfSP',' :Posts to be used for search', 3,
              '3,Posts may be stored also by function of deleted posts detection.<br>'+
              '3,Number of stored posts can be shown in footer.<br>',
              ['auto:All stored posts.(usual usage)',
              'pv:Posts for preview only.<br>5,Search will be done only from recent or newer posts,<br>5,which depends on preview settings.']) +'<br>';
          },
          op_in_search_result: function(mode){
            return '2,' + this.rollup_radio(mode+'.posts_search_op', ' :Unmatched OP in search result', 3, '',
              ['show:Show', 'opaque:Opaque: <ITB3"' +mode+ '.posts_search_op_opacity"> %','hide:Hide']) + '<br>';
//              '2,<ICBX"' + mode + '.auto_config_posts_search"> Auto config and scan at searching posts(WILL BE REPLACED)<br>';
          },
          deleted_posts_detection: function(mode){
            return '2,' + this.rollup_radio(mode+'.deleted_posts.detect', ' :Detect deleted posts', 4, '3,Detection:<br>',
                ['no:No', 'passive:Passive detection. Don\'t make any request',
                 'acc:Best effort without any additional requests', 'full:Full detection, read all posts at first', 'full_IDB:Full detection with IndexedDB'],
              '<br>'+
              '3,<IC"' + mode + '.deleted_posts.merge">Merge deleted posts with live posts'
//              this.radios(mode+'.deleted_posts.store',4,
//                ['LS:Store to LocalStorage to share with other tabs<br>5,<ICBX"' + mode + '.deleted_posts.auto_clean"> Clean up automatically',
//                 'SS:Store to SessionStorage to get higher security', 'MC:Use ChannelMessage for security'])
              );
          },
          popup_others: function(mode){
            return '2,<IC"'+mode+'.popupX.use">Pop up others'+ this.rollup(
              this.popup_delay('proto.popupX.')+'<br>'+
              '3,Pop up other posts of the same XX on XX<br>'+
              '4,<IC"'+mode+'.popupX.ID">ID<br>'+
              '4,<IC"'+mode+'.popupX.ID2">ID2<br>'+
              '4,<IC"'+mode+'.popupX.flag">Flag<br>'+
              '4,<IC"'+mode+'.popupX.name">Name<br>'+
              '4,<IC"'+mode+'.popupX.trip">Trip')+'<br>';
          },
//          show_val_rollup: function(pref, inner){
//            return '<SSV"' +pref+ '">'+ '&emsp;' + this.show_hide('change...', 'hide', '', inner);
//          },
          radios: function(pref, indent, args, pref2){
            var r_str = indent+',<IR"'+pref+',$1">' + (pref2? pref2.map(function(v){return '<IR"'+v+',$1">';}).join('') : '');
            return args.map(function(v){return v.replace(/([^:]*):/, r_str);}).join('<br>');
          },
          rollup_radio: function(pref, header, indent, middler, args, trailer, pref2){
            return '<a name="SHOW2" style="cursor:pointer" data-str="\u25b2]">[<SSV"' +pref+ '" style="font-weight:bold"></span>\u25bc]</a>'+
              '<span name="SHOW2_I">' + header + '</span><span style="display:none" name="SUB"><br>' +middler + this.radios(pref,indent,args,pref2)+ (trailer || '') + '</span>';
//            return '<span name="NEXT" style="cursor:pointer">[<SSV"' +pref+ '"></span>' + this.show_hide('\u25bc]', '\u25b2]', header, middler + args.join('<br>'));
//            return '<SSV"' +pref+ '">'+ header + '&emsp;' + this.show_hide('change...', 'hide', '', middler + args.join('<br>'));
          },
          rollup: function(inner, header, no_blank){
            return (no_blank?'':'&emsp;') + this.show_hide('[\u25bc]', '[\u25b2]', inner, header);
          },
          rollupHelp: function(caption, inner, header){
            return this.show_hide('['+caption+'\u25bc]', '['+caption+'\u25b2]', inner, header, 'float:right');
          },
          rollupTrav: function(tgt, caption){
            return '<a name="SHOW_T" style="cursor:pointer" data-str="['+caption+'\u25b2]" data-trav="'+tgt+'">['+caption+'\u25bc]</a>';
          },
          format_posts: function(mode){
            return '2,<IC"' +mode+ '.open_spoiler_text">Open text spoiler<br>'+
              '2,<IC"' +mode+ '.colorID">Color ID<br>'+
              this.backlink(mode)+
              '2,<IC"' +mode+ '.localtime">Local time<br>'+
              this.deleted_posts_detection(mode)+'<br>'+
              '2,<IC"' +mode+ '.mark_new_posts">Mark new posts<br>'+
              '2,<IC"' +mode+ '.use_expander_always">Use CatChan\'s expander always<br>';
          },
          backlink: function(mode){
            return '2,<IC"' +mode+ '.backlink">Backlink'+ this.rollup(
              '3,<IC"proto.bl_ec">Explicit cross backlink<br>'+
              '3,<IC"proto.bl_rm">Don\'t retain backlinks to OP<br>'+
              '3,<IC"' +mode+ '.backlink_all">Track all backlinks<br>'+
              '4,<IC"' +mode+ '.backlink_all_cross">Track all cross-board links') + '<br>';
          },
          merge_all: function(mode){
            return '2,<IC"' +mode+ '.merge">Merge all threads'+ this.rollup(
              '2,<IC"' +mode+ '.merge_list">Merge listed threads&emsp;<BTN"cleanMerge,CleanUpList">'+'<br>'+
              '3,<TA"proto.merge_list_str"><br>'+
              '3,<IC"' +mode+ '.merge_lv"' /* +(mode==='catalog'?' disabled':'') */ +'>Merge with level<br>'+
              '4,<TA"proto.merge_lv_str"><br>'+
              '4,Use style tips or user CSS to style'+this.rollup(
              this.userCSS(4,4).slice(0,-4))+'<br>'+
              '3,<IC"'+mode+'.merge_auto">Auto fetch and merge listed threads<br>'+
//              (mode==='thread' || mode==='page'? '3,<IC"'+mode+'.merge_auto">Auto fetch and merge listed threads'+ this.rollup(
//              '4,<IC"'+mode+'.merge_auto_lv_add">with level <ITB2"'+mode+'.merge_auto_lv">')+ '<br>' : '')+
              '3,<IC"'+mode+'.merge_op.auto">Auto merge cross-linked threads in OP'+this.rollup(
//              '5,(For new thread of generals typically.)<br>'+
              '4,<IC"'+mode+'.merge_op.fetch">Fetch to hop <ITB2"'+mode+'.merge_op.hop"> recursively<br>'+
              this.merge_lv(mode+'.merge_op'))+'<br>'+
//              '4,(Entries of level are also used as end flags.<br>'+
//              '4,Unmerged states will be cleared after relaod if they are not added.)<br>'+
//              '4,<IC"'+mode+'.merge_op_list">Inherit watch time<br>'+
//              '4,<IC"'+mode+'.merge_op_attr">Inherit style attributes')+'<br>'+
              '3,<IC"' +mode+ '.merge_btn.add">Add merge button to cross-links'+ this.rollup(
              this.merge_lv(mode+'.merge_btn'))+'<br>'+
              '2,Merge mode:<br>'+
              '3,<IR"'+mode+'.merge_mode,all"'+ (mode!=='thread'?' disabled':'')+'>all (Don\'t modify the list)<br>'+
              '3,<IR"'+mode+'.merge_mode,list"'+(mode!=='thread'?' disabled':'')+'>by list (Each target is added to the list)'+
              (mode==='thread'? '<br>3,<IC"'+mode+'.scroll_lock">Don\'t scroll at merging':''))+'<br>';
          },
          merge_lv: function(mode_tgt){
            return '4,<IC"'+mode_tgt+'.lv_add">with level<ITB2"'+mode_tgt+'.lv_def">, <IC"'+mode_tgt+'.lv_inc">increment by hop<br>'+
              '4,<IC"'+mode_tgt+'.attr">Inherit style attributes<br>'+
              '4,<IC"'+mode_tgt+'.list">Inherit watch time';
          },
          userCSS: function(indent, indent_tips){
            return indent_tips+',<IC"style.tips.merged">Add n * <ITBL2"style.tips.merged_cnt"> to merged threads of level n<br>'+
//              (indent+1)+',<IC"style.tips.merged1">Add ">" to merged threads of level 1<br>'+
//              (indent+1)+',<IC"style.tips.merged2">Add ">>" to merged threads of level 2<br>'+
//              (indent+1)+',<IC"style.tips.merged3">Add ">>>" to merged threads of level 3<br>'+
              indent+','+this.ta_with_help('style.userCSS', 'User CSS');},
//              indent+',<IC"style.userCSS.use">User CSS<br>'+
//              (indent+1)+',<TA"style.userCSS.str"><br>'+
//              (indent+1)+',Sample user CSS:<br>'+
//              (indent+1)+',<TA"samples.style.userCSS" disabled>';},
////            '<div>'+
////              '<div style="float:left">&emsp;&emsp;</div>'+
////              '<div style="float:left">'+
////                '<TA"style.userCSS.str,50,1">'+
////                '<div style="overflow:scroll;resize:both;">'+this.userCSS_samples.replace(/\n/g,'<br>')+'</div>'+
////              '</div>'+
////            '</div>';},
////          userCSS_samples: 'Samples:\n'+
////            '.CatChan_titleBar button[name^="op"]{display:none} /* remove transparency buttons in titleBar */\n',
          vb_domains: function(){
            var pf = 'virtualBoard.scan_domains.';
//            var str = '<span name="virtualBoard.scan_domains">'+
            return '2,Scan targets:<br>'+
              '3,None/Board/Thread (No/Passive/Active)<br>'+
              site0.domains.filter(v=>v in pref.virtualBoard.scan_domains).map(function(v,i){return '3,<IR"'+pf+v+',none"><IR"'+pf+v+',board"><IR"'+pf+v+',thread"> '+ v +(i===0?' (this site)':'')+'<br>';}).join('');  // 'apply_prep' can't recognize ['domain'] notation.
          },
//          make_sel: function(pref,args){
//            var str = '<select name="' + pref + '" style="float:none">';
//            for (var i=0;i<args.length;i++) str += '<option>' + args[i] + '</option>';
//            return str + '</select>';
//          },
          features_domains: function(){
            var str = ''
            for (var i=0;i<site0.domains.length;i++) str += '1,<IC"features.domains.'+site0.domains[i]+'">'+ site0.domains[i] +'<br>';
            return str;
          },
          cb_block: function(pf,start, end){
            var str = '';
            var i=start-1;
            while (pref[pf][++i]!==undefined && (!end || i<end)) str += '<ICN"'+pf+'.'+i+'">' + ((i%10===4)? '&emsp;' : (i%50===49)? '<br><br>' : (i%10===9)? '<br>' : '');
            return str;
          },
          footer: function(mode){
            var pf = mode+'.footer.';
            return '1,<IC"'+pf+'use">Show information footer<br>'+ // this.rollup(
//                '3,<IC"'+mode+'.footer_br">Always over/under the image')+'<br>'+
              '2,<IC"'+pf+'nrtm">Show num of new replies to me (nm)'+this.rollup(
                '3,<IC"'+pf+'nrtm1">Style if &gt;0:<ITBL30"'+pf+'nrtm1_style"><br>'+
                '3,<IC"'+pf+'nrtm0">Empty if 0')+'<br>'+
              '2,<IC"'+pf+'nr">Show num of new replies (nr)'+this.rollup(
                '3,<IC"'+pf+'nr1">Style if &gt;0:<ITBL30"'+pf+'nr1_style"><br>'+
                '3,<IC"'+pf+'nr0">Empty if 0')+'<br>'+
              '2,<IC"'+pf+'rp">Show num of replies (rp), bold (RP)'+this.rollup(
                '3,<IC"'+pf+'rOP">Count OP<br>'+
                '3,<IC"'+pf+'rp0">Empty if OP only')+'<br>'+
              '2,<IC"'+pf+'im">Show num of images (im), bold (IM)'+this.rollup(
                '3,<IC"'+pf+'iOP">Count images in OP')+'<br>'+
              '2,<IC"'+pf+'nl">Show num of stored live posts (lp)<br>'+
              '2,<IC"'+pf+'nd">Show num of deleted posts (dp)<br>'+
              '2,<IC"'+pf+'ns">Show num of saged posts (sp)<br>'+
              '2,<IC"'+pf+'page">Show page No. (pg), <IC"'+pf+'pgp">high precision<br>'+
              '2,<IC"threadStats.use">Take statistics of each thread'+this.rollup(
                '4,<IC"threadStats.full">Issue additional fetchs if required<br>'+
                '5,<IC"threadStats.retry">Retry if obsolete data(may cause infinite loop)') + '<br>'+
              '3,<IC"'+pf+'nid">Show num of IDs (ni)<br>'+
              '3,<IC"'+pf+'nf">Show num of flags (nf)<br>'+
              '2,<IC"'+pf+'ctime"> <IC"'+pf+'rctime">Show created time (ct), relative (cT)<br>'+
              '2,<IC"'+pf+'btime"> <IC"'+pf+'rbtime">Show bumped time (bt), relative (bT)<br>'+
              '2,<IC"'+pf+'ptime"> <IC"'+pf+'rptime">Show last posted time (pt), relative (pT)<br>'+
              '2,<IC"'+pf+'prate">Show post rate, posts/day (pr)<br>'+
              '2,<IC"'+pf+'domain">Show domain\'s name (dn)<br>'+
              '2,<IC"'+pf+'board">Show board\'s name (bd)<br>'+
              '2,<IC"'+pf+'no">Show thread\'s no (no)<br>'+
              '2,<IC"'+pf+'archived">Show archived (ar)<br>'+
              '2,<IC"'+pf+'tag">Show tags, <SE"proto.footer.merged_tag,no:no tags,any:in any threads,all:in all threads"> for merged threads<br>'+
    //          ', in <input type="text" name="'+pf+'tag_letters" size="2" style="text-align: right;"> letters'+
              '2,<IC"'+pf+'flag">Show recent flags<br>'+
              '2,<IC"'+pf+'menu">Show menu button at last'+this.rollup(
              '3,'+this.triage_config(pf+'menu_str', 'Footer tail', 'kill,simple,menu:menu (default),headline,headline_ex,headline_merge,headline_merge_ex'))+'<br>'+
//                '3,<TA"'+pf+'menu_str">', this.rollup(Triage.prototype.howto))+'<br>'+
              '2,<IC"'+pf+'triage">Show triage button at head'+this.rollup(
              '3,'+this.triage_config(pf+'triage_str', 'Footer head', 'kill,simple:simple (default),menu,headline,headline_ex,headline_merge,headline_merge_ex'))+'<br>'+
//                '3,<TA"'+pf+'triage_str">', this.rollup(Triage.prototype.howto))+'<br>'+
              '2,Design:<br>'+
              '3,<IR"'+pf+'design,native"> U: */* / R: * / I: * / P: *<br>'+
              '3,<IR"'+pf+'design,condensed"> */*/*/*/*<br>'+
              '3,'+this.inlineBlock('<IR"'+pf+'design,meguca"> /bd/ */*/*/*/*'+this.rollupHelp('How to',
                '1,<TA"samples.proto.footer.design" disabled>')+'<br>'+
                '<IR"'+pf+'design,custom"> <ITBL40"proto.footer.custom">')+'<br>'+
              '3,<IR"'+pf+'design,custom2"> <ITBL40"proto.footer.custom2"><br>'+
              '3,<IR"'+pf+'design,custom3"> <ITBL40"proto.footer.custom3"><br>';
          },
//          design_of_fc: function(pfx){
//            return '<SE"'+pfx+'.view,headline:Headline,catalog:Catalog,page:Page"><span><SE"'+pfx+'.headline.subStyle,SingleLine,MultiLines"></span>';
////            return this.rollup_radio('float.view', ' :View', indent, '', ['headline:Headline','catalog:Catalog<br>'+
////              (indent+1)+',fc'+this.design_of_fc_1(mode, 'fc')+'<br>'+(indent+1)+',customNoOP'+this.design_of_fc_1(mode, 'c')+'<br>'+(indent+1)+',custom'+this.design_of_fc_1(mode, 'ec'), 'page:Page']);
////              (indent+1)+',<IC"float.format.'+prop+'.resize">Resize:<ITB4"float.format.'+prop+'.width"> x <ITB4"float.format.'+prop+'.height">(<ICBX"float.format.'+prop+'.max">Max)', 'page:Page']);
//          },
          design_of_fc_1: function(mode, prop){
            return ': <ITBN4"'+mode+'.format.'+prop+'.width">x<ITB4"'+mode+'.format.'+prop+'.height">(<ICN"'+mode+'.format.'+prop+'.max">Max)';
          },
          design_of_fc_size_doms: document.createElement('div'),
          design_of_fc_size_doms_register: function(pn_size,pn_teaser){
            var pn = this.design_of_fc_size_doms;
            if (pn.childNodes.length!==0) return;
            pn.appendChild(pn_size.cloneNode(true)).name = 'float.catalog_size';
            pn.appendChild(pn_teaser.cloneNode(true)).name = 'float.catalog_teaser';
          },
          design_of_fc_size: function(){
            return this.design_of_fc_size_doms.innerHTML;
          },
          updates: function(mode, instance_form){
            return '<IC"' +mode+ '.auto_update">Auto update'+(instance_form?'':', every ')+'<ITBN2"' +mode+ '.auto_update_period">min'+(instance_form || '.')+'<br>'+
              this.rollup_radio(mode+'.refresh_src', ' :Refresh targets from', 1, '', ['bg:Boards group (usual catalog)','bgwl:Both; boards group and watch list (embed watcher)', 'wl:Watch list (watcher mode)'], '<br>2,Use catalog if >=<ITB3"'+mode+'.refresh_src_th"> threads in a board (for watch list)') + '<br>';
          },
          broadcast_triage: function(prop, caption, caption2){
            return this.rollup_radio('triage.sync_'+prop, ' :Sync '+(caption || prop)+' list in this tab'+(caption2||''), 3, '', ['no:No','bg:Sync among the same board group', 'all:Sync with any board group']) + '<br>';
          },
          popup3: function(mode, indent){
            return this.rollup_radio(mode+'.popup3.ww',' :Pop up', indent,
              '3,WW/WN/NW/NN (Wider/Narrower) side:'+this.rollup(this.popup_delay('proto.popup3.')+'<br>'+
              '4,Distance from mouse pointer, x:<ITB4"proto.popup3.oX">, y:<ITB4"proto.popup3.oY">')+'<br>',
              ['no:No','sr:Search result','srpv:Search result or preview','pv:Preview','chart:Post rate chart (statistics must be activated)','dp:Deleted posts'], '',[mode+'.popup3.wn',mode+'.popup3.nw',mode+'.popup3.nn'])+ '<br>';
          },
          t2h: function(mode, caption){
            return '2,' + this.rollup_radio(mode+'.t2h_sel', ' :'+caption, 3, '',
              ['page:Inherit from index page',
               'L: <ITB3"'+mode+'.t2h_L"> posts(L posts)',
               'M: <ITB3"'+mode+'.t2h_M"> posts(M posts)',
               'N: <ITB3"'+mode+'.t2h_num_of_posts"> posts(N posts)',
               'N_unread:N + all unread posts',
               'unread:All unread posts',
               'ALL:All posts',
//               'ALL_agg:All posts (for incremental posts search)',
              ]) +'<br>';
          },
          appearance: function(prop, style){
            if (!style) style='';
            return '<IC"catalog.appearance.'+prop+style+'"><IC"page.appearance.'+prop+style+'"><IC"thread.appearance.'+prop+style+'"><IC"float.appearance.'+prop+style+'">';
          },
          triage_config: function(prop, caption, samples){
            return this.inlineBlock(
              '<span>'+caption+': <span style="float:right"><SE"triage.sampleLoader,Load sample...,'+samples+'" data-tgt="'+prop+'"> '+this.rollupTrav('PPn','How to')+'</span></span>'+
              '<span style="display:none"><br>'+
                '<TA"samples.how_to_triage" disabled>'+
              '</span><br>'+
              '<TA"'+prop+'">');
//            return '<div style="display:inline-block">'+
//                caption+': <SE"triage.sampleLoader,Load sample...,'+samples+'" data-tgt="'+prop+'">'+this.rollupHelp('How to','<TA"samples.how_to_triage" disabled>')+'<br>'+
//                '<TA"'+prop+'">'+
//              '</div>'+
//              '<div style="clear:both"></div>';
          },
//          triage_config: function(prop, indent, caption, samples){
//            return indent+','+caption+': <SE"triage.sampleLoader,Load sample...,'+samples+'" data-tgt="'+prop+'">'+this.rollup(Triage.prototype.howto)+'<br>'+
//              (indent+1)+',<TA"'+prop+'">';
//          },
          triage_hover: function(mode){
            return '3,<IC"'+mode+'.triage_hover">'+(mode==='page'? 'index ':'')+mode+' view, <SE"'+mode+'.triage_place,topLeft:Top left,bottomLeft:Bottom left,topRight:Top right,bottomRight:Bottom right"><br>';
          },
          IDinfo: function(tgt, op, same, sameCap){
            return '3,<IC"IDinfo.'+tgt+'.use">nth/all by each '+tgt+', enclosed by <ITBL1"IDinfo.'+tgt+'.h"><ITBL1"IDinfo.'+tgt+'.t"><br>'+
              (same? '4,<IC"IDinfo.'+tgt+'.'+same+'">nth/all/nof '+same+'s of the same '+tgt+'<br>':'')+
              '4,<IC"IDinfo.'+tgt+'.nof">nth/all'+(same? '(/'+(sameCap)+')':'')+'/nof '+tgt+'s at that time<br>'+
              (op? '4,<IC"IDinfo.'+tgt+'.op">Add \'OP\'<br>':'');
          },
          save_idx_promiscuous: function(mode){
            return '2,<IC"'+mode+'.save_board_list_sel">Save board group index<br>'+
              (mode==='float'? '2,<IC"'+mode+'.clone">Clone threads at initial if others refer the same board group<br>' : '')+
              '2,<IC"'+mode+'.accept_others_refresh">Sync with others which refer the same board group<br>'+
              '2,<IC"'+mode+'.promiscuous">Show all fetched threads<br>';
          },
          ta_with_help: function(prop, caption, old_prop, caption_button, woCbx){
            return this.inlineBlock((woCbx?'':'<IC"'+prop+(old_prop?'':'.use')+'">')+caption+this.rollupHelp(caption_button||'sample',
              '1,<TA"samples.'+prop+(old_prop?'_':'.')+'str" disabled>')+'<br>'+
              '1,<TA"'+prop+(old_prop?'_':'.')+'str">')+'<br>';
          },
          notify_init: function(prop){
            return this.rollup('4,<IC"notify.'+prop+'.supp_init">Suppressed at initial')+ '<br>';
          },
          notify_tgts: function(prop){
            return '2,<IC"notify.'+prop+'.reply_to_me">New replies to me<br>'+
              '2,<IC"notify.'+prop+'.reply">New replies<br>'+
              '2,<IC"notify.'+prop+'.new_thread">New threads<br>'+
              (pref.test_mode['133']? '2,<IC"notify.'+prop+'.appear">Appear threads<br>' : '');
          },
          add_indent: function(indent, str){
            return str.replace(/(^|<br>)(\d+,)*([<\w])/g, function(m,p1,p2,p3){
//            return str.replace(/(^|<br>|<\/div>)(\d+,)*</g, function(m,p1,p2){
              var idt = indent + (parseInt(p2,10)||0);
              return (p1||'') + (idt>0? idt+',':'')+p3;});
          },
          inlineBlock: function(str){
            return '<div style="display:inline-block">'+str+'</div>'; // +
//              '<div style="clear:both"></div>';
          },
          dashboard_row: function(prop, data, kind, expand_close, page){
            var entry_auto = kind==='auto';
            var entry_an   = kind==='auto' | kind==='new';
            var ph = data.db_def;
            var cs = data.colStyle;
            var ex_cl = '<SPB"P.'+(expand_close==='ex'?'expand,[+]':'close,[-]')+'"'+(!expand_close?' style="display:none"':'')+'>';
            var ro = ' style="opacity:0.6;background:transparent" readonly';
            return this.expand_row(('<SPB"P.'+(entry_an?'add,+':'up,\u2191')+'">|<SPB"P.col,\u2215" style="display:block;'+(cs||'')/*+(entry_new?'visibility:hidden':'')*/+'">|<IC"P.sticky"'/*+(entry_new?' style="visibility:hidden"':'')*/+'>|'+ex_cl+'|<ITBL10"P.db" placeholder="'+ph+'"'+(entry_auto?ro:'')+'>|<ITBL10"P.url"'+(entry_auto?ro:'')+'>|<span>'+(expand_close||page?':'+page:'')+'</span>|'+
                '<'+(page?'DISABLED':'')+'SE"P.isTrusted,0,1,2,3::disabled,4::disabled">|'+
//              '<IC"P.t1"><IC"P.t2"><SSV"P.isTrusted" style="opacity:0.6">'+
                '<'+(page?'DISABLED':'')+'ITBL10"P.opt"'+ro+'>|'+(entry_an?'':'<SPB"P.del,\u00d7">')+'|'+(kind!=='new'?this.rss_dashboard_status(data, prop):'')),
              null,'|').replace(/><DISABLED/g,' class="'+pref.cpfx+'disabled"><').replace(/\"P\./g,'"P.'+prop+'.');
          },
          expand_row: function(str,tag, sep){
            var t = tag||'td';
            return ('<tr><'+t+'>'+str.replace(sep? new RegExp('\\'+sep,'g'):/,/g,'</'+t+'><'+t+'>')+'</'+t+'></tr>')
              .replace(/>(\d+)L:/g,' style="text-align:left">$1:').replace(/>(\d+):/g,' colspan="$1">');
//            var sep_tag = !sep && str.indexOf('><')!==-1; // working code
//            return ('<tr><'+t+'>'+str.replace(sep_tag?/></g:sep? new RegExp('\\'+sep,'g'):/,/g,(sep_tag?'>':'')+'</'+t+'><'+t+'>'+(sep_tag?'<':''))+'</'+t+'></tr>')
//              .replace(/>(\d+)L:/g,' style="text-align:left">$1:').replace(/>(\d+):/g,' colspan="$1">');
          },
          colorPicker: function(){
            return '<div data-color_picker="popup" class="'+pref.cpfx+'popUp" style="position:absolute;display:none">'+
              (['bisque', 'mistyrose', 'aliceblue','lightcyan','cyan','aquamarine','lime','yellow','orange','pink','magenta'].map(v=>'<SPB"colorPicker.colBtn.'+v+',&emsp;\u2215&emsp;" style="background:'+v+'">')).join('')+'<br>'+
              '1,custom: <ITBL20"colorPicker.txt">, or pick. <input name="colorPicker.picker" type="color"><br>'+
              '1,to <IR"colorPicker.bgbd,bg">background <IR"colorPicker.bgbd,bd">border:<ITBL10"colorPicker.bds"><br>'+
              '<span data-color_picker="sample">sample ABC xyz</span><br>'+
              '<BTN"colorPicker.Set"><BTN"colorPicker.Clear"><BTN"colorPicker.Cancel"></div>';
          },
        },
        'Easy Setting2:': function(){return 'Easy Setting2: --- Welcome to CatChan! ---<br>'+
          '<br>'+
          '1,0. <BTN"easy2.beginner,I\'m a beginner and I just want to try">, click this.<br>'+
          '2,Then a reload will occur after loading settings for beginner.<br>'+
          '2,(This will be default after a certain period of test.)<br>'+
          '2,Or follow next steps to choose your favorite settings.<br>'+
          '<br>'+
          '1,1. <IC"easy2.reset">Reset all settings before loading new one if you prefer.<br>'+
          '1,2. Select a preset: '+
          '<SE"easy2.presets,'+
            'Safe default,'+
            'Silent inter-board operability (for beginner),'+ // passive/No/No. works silently.
            'Silent inter-site operability,'+  // passive/passive/No, works silently.
//            'Silent in-board operability with tags from OP,'+    // active/No/liveTag-OP
            'Active inter-board operability,'+ // active/No/LiveTag-OP, scans all catalogs.
            'Active inter-site ondemand operability,'+ // active/passive/LiveTag-OP, scans all catalogs, but ondemand for other site.
            'Active inter-site operability,'+ // active/active/LiveTag-OP, scans all catalogs.
            'Active in-board operability with LiveTag,'+    // active/No/liveTag-Post, scans all threads.
            'Active inter-board operability with LiveTag,'+ // active/passive/liveTag-Post, scans all threads.
            'Active inter-site operability with LiveTag'+ // active/active/liveTag-Post, scans all threads.
          '"><br>'+
          '1,3. Select limits for safe: '+
          '<SE"easy2.limits,'+
            'General,'+
            '/all/ for lain\\, meguca,'+
            '/all/ for 4chan,'+
            '8chan with top 50 boards,'+
            '8chan with top 100 boards,'+
            '8chan with top 500 boards,'+
            '8chan with all boards,'+
            '/all/ for KC'+
          '"><br>'+
          '1,4. Modify details if you want.<br>'+
          '2,<IC"easy2.virtualBoard.bl.show">VirtualBoards, shows '+
          '<ITB3"easy2.virtualBoard.bl.max"> virtual boards<br>'+
          '4,Local: <SE"easy2.VB.local,No,Passive,Active">&emsp;Global: <SE"easy2.VB.global,No,Passive,Active"><br>'+
          '2,<IC"easy2.liveTag.use">LiveTag, from: <SE"easy2.LTfrom,OP,Posts"><br>'+
          '2,<IC"easy2.catalog.embed">Use CatChan in catalog<br>'+
          '2,<IC"easy2.catalog.embed_page">Use CatChan in index page<br>'+
          '2,<IC"easy2.thread.embed">Use CatChan in thread<br>'+
          '2,<IC"easy2.auto_update">Auto update, every <ITB2"easy2.auto_update_period"> min.<br>'+
          '2,<IC"easy2.notify.desktop.notify">Desktop notification<br>'+
          '2,Misc.'+this.rollup(
            '3,<IC"easy2.filter.time.use"> <SE"easy2.filter.time.func,pn:Hide threads w/o posts in recent,cn:Hide threads created before,np:Watch threads posted in recent,nc:Watch threads created in recent"><ITB3"easy2.filter.time.tv"> hours<br>'+
//            '3,<IC"easy2.filter.time_watch">Hide all threads which have no posts in recent <ITB3"easy2.time_post"> hours<br>'+
//            '3,<IC"easy2.filter.time_watch_creation">Hide all threads which are created before <ITB3"easy2.time_op"> hours ago<br>'+
            '3,<IC"easy2.stats.use">Statistics<br>'+
            '3,<IC"easy2.healthIndicator.expand_running">Show details of running indicators<br>'+
            '3,<IC"easy2.network.fetch_actively">Fetch missing info actively<br>'+
//          '2,<IC"easy2.basics">Other basic settings, which depends on preset<br>'+
            '3,<IC"easy2.basics">Other basic settings<br>'+
            '3,Limits for safe: <br>'+
            '4,At scan: <ITB6"easy2.scan.max"> boards, <ITB6"easy2.scan.max_threads"> threads.<br>'+
            '4,In a catalog: <ITB6"easy2.catalog.max_threads_at_refresh"> threads.')+'<br>'+
          '1,5. Click <BTN"easy2.apply,Apply"><br>'+
          '1,6. Click <BTN"save,Save"> if you want to save your settings to localStorage<br>'+
          '1,7. Click <BTN"reload,Reload">, because some settings may need<br>';},
        'Easy Setting:': 'Easy Setting:<br>'+
          '1,VBs: virtual boards<br>'+
          '1,AU: auto update every X min<br>'+
          '1,<BTN"easy.virtualBoard_10_passive,Click"> 20 VBs / AU 10 min. / passive scan. (for all)<br>'+
          '1,<BTN"easy.virtualBoard_10,Click"> 20 VBs / AU 10 min. (for lainchan and meguca)<br>'+
          '1,<BTN"easy.virtualBoard_1,Click"> 100 VBs / AU 1 min. (for 4chan)<br>'+
          '1,<BTN"easy.virtualBoard_8_50,Click"> 100 VBs / AU 5 min from top <ITB3"easy.max_boards">'+
          ' boards. (for 8chan)<br>'+
//          '&emsp;<BTN"easy.virtualBoard_8_100,Click"> 100 VBs / AU 5 min from top 100 boards. (for 8chan)<br>'+
//          '&emsp;<BTN"easy.virtualBoard_8_500,Click"> 100 VBs / AU 5 min from top 500 boards. (for 8chan)<br>'+
          '1,<BTN"easy.virtualBoard_8_all,Click"> 100 VBs / AU 5 min from all boards. (for 8chan)<br>'+
          '1,<BTN"easy.virtualBoard_interSite,Click"> 100 VBs / AU 10 min from all sites. / passive scan. (for all)<br>'+
          '<br>'+
          '1,<BTN"easy.posts_0h,Click"> I want to check all new posts from now.<br>'+
          '1,<BTN"easy.posts_24h,Click"> I want to check all posts from <ITB3"easy.posts_ago">'+
          ' hours ago.<br>'+
          '1,<BTN"easy.threads_24h,Click"> I want to check all new threads from <ITB3"easy.threads_ago">'+
          ' hours ago.<br>'+
          '<br>'+
//          '&emsp;<BTN"easy.embed_index,Click"> Use CatChan in index page without infinite scroll.<br>'+
          '1,<BTN"easy.embed_index_lazy,Click"> Setup for narrow band.<br>'+
          '1,<BTN"easy.embed_index_infinite,Click"> Setup for infinite scroll.<br>'+
          '1,<BTN"easy.embed_index_backwash,Click"> Backwash style. (infinite scroll/quick auto update)<br>'+
          '<br>'+
          '1,<BTN"easy.light,Click"> Setup for light start up.<br>'+
          '',
        'Virtual Board:': function(){return ('Virtual Board:<br>'+
            '1,<IC"V.bl.show">Show virtual boards in boardlist, max:<ITB3"V.bl.max"><br>'+
            '2,<IC"V.search.show">Show search bar and controls, <IC"V.assb">auto shrink<br>'+
//            '3,<ICBX"V.search.re"> Regular Expression<br>'+
            '3,<IC"V.bl.dumb">Show only if search is active<br>'+
            '2,<IC"V.bl.showActives">Always show active tags <IC"V.bl.activesWOF">if search is inactive<br>'+
            '1,<IC"V.scan">Scan at start up, delay: <ITB2"V.scanDelay">s,<br>'+
            '3, or manual scan: <BTN"V.scanStart,Start"> <BTN"V.scanStop,Stop"><br>'+
            this.vb_domains()+
            '2,<IC"V.instant_scan">Instant scan<br>'+
            '1,Physical boards in boardlist:<br>'+
            '2,<IR"V.bl.p_board,both">Show as they are<br>'+
            '3,<IC"V.bl.p_remove">Remove virtual boards of the same name<br>'+
            '2,<IR"V.bl.p_board,replace">Replace with virtual boards<br>'+
//            '3,<ICBX"V.v_remove"> Remove filtered virtual boards<br>'+
            '1,Tagging about physical boards in virtual boards:<br>'+
            '2,<IR"V.vtag,add">Add tag of virtual board\'s name to physical boards<br>'+
            '3,<IC"V.vtagsel">Select the tag<br>'+
            '2,<IR"V.vtag,no">Don\'t add any tags to physical boards<br>'+
            '3,<IC"V.vptagsel">Select tags of all virtual/physical boards\' names<br>'+
            '1,Tagging at switching board groups:<br>'+
            '2,<IR"V.ts,cr">Restore previous tags\' state<br>'+ // clear and restore
            '2,<IR"V.ts,cs">Clear and set requisite tags<br>'+ // clear and set 
            '2,<IR"V.ts,ns">Set requisite tags and keep others<br>'+ // no and set
            '2,<IR"V.ts,nn">No. Keep all tags<br>'+ // no
            '1,Click function of tags on:<br>'+
            '2,boardlist / threads / Ctrl-click<br>'+
            '2,<IR"L_bl,pk"><IR"L,pk"><IR"L_ctrl,pk">None -&gt; Fetch -&gt; None<br>'+
            '2,<IR"L_bl,pkin"><IR"L,pkin"><IR"L_ctrl,pkin">None -&gt; Fetch+In -&gt; None<br>'+
            '2,<IR"L_bl,pk_in"><IR"L,pk_in"><IR"L_ctrl,pk_in">None -&gt; In (or Fetch if vacant) -&gt; None<br>'+
            '2,<IR"L_bl,in"><IR"L,in"><IR"L_ctrl,in">None -&gt; In -&gt; None<br>'+
            '2,<IR"L_bl,inex"><IR"L,inex"><IR"L_ctrl,inex">None &lt;-&gt; In &lt;-&gt; Out &lt;-&gt; None (shift for backward)<br>'+
            '2,<IR"L_bl,ex"><IR"L,ex"><IR"L_ctrl,ex">None -&gt; Out -&gt; None<br>').replace(/"V\./g,'"virtualBoard.').replace(/"L/g,'"liveTag.click_func');},
        'Catalog:': function(){
            return this.show_hide_all_to_header('Catalog:') + '<br>'+
              '1,<IC"catalog.embed">Use CatChan in catalog<br>'+
              '2,<IC"catalog.infoPv.use">Info preview hover'+this.rollup(this.popup_delay('catalog.infoPv.'))+'<br>'+
              this.expand_thumbnail_hover('catalog')+
              this.popup_others('catalog')+
              '2,<IC"catalog.open_spoiler_text">Open text spoiler<br>'+
              this.add_indent(2,this.updates('catalog'))+
              '2,<IC"catalog.mark_new_posts">Mark new posts at opening threads<br>'+
              '2,'+ this.rollup_radio('catalog.click_area', ' :Click to open the thread', 3, '',
                ['thumbnail:Thumbnail','entire:Entire thread card'])+ '<br>'+
              '2,'+ this.popup3('catalog',3)+
//              '2,'+ this.rollup_radio(!pref.test_mode['110']? 'catalog.popup3.ww':'catalog.popup2',' :Pop up', 3,
//                '3,WW/WN/NW/NN (Wider/Narrower) side:'+this.rollup(this.popup_delay('proto.popup3.'))+'<br>',
//                ['no:No','sr:Search result','srpv:Search result or preview','pv:Preview','chart:Post rate chart (statistics must be activated)','dp:Deleted posts'], '',['catalog.popup3.wn','catalog.popup3.nw','catalog.popup3.nn'])+ '<br>'+
              '2,'+ this.rollup_radio('catalog.t2h_sel',' :Num of posts for preview or footer', 3, '',
                ['no:No','N:Last <ITB3"catalog.t2h_num_of_posts"> posts (N posts)',
                 'N_unread:N + all unread posts', 'unread:All unread posts', 'ALL:All posts' //, ALL_agg:All posts from all scanned threads'
                ]) +'<br>'+
              this.posts_for_search('catalog')+
              this.op_in_search_result('catalog')+
              this.deleted_posts_detection('catalog')+'<br>'+
              '2,<IC"catalog.detect_sage">Detect saged posts in 4chan<br>'+
              '3,<IC"catalog.page_his">Store page history in 4chan<br>'+
              this.backlink('catalog')+
              this.save_idx_promiscuous('catalog')+
              this.merge_all('catalog')+
              '1,<IC"catalog.embed_archive">Generate catalog in archive'+this.rollup(
                '5,from top <ITB5"scan.max_archive"> threads')+'<br>'+
              '<br>'+
              '1,Environment values:(advanced option)'+ this.rollup(
                '2,You are using native function of:<br>'+
                '3,<IC"catalog.env.popup_native">Pop up reply<br>'+
                '3,<IC"catalog.env.image_hover_native">Image hover<br>'+
                '2,<IC"catalog.env.refresh_initial">Refresh at initial<br>'+
//                '2,Custom size:<br>'+
//                '3,With OP'+this.design_of_fc_1('catalog','ec')+'<br>'+
//                '3,Without OP'+this.design_of_fc_1('catalog','c')+'<br>'+
                this.resize_thumbnails('catalog')) + '<br>';},
//                this.native_html_config('catalog.env')) + '<br>';},
        'Index Page:': function(){
            return this.show_hide_all_to_header('Index Page:') + '<br>'+
              '1,<IC"catalog.embed_page">Use CatChan in index page' + this.rollup(
                '3,Refresh up to:<br>'+
                '4,<IR"catalog_max_page_select,manual"> <ITB2"catalog_max_page"> pages<br>'+
                '4,<IR"catalog_max_page_select,auto"> Max pages<br>'+
                '5,<IC"catalog_max_page_auto">Update automatically') +'<br>'+
    //          '&emsp;&emsp;<IC"page.infinite">Infinite scroll<br>'+
              this.popup('page')+
              this.expand_thumbnail_inline('page')+
              this.expand_thumbnail_hover('page')+
              this.popup_others('page')+
              this.format_posts('page')+
    //          '&emsp;&emsp;<IC"page.scan_tag">Scan tags at initial<br>'+
              this.t2h('page','Num of posts to show')+
//              '2,' + this.rollup_radio('page.t2h_sel', ' :Num of posts to show', 3, '',
//                ['page:Inherit from index page',
//                 'L: <ITB3"page.t2h_L"> posts(L posts)',
//                 'M: <ITB3"page.t2h_M"> posts(M posts)',
//                 'N: <ITB3"page.t2h_num_of_posts"> posts(N posts)',
//                 'N_unread:N + all unread posts',
//                 'unread:All unread posts',
//                 'ALL:All posts',
////                 'ALL_agg:All posts (for incremental posts search)',
//                ]) +'<br>'+
              this.posts_for_search('page')+
              this.op_in_search_result('page')+
              this.add_indent(2,this.updates('page'))+
              this.merge_all('page')+
              '2,<IC"page.scroll_lock">Don\'t scroll at update<br>'+
              this.save_idx_promiscuous('page')+
              this.resize_thumbnails('page')+
              '2,<IC"page.hide_posts_without_images">Hide posts without images<br>'+
              '<br>'+
              this.env('page.env','');},
        'Thread:': function(){
            return this.show_hide_all_to_header('Thread:') + '<br>'+
              '&emsp;<IC"thread.embed">Use CatChan in thread<br>'+
              this.popup('thread')+
              this.expand_thumbnail_inline('thread')+
              this.expand_thumbnail_hover('thread')+
              this.popup_others('thread')+
//              (pref.test_mode['121']? this.popupX('thread','proto.popupID.', 'Pop up ID') + this.popupX('thread','proto.popupFlag.','Pop up Flag'):'')+
              this.format_posts('thread')+
              this.posts_for_search('thread')+
              this.add_indent(2,this.updates('thread'))+
              this.merge_all('thread')+
              this.t2h('thread','Num of posts to show at initial merging')+
              '2,<IC"archive.IDB.redirect_404">Open archive from IDB if 404'+this.rollup(
                '3,<IC"archive.IDB.redirect_404_CSP">Open blob to bypass Content Security Policy')+'<br>'+
    //          '&emsp;<IC"thread.save_board_list_sel">Save board group index<br>'+
              '2,<IC"thread.hide_posts_without_images">Hide posts without images<br>'+
              '<br>'+
              this.env('thread.env', '<br>3,<IC"thread.env.auto_update_native">Auto update');},
        'Watcher/FC:': function(){
        return 'Watcher / Floating Catalog:<br>'+
          '1,Default settings:<br>'+
          '2,<IR"float.view,headline">Headline: <SE"headline.subStyle,SingleLine,MultiLines">, max<ITB4"headline.max_letters">letters<br>'+
          '2,<IR"float.view,catalog">Catalog: <SEC"catalog.format.thumb.size,small,large,custom"><SE"catalog.format.op,on:w/ OP,off:w/o OP"><br>'+
          '2,<IR"float.view,page">Page:<br>'+
//          '2,'+ this.design_of_fc('float',3)+', max<ITB4"headline.max_letters">letters in headline<br>'+
          (this.design_of_fc_size()? '3,'+ this.design_of_fc_size() +'<br>' : '')+
          this.add_indent(2,this.updates('float'))+
//          '2,<IC"float.refresh_by_watchlist">Refresh according to watchlist (watcher mode)<br>'+
          '2,<IC"float.hide_unwatched">Show watched threads only (watcher mode)<br>'+
          '2,<IC"liveTag.NC">Separated virtual board for each instance<br>'+
          this.merge_all('float')+'<br>'+
          '3,<IC"headline.merge_truncated">Truncate members in headline view<br>'+
          '2,<IC"catalog.appearance.orderOnTB">Order selector on title bar<br>'+
          '2,'+this.rollup_radio('catalog.appearance.initial.state',' :At initial, <ITBN4"catalog.appearance.initial.width">x<ITB4"catalog.appearance.initial.height">(0 means auto)', 3, '',
            ['max:Maximized',
             'float:Floating, left:<ITB4"catalog.appearance.initial.left">px, top:<ITB4"catalog.appearance.initial.top">px',
             'top:Embed to top', 'bottom:Embed to bottom'])+'<br>'+
          '2,<IC"float.disable_kwd_filter_at_initial">Disable keyword filter at initial<br>'+
          '1,'+ this.popup3('float',2)+
          '1,<IC"float.auto_launch">Auto start an instance at initial<br>'+
          '1,<IC"float.env.refresh_initial">Refresh at initial<br>'+
          this.add_indent(-1, this.save_idx_promiscuous('float'))+
////          '1,'+this.resize_thumbnails('float')+
//          (pref.test_mode['147']? this.add_indent(-1,this.merge_all('float')) : '')+
          '<br>'+
          '1,Advanced options:<br>'+
          '2,<IC"common.consolidated_watch_list">Consolidated watch list (for watcher mode)<br>'+
          '3,<IC"common.sync_watch_list">Sync watch list with other tabs<br>'+
          '2,'+this.broadcast_triage('watch', null, ' (ignored if consolidated)')+
          '2,'+this.broadcast_triage('attr')+
          '2,'+this.broadcast_triage('ex', 'exclude')+
//          '2,<IC"triage.broadcast_attr">Sync attributes list of all watcher<br>'+
//          '2,<IC"triage.broadcast_time">Sync hide/watch time list of all watcher<br>'+
          '2,<IC"liveTag.watch_all">Watch all boards<br>'+
          '';},
        'Gerenal 0': function(){
            return 'General 0:<br>'+
          '1,Open short link<br>'+
          '2,<IR"catalog_open_last50,no">No. Open entire thread<br>'+
          '2,<IR"catalog_open_last50,exist">If it exists<br>'+
          '2,<IR"catalog_open_last50,exist_watch">If it exists and all unread posts are in it<br>'+
////          '2,<IR"catalog_open_last50,speculative">If it is estimated to exist<br>'+
////          '2,<IR"catalog_open_last50,spec_watch">If it is estimated to exist and unread posts are less than 50<br>'+
          '1,Open the thread in<br>'+
//          '3,<IC"catalog_open_where_click">Apply to reply links in index page<br>'+
          '2,<IR"catalog_open_where,_self">this tab<br>'+
          '2,<IR"catalog_open_where,_blank">new tab always<br>'+
          '2,<IR"catalog_open_where,named">named tab<br>'+
          '2,<IR"catalog_open_where,CatChan_tgt">a fixed tab<br>'+
//          '2,<IC"catalog_use_named_window">Prevent opening a thread in multiple tabs<br>'+
          '2,<IC"pref2.meguca.historyAPI">Use historyAPI in meguca<br>'+
          '1,At opening thread ... :<br>'+
          '2,<IC"auto_watch.watch">Add to watch list<br>'+
          '2,<IC"auto_watch.hide">Hide the thread <SE"auto_watch.hide_com,KILL:permanently,TIME:until it gets new replies"><br>'+
          '2,<IC"auto_watch.style">Add style: <ITBL30"auto_watch.style_str"><br>'+
          '2,'+this.inlineBlock('<IC"auto_watch.triage">Additional triage commands'+this.rollupHelp('How to', '1,<TA"samples.how_to_triage" disabled>')+'<br>'+
            '1,<TA"auto_watch.triage_str" placeholder="NONE,,background:lightgray">')+'<br>'+
          '1,Triage:<br>'+
          '2,Use hover triage on and where<br>'+
          this.triage_hover('catalog')+
          this.triage_hover('page')+
          this.triage_hover('thread')+
          this.triage_hover('headline')+
//          '2,Where to show:<br>'+
//          '3,<span style="display:inline-block">'+
//            '<IR"catalog_triage_place,topLeft">Top left<br>'+
//            '<IR"catalog_triage_place,bottomLeft">Bottom left'+
//          '</span>'+
//          '<span style="display:inline-block">'+
//            '<IR"catalog_triage_place,topRight">Top right<br>'+
//            '<IR"catalog_triage_place,bottomRight">Bottom right'+
//          '</span><br>'+
          '2,<IC"triage.popdown">Auto pop down with delay <ITBN4"triage.popdown_delay">ms<br>'+
          '2,<IC"triage.hide_toggle">Hide useless toggle buttons<br>'+
          '2,Configurations of triages:<br>'+
          '3,'+this.triage_config('catalog_triage_str', 'Hover', 'simple_kill,simple:simple (default),basic,basic_color,colorful,borders,toggles,samples,test')+'<br>'+
//          '<!-- &emsp;<IC"catalog_enable_cross_board">Enable cross-board catalog<br> -->'+
//          '<!-- &emsp;<IC"catalog_enable_cross_domain">Enable cross-domain catalog<br> -->'+
//          '<!-- &emsp;&emsp; Cache working in <textarea style="height:1em" cols="20" name="catalog_sw_domain"></textarea><br> -->'+
//          '2,Load sample to hover triage:<br>'+
//          '3,<BTN"triage.sample.simple_kill"> <BTN"triage.sample.simple"> <BTN"triage.sample.basic"> <BTN"triage.sample.basic_color"><br>'+
//          '3,<BTN"triage.sample.colorful"> <BTN"triage.sample.borders"> <BTN"triage.sample.toggles"> <BTN"triage.sample.samples"> <BTN"triage.sample.test"><br>'+
          '3,'+this.triage_config('triage.menu_str', 'Menu', 'menu_basic:basic (default),menu_attr:attr,menu_full:full')+'<br>'+
          '3,'+this.triage_config('triage.postMenu_str', 'PostMenu', 'simple:simple (default),basic,full')+'<br>';},
//          '2,z-Index: <ITB4"triage.zIndex"><br>';},
        'General 1': function(){
            return 'General 1:<br>'+
          '1,Click to:<br>'+
          '2,<IR"float.click,open">Go to/open the thread<br>'+
          '2,<IR"float.click,expand">Expand/shrink the OP in catalog<br>'+
//          '4,<IC"catalog_expand_at_initial">Expand at initial<br>'+
//          '4,<IC"catalog_expand_at_initial_embed">Expand at initial in index.<br>'+
          '4,<IC"catalog_expand_with_hr">Insert horizontal splitter when it is expanded<br>'+
          '3,<IC"catalog_no_popup_at_expanded">Don\'t popup when the catalog is expanded<br>'+
          '2,<IR"float.click,none">None<br>'+
          '1,Click area:<br>'+
          '2,<IR"float.click_area,thumbnail">Thumbnail<br>'+
          '2,<IR"float.click_area,entire">Entire thread card<br>'+
//          '1,<IC"float.popup2">Use pop-up window<br>'+
//          '2,appear/disappear:<br>'+
//          '3,<IR"catalog_popdown,imm">immediately<br>'+
//          '3,<IR"catalog_popdown,delay">delayed: <ITB4"catalog_popup_delay"><ITB4"catalog_popdown_delay"> ms<br>'+
          '1,Window handling<br>'+
          '2,grab to move / select text:<br>'+
          '3,<IR"proto.popup2_sel,move">Move always<br>'+
          '3,<IR"proto.popup2_sel,auto">Auto, padding-x:<ITB4"proto.popup2_sel_tolerance"> px<br>'+
          '3,<IR"proto.popup2_sel,sel">Select always<br>'+
          '3,<IC"catalog_popup_size_fix">Fix size when you move it<br>'+
          '2,<IC"proto.popup2_resize">Resize on borders of <ITB4"proto.popup2_resize_bw"> px<br>'+
          '3,Size of corner angle: <ITB4"proto.popup2_resize_cw"> px<br>'+
          '1,Appearance:'+this.rollup(
            '2,catalog/page/thread/watcher<br>'+
            '2,'+this.appearance('dummy','" style="visibility:hidden')+'Shows in title bar:<br>'+
            '2,'+this.appearance('titleBar.filter')+'Filter button<br>'+
            '2,'+this.appearance('titleBar.settings')+'Settings button<br>'+
            '2,'+this.appearance('titleBar.refresh')+'Refresh button<br>'+
            '2,'+this.appearance('titleBar.boards_selector')+'Boards selector<br>'+
  //          '2,<IC"catalog_localtime"> Localtime<br>'+
            '2,'+this.appearance('dummy','" style="visibility:hidden')+'Expands in filters panel by default<br>'+
            '2,'+this.appearance('expand.kwd')+'Keyword filter<br>'+
            '2,'+this.appearance('expand.time')+'Time filter<br>'+
            '2,'+this.appearance('expand.tag')+'Tag filter<br>'+
            '2,'+this.appearance('expand.list')+'List filter<br>'+
            '2,'+this.appearance('expand.ctrl')+'Filter controls')+'<br>'+
              '<br>'+
              '1,<IC"catalog.embed_frame">Embed to frame<br>'+
              '2,Frame size : '+
              '2,<ITB4"catalog_size_frame0_width">%, '+
              '2,<ITB4"catalog_size_frame1_width">%<br>'+
    //          '1,Max threads in catalog: <ITB4"catalog.max_threads"><br>'+
              '2,<IC"catalog.refresh.except_bt">Except the page of selecting boards\' tag.<br>'+
              '1,<IC"catalog.refresh.at_switch">Clear and refresh when boards are switched<br>'+
              '1,<IC"common.clear_at_manual_scan">Clear all threads at manual scan<br>'+
              '1,'+ this.rollup_radio('common.watch_list_rm404',' :Remove 404 threads in watch list', 2, '',
                ['imm:Immediately','unread:Keep them if they have unread posts<br>4,(can\'t catch unread posts during offline)',
                 'no:No, user interaction is need to remove<br>2,<IC"archive.IDB.auto_restore_watch">Auto restore pruned threads from IDB archive']) +'<br>'+
//              '1,<IC"common.watch_list_fromAutoKwd">Auto keyword filter adds items to watch list<br>'+
//              '1,<IC"common.watch_list_fromTimeFilter">Time filter adds items to watch list<br>'+
              '1,'+ this.rollup_radio('liveTag.rm_404',' :Remove 404 threads', 2, '',
                ['imm:Immediately','watched:Keep watched threads','unread:Keep watched threads if they have unread posts', 'no:No, keep all threads remain']) +'<br>'+
              '1,<IC"common.blur_404">Blur 404 threads<br>'+
              '1,<IC"catalog.bookmark_list_rm404">Remove 404 threads from bookmark<br>'+
              '1,'+ this.rollup_radio('common.merge_list_rm404',' :Remove 404 threads in merge list', 2, '',
                ['imm:Immediately',/*'UI:Wait 404 for threads merged by UI(NOT IMPLEMENTED)',*/'dead:Wait 404(the thread can\'t be merged anymore)', 'no:No, keep all threads remain']) +'<br>'+
              '1,<IC"pref2.8chan.utilize_boards_json">Utilize boards.json in 8chan<br>'+
              '1,<IC"pref2.meguca.utilize_boards_json">Utilize boardTimestamps in meguca<br>'+
              '2,<IC"pref2.meguca.utilize_boards_json_domain">Utilize boardTimestamps at refresh in meguca<br>'+
              '1,<IC"common.consolidated_filter">Use consolidated filter<br>';},
        'Board Group': function(){return 'Board Group:<br>'+
          '1,'+this.ta_with_help('catalog_board_list','Board group configuration',true,null,true)+
//          '1,<TA"catalog_board_list_str">'+this.rollup(
          '2,Obsolete functions...'+this.rollup(
          '3,<IC"catalog.board.recommendation">Read owner\'s recommendation <BTN"tag.recommendation_add,Add to list"><br>'+
//          '&emsp;&emsp;<input type="button" value="Scan"> Scan board tags<br>'+
//          '&emsp;&emsp;<input type="button" value="Generate" name="tag.generate"> Generate board groups from tags <br>'+
          '3,<IC"catalog.board.board_tags">Generate board groups from tags <BTN"tag.scan,Scan"><br>'+
              '3,<IC"catalog.board.board_tags_same">Pick up boards which have the same tag <BTN"tag.same_tag_refresh,Refresh">',null,true)+'<br>'+
          '2,<IC"catalog.board.all_boards">Add all boards group to the last<br>'+
          '1,'+this.ta_with_help('catalog.board.ex_list','Use global exclusive list',true)+
          '1,'+this.ta_with_help('catalog.style_general_list', 'Use global style', true)+
          '1,'+this.ta_with_help('postFilter', 'Use global post filter')+
//          '1,<IC"catalog.mimic_base_site">Mimic base site<br>'+
          '1,'+this.ta_with_help('RSS', 'RSS boards configuration', null, null, true)+
          '2,Auto generated temporal configurations(Read only)'+this.rollup(
            '3,You can promote these settings to be permanent in RSS dashboard,<br>'+
            '3, or copy-paste these to RSS boards configurations(upper textarea)',null,true)+'<br>'+
          '2,<TA"samples.dashboard.rss.auto" disabled><br>'+
          '1,<span class="JSON_result"></span><br>';},
//          '1,<BTN"RSS.dashboard"><br>'+
//          '1,<div class="dashboard">'+this['RSS dashboard']()+'</div><br>'+
////////          '&emsp;Tagging:<br>'+
////////          '&emsp;&emsp;Ignore tags latter than <ITB2"catalog.tag.ignore">th in a board/thread<br>'+
////////          '&emsp;&emsp;Ignore boards/threads which have more than <ITB2"catalog.tag.max"> tags<br>'+
        'Live Tag': function(){return ('Live Tag:<br>'+
          '1,Configuration of extraction:<br>'+
          '3,From:<br>'+
          '4,<IR"L.from,op">OP<br>'+
          '4,<IR"L.from,post">All posts<br>'+
          '3,<IC"L.use">Live<br>'+
//          '4,<ICBX"stats.use"> Take statistics<br>'+
          '3,max tags in a thread: <ITB2"L.max"><br>'+
          '3,max letters in a tag: <ITB2"L.maxstr"><br>'+
          '2,<IC"L.ci">Case insensitive<br>'+
          '2,<IC"L.inherit_board_name">Threads inherit board\'s name as a tag<br>'+
          '3,<IC"L.lock_board_name">Lock & sticky<br>'+
          '2,<IC"L.inherit_board_tags">Threads inherit all tags from board<br>'+
          '3,<IC"L.lock_board_tags">Lock & sticky<br>'+
          '2,<IC"L.lock_tags_in_op">Lock & sticky tags in OP<br>'+
          '1,<IC"L.info">Show infomation on hover<br>'+
          '1,<IC"L.ex_list">Use exclusive list<br>'+
          '2,'+this.ta_with_help('liveTag.rm_list','String list, applied before extraction (costs much)',true,null,true)+
//          '2,String list to remove before extraction (costs much)<br>'+
//          '3,<TA"L.rm_list_str"><br>'+
          '2,'+this.ta_with_help('liveTag.ex_list','Tag list, applied after extraction (costs less)',true,null,true)+
//          '2,Tag list to remove after extraction (costs less)<br>'+
//          '3,<TA"L.ex_list_str"><br>'+
          '1,Styles:'+this.rollup(
//          '2,Selected:<ITBL30"LS.in"><br>'+
//          '2,Excluded:<ITBL30"LS.ex"><br>'+
//          '2,<IC"LS.use">Add style to tags if unread posts in the thread:<br>'+
//          '3,Unread replies to me:<ITBL30"LS.urtm"><br>'+
//          '3,Unread replies:<ITBL30"LS.ur"><br>'+
//          '3,Unread replies to me + selected:<ITBL30"LS.inurtm"><br>'+
//          '3,Unread replies + selected:<ITBL30"LS.inur"><br>'+
//          '3,Unread replies to me + excluded:<ITBL30"LS.exurtm"><br>'+
//          '3,Unread replies + excluded:<ITBL30"LS.exur"><br>'+
          '<div style="display:inline-block">&emsp;&emsp;</div><div style="display:inline-block"><table><thead>'+this.expand_row('There,Unread replies,Unread replies,No unread,','th')+
          this.expand_row('are,to me,,replies,','th')+'</thead><tbody>'+
          this.expand_row('Excluded:,<ITBL16"LS.exurtm">,<ITBL16"LS.exur">,<ITBL16"LS.ex">')+
          this.expand_row('Selected:,<ITBL16"LS.inurtm">,<ITBL16"LS.inur">,<ITBL16"LS.in">')+
          this.expand_row('Picked:,<ITBL16"LS.pkurtm">,<ITBL16"LS.pkur">,<ITBL16"LS.pk">')+
          this.expand_row('None:,<ITBL16"LS.urtm">,<ITBL16"LS.ur">,')+'</tbody></table></div><br>'+
          '2,<IC"LS.use">Add style to tags if unread posts in the thread:')+'<br>'+
          '1,Update intervals of boardlist and search results:'+this.rollup(
          '2,Foreground: <ITB4"L.disp_delay.fg">ms<br>'+
          '2,Background: <ITB4"L.disp_delay.bg">ms<br>'+
          '2,Lazy draw of incremental search:<br>'+
          '3,every <ITB4"L.lazy_each"> tags with <ITB4"L.lazy_delay"> ms delay')+'<br>').replace(/"LS\./g,'"L.style.').replace(/"L\./g,'"liveTag.');},
        'Statistics': function(){
            return 'Statistics:<br>'+
////            '&emsp;&emsp;Statistics is a subfunction of the LiveTag,<br>'+
////            '&emsp;&emsp;You must activate it beforehand and RELOAD to start.<br>'+
////            '&emsp;&emsp;Then, the "Graph" button will appear in the setting button([CC])<br>'+
//////            '&emsp;&emsp;And this function uses both local and server timestamps,<br>'+
//////            '&emsp;&emsp;your local clock must be accurate.<br>'+
////            '<span style="float:left,&emsp;&emsp;<BTN"easy.stat_activate,Activate"></span>'+
////            '<span>'+
////              '&emsp;<IC"stats.use">Take statistics<br>'+
////              '&emsp;<IC"stats.estimate_posts">Estimate num of posts from posts\' No.<br>'+
////            '</span>'+
////            '<br>'+
            '1,<IC"stats.use">Take statistics<br>'+
            '3,<IC"stats.estimate_posts">Estimate num of posts from posts\' No.<br>'+
            '4,<IC"stats.patch_tm">Patch for thread move<br>'+
            '2,Default settings:<br>'+
            '3,Time unit: '+ chart_obj.time_unit_sel_html + '<br>' +
            '3,' + chart_obj.chart_options_str.join('3,') +
            '3,Size of window: <ITB4"chart.window_width"> x <ITB4"chart.window_height"><br>'+
            '<br>'+
//            '&emsp;Data of pruned threads:<br>'+
//            '&emsp;&emsp;<IC"stats.retain_404">Retain values of pruned thread<br>'+
            '2,Accumulation:<br>'+
            '3,<IC"stats.load">Load data<br>'+
            '4,<IC"stats.save">Save data: <ITB4"stats.len_capture"> points<br>'+
            '4,Auto acquisition of data:<br>'+
            '5,<IC"stats.auto_acquisition_all">All board groups which you saw once<br>'+
            '5,<IC"stats.auto_acquisition">Board groups with \'!stats\'<br>'+
            '6,<IC"stats.auto_acquisition_scan">Scan all targets at startup with '+
            '<ITB2"stats.auto_acquisition_scan_delay"> secs delay<br>'+
            '5,(Targets are colored <span style="background:#b5fbda">green</span> in board selector)<br>'+
            '<br>'+
            '2,<IC"chart.instant_scan">Instant scan at switching board group<br>'+
            '2,Delay to draw: <ITB2"stats.draw_delay"> sec.<br>'+
            '2,<IC"chart.off_anime_blur">Off animation in background<br>'+
            '2,<IC"stats.tolerant">Tolerant of inaccuracy of server and local clocks<br>'+
            '5,up to <ITB2"stats.tolerance"> min<br>'+
            '';
          },
//        function(){return 'Design of Floating Catalog:<br>'+
////          '<input type="radio" name="catalog.text_mode.mode" value="graphic"> Graphical mode: '+
////          '<ITB4"catalog_size_width"> x '+
////          '<ITB4"catalog_size_height"><br>'+
////          '&emsp;&emsp;&emsp;1st thumbnail: '+
////          '<ITB4"catalog_size_tn1_width"> x '+
////          '<ITB4"catalog_size_tn1_height"><br>'+
////          '&emsp;&emsp;&emsp;2nd and later: '+
////          '<ITB4"catalog_size_tn2_width"> x '+
////          '<ITB4"catalog_size_tn2_height"><br>'+
//          '&emsp;Catalog/Pop-up/Search<br>'+
//          '<!-- &emsp;&emsp;<input type="checkbox" name="catalog_border_show">&emsp;&emsp;&emsp; Show border<br> -->'+
//          '<!-- &emsp;&emsp;<input type="checkbox" name="catalog_enable_background">&emsp;&emsp;&emsp; Use backgfound color<br> -->'+
//          '&emsp;Num of posts in thread headline: <ITB3"float.t2h_num_of_posts"><br>'+
////          '<input type="radio" name="catalog.text_mode.mode" value="text"> Text mode: '+
////          '<ITB4"catalog_size_text_width"> x '+
////          '<ITB4"catalog_size_text_height"><br>'+
////          '&emsp;<IC"catalog.text_mode.sub">Show title<br>'+
////          '&emsp;<IC"catalog.text_mode.name">Show op\'s name<br>'+
////          '&emsp;<IC"catalog.text_mode.com">Show op\'s comment<br>';
//          ''},
        'Footer': function(){return this.show_hide_all_to_header(
            'Footer for <SE"settings.footer_sel,embedded\\, headline view,embedded\\, catalog view,embedded\\, page view,watcher / floating catalog">')+'<br>'+
          this.footer(['headline','catalog','page','float'][pref.settings.footer_sel]);},
//        'Footer': function(){return 'Footer:<br>'+this.footer('catalog');},
//        'Footer for watcher': function(){return 'Footer for watcher:<br>'+this.footer('float');},
        'ThreadReader': function(){return 'ThreadReader:<br>'+
//          '1,<IC"catalog.order.find_sage_in_8chan">Find sage post in native catalog in 8chan<br>'+
          '1,<IC"thread_reader.unmark_on_hover">Unmark post on hover<br>'+
          '1,<IC"thread_reader.use">Thread reader<br>'+
          '2,<IC"thread_reader.own_posts_tracker">Own posts tracker<br>'+
          '4,>> (You)<br>'+
          '4,<IR"thread_reader.show_reply_to_me_by,anchor"> anchor text<br>'+
          '4,<IR"thread_reader.show_reply_to_me_by,plain"> plain text (for dollchan)<br>'+
          '4,(You) in name field<br>'+
          '4,<IR"thread_reader.show_own_post_by,anchor"> name string<br>'+
          '4,<IR"thread_reader.show_own_post_by,plain"> plain text<br>'+
          '3,<IC"thread_reader.clean_up_own_posts">Clean up localStorage at loading embed native catalog<br>'+
          '2,<IC"thread_reader.sync">Sync with parent catalog<br>'+
          '3,<IC"thread_reader.triage">Show triage to parent catalog<br>'+
//          '3,<IC"thread_reader.triage_close">Close window when triage is clicked<br>'+
          '2,<IC"IDinfo.use">ID counters'+this.rollup(
          '4,<IC"IDinfo.auto">Auto off when OP doesn\'t have ID or flag')+'<br>'+
          this.IDinfo('ID',true)+
          this.IDinfo('ID2',true)+
          this.IDinfo('flag',false,'ID','nof flagged IDs')+
          this.IDinfo('name',false,'trip','nof named trips')+
          this.IDinfo('trip',false,'name','nof tripped names');},
//          '3,<IC"IDinfo.ID">nth/all by each ID<br>'+
//          '4,<IC"IDinfo.nofIDs">nth/all/nof IDs at that time<br>'+
//          '4,<IC"IDinfo.IDop">Add \'OP\'<br>'+
//          '3,<IC"IDinfo.ID2">nth/all by each ID2<br>'+
//          '4,<IC"IDinfo.nofID2s">nth/all/nof ID2s at that time<br>'+
//          '4,<IC"IDinfo.ID2op">Add \'OP\'<br>'+
//          '3,<IC"IDinfo.flag">nth/all by each flag<br>'+
//          '4,<IC"IDinfo.flagID">nth/all/nof IDs of the same flag<br>'+
//          '4,<IC"IDinfo.nofFlags">nth/all(/nofIDs)/nof flags at that time<br>'+
//          '3,<IC"IDinfo.name">nth/all by each name<br>'+
//          '4,<IC"IDinfo.nameTrip">nth/all/nof trips of the same name<br>'+
//          '4,<IC"IDinfo.nofNames">nth/all(/namedTrip)/nof names at that time<br>'+
//          '3,<IC"IDinfo.trip">nth/all by each trip<br>'+
//          '4,<IC"IDinfo.tripName">nth/all/nof names of the same trip<br>'+
//          '4,<IC"IDinfo.nofTrips">nth/all(/trippedName)/nof trips at that time<br>';},
        'Notifiers': function(){return 'Notifiers:<br>'+
            '1,<IC"notify.desktop.notify">Desktop Notification'+ this.notify_init('desktop')+
            '3,<ITB3"notify.desktop.lifetime"> secs (0 means permanent)<br>'+
            '3,max: <ITB3"notify.desktop.limit">, '+
              'delay: <ITB3"notify.desktop.delay">ms<br>'+
            '3,<IC"notify.desktop.show_last">Show the last post only<br>'+
            this.notify_tgts('desktop')+
            '1,<IC"notify.favicon">Favicon<br>'+
            '1,<IC"notify.title.notify">Show number of unread replies in title<br>'+
            '2,<IC"notify.title.hide_zero">Hide unread count in title bar when it is zero<br>'+
            '1,<IC"notify.sound.notify">Sound&emsp;&emsp;<BTN"notify.sound.pause,Pause">'+ this.notify_init('sound')+
            '3,<IR"notify.sound.src,beep">Beep '+
              '&emsp;freq:<ITB4"notify.sound.beep_freq"> '+
              'length:<ITB4"notify.sound.beep_length_f"> '+
              'volume:<ITB4"notify.sound.beep_volume_f"><br>'+
            '3,<IR"notify.sound.src,file">File '+
            '&emsp;<input type="file" accept="audio/*" name="notify.sound.file"><br>'+
            this.notify_tgts('sound');
          },
        'JSON Archiver: Store': function(){
            archiver.event_funcs['queryQuota']();
            return 'JSON Archiver: Store<br>'+
//            '1,Store:<br>'+
            '<br>'+
            '1,1. Make sure DOWNLOAD DIRECTORY to be set correctly.'+this.rollup(
              '3,All files will be into the directory.<br>'+
              '3,And also, make sure each download doesn\'t ask you anything.<br>'+
              '3,So many files will be downloaded.<br>'+
              '2,<IC"archive.editing_timeout">Timeout for editing posts (patch for meguca)') +'<br>'+
            '1,2. Select sources for each target:<br>'+
            '2,File: Manual/Auto<br>'+
            '3,<IC"archive.oneshot.post"><IC"archive.live.post">Posts<br>'+
            '3,<IC"archive.oneshot.tn"><IC"archive.live.tn">Thumbnails<br>'+
            '3,<IC"archive.oneshot.img"><IC"archive.live.img">Images (original size)<br>'+
            '3,<IC"archive.oneshot.webm"><IC"archive.live.webm">Webms<br>'+
            '2,IndexedDB:'+this.rollup(
              '4,<IC"archive.IDB.auto_clean">Automatic clean up<br>'+
              '4,<IC"archive.IDB.auto_clean_init">Automatic clean up at start up<br>'+
              '4,Delayed pruning: <ITB3"archive.IDB.prune"> hours<br>'+
              '5,<IC"archive.IDB.prune_flush">Archive to file at pruning<br>'+
              '4,num of transactions: <ITB3"archive.IDB.nof_tr"><br>'+
              '4,num of requests in a cluster: <ITB3"archive.IDB.nof_cl"> - <ITB3"archive.IDB.nof_cl_max"><br>'+
              '4,watchdog timer: <ITB3"archive.IDB.watchdog">s')+'<br>'+
            '4,used / limit: <span name="SHOW_QUOTA"></span><button type="button" name="archive.queryQuota"><img src="' + cnst.icons.refresh + '" style="width:1em;height:1em"></button><br>'+
            '3,Manual/Auto<br>'+
            '3,<IC"archive.oneshot.post_idb"><IC"archive.live.post_idb">Posts<br>'+
            '3,<IC"archive.oneshot.tn_idb"><IC"archive.live.tn_idb">Thumbnails<br>'+
            '3,<IC"archive.oneshot.img_idb"><IC"archive.live.img_idb">Images (original size)<br>'+
            '3,<IC"archive.oneshot.webm_idb"><IC"archive.live.webm_idb">Webms<br>'+
//            '2,3. Select files which will be skipped downloading<br>'+
//            '3,Downloaded files: <IF"archive.dir_dled" multiple directory><br>'+
            '1,3. <IC"archive.list_inherit">Inherit previous archiving list (for 4d,e)<br>'+
            '1,4a. <IC"archive.store_watched">Auto archiving for watched threads<br>'+
            '1,4b. <IC"archive.store_auto">Auto archiving if OP matches:<br>'+
            '2,Keyword: <ITBL25"archive.kwd.str">'+
            '<SE"archive.kwd.match,match all,match any,unmatch all,unmatch any"><br>'+
//            this.make_sel('archive.kwd.match', ['match all','match any','unmatch all','unmatch any'])+ '<br>'+
            '3,<ICN"archive.kwd.sub">Subject '+
            '<ICN"archive.kwd.name">Name '+
            '<ICN"archive.kwd.com">Comment <br>'+
            '3,<ICN"archive.kwd.ci">CI'+
            ' <ICN"archive.kwd.ew">exact'+
            ' <ICN"archive.kwd.sentence">Sentence'+
            ' <ICN"archive.kwd.re">RE<br>'+
            '1,4c. <IC"archive.list">List picker<br>'+
            '2,<textarea rows="1" cols="40" name="archive.list_str" placeholder="lain  // whole lainchan\n/tech/  // whole /tech/\n/q/1234  // thread /q/1234"></textarea><br>'+
            '1,4d. <BTN"archive.oneshot,OneShotArchive"> or <BTN"archive.start,StartArchiving"><br>'+
            '3,<IR"archive.src,shown"> All shown threads<br>'+
            '3,<IR"archive.src,watched"> All watched threads<br>'+
            '3,<IR"archive.src,stored"> All stored threads<br>'+
//            '3,<IR"archive.src""scanned"> All scanned threads for automatic archiving<br>'+
            '1,4e. You can select target threads individually using triage<br>';},
        'JSON Archiver: Restore': function(){
            if (pref.archive.files_sel===2) archiver.event_funcs['queryList']();
            return 'JSON Archiver: Restore<br>'+
//            '1,Restore:<br>'+
            '<br>'+
            '1,1. Open a live catalog, index page or thread to be overriden.<br>'+
            '2,CatChan must be working in it.<br>'+
            '1,2. Select where to extract<br>'+
            '2,<IR"archive.format,auto">Select from filename: Domain-Board-No_Sub.json<br>'+
            '2,<IR"archive.format,manual">Set manually: <SE"archive.domain,'+site0.domains.map(v=>v.replace(/,/g,'\\,')).join(',')+'">/<ITB5"archive.board">/No.<br>'+
//            '2,<IR"archive.format,manual">Set manually: '+ this.make_sel('archive.domain',site0.domains) + '/<ITB5"archive.board">/No.<br>'+
            '1,3a. <IC"archive.IDB.auto_restore">Automatic restore from IndexedDB<br>'+
            '3,<IC"archive.IDB.auto_restore_remove">Remove deleted thread from display<br>'+
            '1,3b. Select source from: <SE"archive.files_sel,Dir,Files,IndexedDB"><br>'+
//            '1,3b. Select source from: '+ this.make_sel('archive.files_sel',['Dir','Files','IndexedDB'])+ '<br>'+
            '<span name="FILES_ARCHIVE0"' +((pref.archive.files_sel===0)? '' : ' style="display:none"') +'>'+
              '&emsp;&emsp;<span><IF"archive.dir" multiple webkitdirectory directory><BTN"archive.files_clear,X"></span><br>'+
            '</span>'+
            '<span name="FILES_ARCHIVE1"' +((pref.archive.files_sel===1)? '' : ' style="display:none"') +'>'+
              '&emsp;&emsp;JSONs/HTMLs: <IF"archive.jsons" multiple accept=".html,.json,text/*"><br>'+
              '2,<IC"archive.load_img">Images: <IF"archive.imgs" multiple webkitdirectory directory><br>'+
            '</span>'+
            '<span name="FILES_ARCHIVE2"' +((pref.archive.files_sel===2)? '' : ' style="display:none"') +'>'+
              '&emsp;&emsp;<span><select name="archive.IDB_board_sel"' + ((pref.archive.IDB_select_multiple)? ' multiple':'') + ' style="float:none"></select>'+
              '<select name="archive.IDB_thread_sel" style="float:none' + ((pref4.archive.IDB_thread_sel_options)? '"' : ';display:none"')
                                                      + ((pref.archive.IDB_select_multiple)? ' multiple':'') +'></select></span>'+
                this.rollup('&emsp;&emsp;<IC"archive.IDB_select_multiple">Multiple select<br>'+
                                  '&emsp;&emsp;Export selected &emsp;&emsp;&emsp;&emsp;<BTN"archive.IDB.export_thread,Threads"><br>'+
                                  '&emsp;&emsp;Reset archived time of selected <BTN"archive.IDB.reset_time,Threads">')+
                this.rollup('&emsp;&emsp;Delete selected <BTN"archive.IDB.delete_board,Boards"><BTN"archive.IDB.delete_thread,Threads"><BTN"archive.IDB.delete_imgs,Images">')+'<br>'+
            '</span>'+
            '&emsp;&emsp;<BTN"archive.restore_button,Restore"> <IC"archive.restore_auto">Auto restore after selection<br>'+
            '3,<IC"archive.clear_threads">Clear all threads at opening an archive<br>'+
            '3,<IC"archive.fix_inconsistency">Fix inconsistencies at open<br>'+
            '3,<IC"archive.open_local">Open remote archive in local environment<br>';
//            '1,5. <BTN"archive.clear_files_button,Clear Files"> <ICBX"archive.clear_files"> Auto clear after open<br>';
          },
        'UIP tracker for 4chan': 'UIP tracker for 4chan:<br>'+
          '1,<IC"uip_tracker.on">Show num of unique IPs after post No.<br>'+
          '3, Check interval: <ITB3"uip_tracker.interval">sec, <IC"uip_tracker.adaptive">Adaptive<br>'+
          '3, Style of changes: <ITBL30"uip_tracker.highlight_str"><br>'+
          '2,<IC"uip_tracker.posts">Show num of posts<br>'+
          '2,<IC"uip_tracker.deletion.show">Show deleted posts\' No.<br>'+
          '3,<IC"uip_tracker.deletion.link">as links<br>'+
          '2, Apply style to deleted post:<br>'+
          '3,<IC"uip_tracker.deletion.name">Add style to name: <ITBL30"uip_tracker.deletion.name_str"><br>'+
          '3,<IC"uip_tracker.deletion.addName">Add <ITBL30"uip_tracker.deletion.addName_str"> to name<br>'+
          '3,<IC"uip_tracker.deletion.post">Add style to post: <ITBL30"uip_tracker.deletion.post_str"><br>'+
          '2,<IC"uip_tracker.annotate">Annotate history of UIP from catalog<br>'+
          '2,<IC"uip_tracker.auto_open">Open next thread automatically<br>'+
          '3,Conditions:<br>'+
          '4,After <ITB3"uip_tracker.auto_open_th">th post<br>'+
          '4,OP contains <textarea style="height:1em" cols="20" name="uip_tracker.auto_open_kwd"></textarea><br>'+
          '2,<IC"uip_tracker.sage.detect">Sage detection, tolerance: <ITB2"uip_tracker.sage.tolerance"> sec<br>'+
          '3,<IC"uip_tracker.sage.name">Add style to name: <ITBL30"uip_tracker.sage.name_str"><br>'+
          '3,<IC"uip_tracker.sage.addName">Add <ITBL30"uip_tracker.sage.addName_str"> to name<br>'+
          '3,<IC"uip_tracker.sage.post">Add style to post: <ITBL30"uip_tracker.sage.post_str"><br>'+
          '3,Bug patch for corrupted data<br>'+
          '4,<IC"uip_tracker.sage.patch_bug3">Ignore corrupted data at head by history<br>'+
          '4,<IC"uip_tracker.sage.patch_bug4">Recover by page history<br>'+
          '4,<IC"uip_tracker.sage.patch_bug5">Cease detection at encountering obsolete data<br>'+
          '4,<IC"uip_tracker.sage.patch_bug2">Loose detection, to <ITB2"uip_tracker.sage.patch_bug2nth">th thread<br>'+
          '4,<IC"uip_tracker.sage.patch_bug">Re-evaluate and recover<br>'+
          '3,<IC"uip_tracker.sage.page">Annotate page history<br>'+
          '3,<IC"uip_tracker.sage.annotate">Annotate sage from catalog<br>'+
          '4,<IC"uip_tracker.sage.annotate_page">Annotate page history from catalog<br>',
        'Command Line Interface': function(){return 'Command line interface<br>'+
          '1,JSON representation of settings or query<br>'+                                                     
          '2,<TA"cli.json_out_str" class="JSON_result" disabled><br>'+
          '2,<BTN"CLI.ex,extract"><BTN"CLI.ex_full,extract_full"><BTN"CLI.ex_filter,extract_current_filter"><BTN"CLI.query_boards"><br>'+
          '2,<TA"cli.json_query_str" placeholder="Output strings (in upper textarea) is used if blank"><br>'+
          '2,<BTN"CLI.query"><BTN"CLI.query_default"> <span class="JSON_result"></span><br><br>'+
          '1,'+this.ta_with_help('cli.json', 'JSON for overwriting settings or script itself', true, null, true)+
//          '2,<TA"cli.json_str"><br>'+
          '2,<BTN"CLI.apply"> <span class="JSON_result"></span><br>'+
          '2,<IC"cli.auto">auto apply at startup<br><br><br>'+
          '2,<TA"cli.eval_str"><br>'+
          '2,<BTN"EVAL,EVAL"><br></div>';},
//          '5',
        'Networking': function(){
            return 'Networking:<br>'+
          '1,Timeout: <ITB3"network.timeout"> sec.<br>'+
          '1,<IC"proto.auto_update_countdown">Show countdown to auto update, shrink &gt;<ITB3"proto.auto_update_shrink">min<br>'+
          '1,<IC"healthIndicator.show">Health indicator, history: <ITB3"healthIndicator.max"><br>'+
          '2,<IC"healthIndicator.expand_running">Show details of running indicators<br>'+
          '2,<IC"healthIndicator.dont_retire_running">Don\'t retire running indicators<br>'+
          '2,<IC"healthIndicator.cancel">Cancel scan when it is clicked<br>'+
          '1,<IC"network.fetch_actively">Fetch missing info actively<br>'+
          '1,Scan:<br>'+
          '2,Max scan boards <ITB6"scan.max"><br>'+
          '2,Max found threads <ITB6"scan.max_threads"><br>'+
////////          '2,Reload older than <ITB6"scan.lifetime"> minutes old<br>'+
          '2,Num of crawler: <ITB2"scan.crawler"><br>'+
          '3,<IC"scan.crawler_adaptive">Spawn adaptively at idle <ITB2"scan.crawler_idle_time_to_spawn">ms<br>'+
          '1,Cross domain connection:<br>'+
          this.radios('network.cross_domain',2, ['direct: Direct connection',
          'indirect: Indirect connection<br>'+
            '3,<IC"network.overXFO">Over X-Frame-Options<br>'+
            '3,<IC"network.overCSPF">Over Content Security Policy frame',
          'GM: GreaseMonkey\'s extension'])+ '<br>'+
//          '2,<IC"catalog_fake_access">Fake access made by human to avoid poor administration<br>'+
//          '3,(This causes heavier network traffic and server load,<br>'+
//          '3,but administrators can\'t see what script you are using)<br>'+
//          '1,Configuration:<br>'+
//          '2,(To get faster feeling, you should check them all.)<br>'+
          '1,Data source:<br>'+
          '2,<IR"catalog.design,page">Page<br>'+
          '2,<IR"catalog.design,auto">Auto<br>'+
          '2,<IR"catalog.design,catalog">Catalog<br>'+
          '3,<IC"catalog.catalog_json">From json file<br>'+
          '1,Lazy load/draw<br>'+
          '2,Catalog/IndexPage/Float <br>'+
          '3,<IC"catalog.lazyDraw.merge" style="visibility:hidden"><IC"page.lazyDraw.merge">'+
            '<IC"float.lazyDraw.merge" style="visibility:hidden">Lazy draw each <ITB3"proto.lazyDraw.merge_step">posts<br>'+
          '3,<IC"catalog.draw_on_demand"><IC"page.draw_on_demand"><IC"float.draw_on_demand">Lazy draw each <ITB3"proto.lazyDraw.step">threads<br>'+
          '3,<IC"catalog.load_on_demand"><IC"page.load_on_demand"><IC"float.load_on_demand">Lazy load HTMLs/JSONs<br>'+
          '2,<IC"network.adaptive">Adaptive<br>'+
          '3,100%: new <ITB3"network.th100"> threads, '+
          '<ITB4"network.th100_delay"> ms delay<br>'+
          '3,20%: new <ITB3"network.th20"> threads, '+
          '<ITB4"network.th20_delay"> ms delay<br>';},
        'General 2': 'General 2:<br>'+
          '1,Localtime offset<ITB2"localtime_offset"><br>'+
          'Share loaded html with other tabs to update<br>'+
          '1,<IC"info_server">Broadcast loaded html to other tabs (server)<br>'+
          '1,<IC"info_client">Listen other tab\'s broadcasting (client)<br>'+
          'Cloudflare<br>'+
          '1,<IC"cloudflare.auto_reload">Auto reload at server error<br>'+
          '2,<ITB2"cloudflare.auto_reload_time"> minutes after<br>'+
          '<br>'+
          'Tooltips:<br>'+
          '1,<IC"tooltips.info.show">Show infotips<br>'+
          '2,pop up delay: <ITB4"tooltips.info.popup_delay"> ms<br>'+
          '2,pop down delay: <ITB4"tooltips.info.popdown_delay"> ms<br>'+
          '1,<IC"tooltips.help.show">Show helptips<br>'+
          '2,pop up delay: <ITB4"tooltips.help.popup_delay"> ms<br>'+
          '2,pop down delay: <ITB4"tooltips.help.popdown_delay"> ms<br>'+
          '1,Z-Index: <ITB4"tooltips.zIndex"><br>'+
          'Misc.<br>'+
          '1,<IC"recovery.comment">Recover comment when browser was crashed<br>'+
          '1,<IC"catalog_footer_ignore_my_own_posts">Ignore my own posts at counting unread posts<br>'+
          '1,<IC"pref2.KC.summer_time">Summer time in KC<br>'+
          '<br>'+
          'Patches<br>'+
          '1,<IC"patch.delayed_invoke.use">Delayed invoke for 4chan, <ITB2"patch.delayed_invoke.sec">sec.<br>'+
//          '<IC"pref2.meguca.remove_history_class">Remove history class in meguca'+
          '',
        'Styles': function(){
        return 'Styles:<br>'+
          '1,<IC"style.addSS.use">Additional StyleSheets, URLs are below<br>'+
          '2,<TA"style.addSS.str"><br>'+
          '1,Design of Windows, popups, and others:<br>'+
          '<div style="display:inline-block">&emsp;&emsp;</div><table style="display:inline-block"><thead>'+this.expand_row(',,3L:Override with CSS style','th')+'</thead><tbody>'+
          this.expand_row(',Base design,,Selector,Computed','th')+'</thead><tbody>'+
          this.expand_row('Title bar:,<ITBL15"style.design.titleBar.base">,<IC"style.design.titleBar.ov">,<ITBL10"style.design.titleBar.sel">,<IC"style.design.titleBar.comp">')+
          this.expand_row('Window:,<ITBL15"style.design.window.base">,<IC"style.design.window.ov">,<ITBL10"style.design.window.sel">,<IC"style.design.window.comp">')+
          this.expand_row('Pop up:,<ITBL15"style.design.popUp.base">,<IC"style.design.popUp.ov">,<ITBL10"style.design.popUp.sel">,<IC"style.design.popUp.comp">')+
          this.expand_row('New post:,<ITBL15"style.design.post_new.base">,<IC"style.design.post_new.ov">,<ITBL10"style.design.post_new.sel">,<IC"style.design.post_new.comp">')+
          this.expand_row('Editing post:,<ITBL15"style.design.post_editing.base">,<IC"style.design.post_editing.ov">,<ITBL10"style.design.post_editing.sel">,<IC"style.design.post_editing.comp">')+
          '</tbody></table><br>'+
          '2,Z-Index: <ITB4"style.zIndex"><br>'+
          '1,Style tips for merge:<br>'+
//          '2,<IR"style.sel,fix"> Use base design <BTN"style.reset.fix,reset"><br>'+
//          '3,Title bar:<ITBL30"style.fix.titleBar_str"><br>'+
//          '3,Window:<ITBL30"style.fix.window_str"><br>'+
//          '3,Pop up:<ITBL30"style.fix.popUp_str"><br>'+
//          '2,<IR"style.sel,copy"> Override with CSS <BTN"style.reset.copy,reset"><br>'+
//          '3,Title bar:<ITBL20"style.copy.titleBar_str" placeholder="CSS selector"><ICN"style.copy.titleBar_comp">Computed<br>'+
//          '3,Window:<ITBL20"style.copy.window_str" placeholder="CSS selector"><ICN"style.copy.window_comp">Computed<br>'+
//          '3,Pop up:<ITBL20"style.copy.popUp_str" placeholder="CSS selector"><ICN"style.copy.popUp_comp">Computed<br>'+
//          '1,Others:<br>'+
//          '2,New post:<ITBL30"style.post_new"><br>'+
//          '2,Editing post:<ITBL30"style.post_editing"><br>'+
          this.userCSS(1,2);},
        'Features': function(){
        return 'Features:<br>'+
          '1,<IC"features.page">Page<br>'+
          '1,<IC"features.graph">Graph<br>'+
//          '1,<ICBX"features.setting"> Setting<br>'+
          '1,<IC"features.setting2">Setting2<br>'+
          '1,<IC"features.postform">Postform<br>'+
          '1,<IC"features.catalog">Catalog<br>'+
          '1,<IC"features.listener">Listener<br>'+
          '1,<IC"features.uip_tracker">UIP_tracker<br>'+
          '1,<IC"features.thread_reader">Thread Reader<br>'+
          '1,<IC"features.recovery">Recovery comment<br>'+
          '1,<IC"features.IDB">IndexedDB<br>'+
//          '<IC"features.debug">Debug<br>'+
          'Notifications:<br>'+
          '1,<IC"features.notify.desktop">DesktopNotification<br>'+
          '1,<IC"features.notify.sound">Sound<br>'+
          '1,<IC"features.notify.favicon">Favicon<br>'+
          'Sites:<br>'+
          this.features_domains();},
        'About': function(){
        return 'CatChan<br>'+
          'Version 2023.01.22.1<br>'+
          '<a href="https://github.com/DogMan8/CatChan">GitHub</a><br>'+
          '<a href="https://github.com/DogMan8/CatChan/raw/master/CatChan.user.js">Get stable release</a><br>'+
          '<a href="https://github.com/DogMan8/CatChan/raw/develop/CatChan.user.js">Get BETA release</a><br>'+
          '<br><br>'+
          'Debug mode<br>'+
          '<IC"debug_mode.0">'+
          '<IC"debug_mode.1">'+
          '<ITB12"debug_mode.unread_count">'+
          this.cb_block('debug_mode',2,9).replace(/\.6/,'.parse_error')+'<br>'+
          '<ITB20"debug_mode.site2func" placeholder="catalog_json2html3"><BTN"Debug.site2func,dump_site2"><IC"debug_mode.site2func_expand">func<br>'+
          '<ITB25"debug_mode.pfunc" placeholder="4chan or 4chan:catalog_json"><BTN"Debug.dump,dump"><IC"debug_mode.pfunc_expand">func<br>'+
          '<ITB25"debug_mode.pfunc_comp" placeholder="4chan or 4chan:catalog_json"><BTN"Debug.comp,compare">'+
            '<IC"debug_mode.pfunc_comp_expand_same"><IC"debug_mode.pfunc_comp_expand_diff">func <br>'+
          '<ITBL60"debug_mode.pfunc_comp_proto" placeholder="temporal prototype dictionary in JSON format, {&quot;BBC.page_html_template&quot;:&quot;RSS.page_html_template&quot;}"><br>'+
          '<ITB15"debug_mode.pfunc_all" placeholder="ths"><BTN"Debug.pfunc_all,find_parse_funcs"><IC"debug_mode.pfunc_all_expand">func<IC"debug_mode.pfunc_all_site2">site2'+this.rollup(
            '<div style="float:left">' + site0.debug_domains.map(function(v){return '<IC"debug_mode.domains.'+v+'">'+v;}).join('<br>')+'</div>'+
            '<div style="float:left">' + site0.debug_types.map(function(v){return '<IC"debug_mode.types.'+v+'">'+v;}).join('<br>')+'</div>'+
            '<div style="clear:both"></div>')+'<br>'+
          this.cb_block('debug_mode',9)+
          '<BTN"debugWindow,debugWindow"><br>'+
          'Test mode'+this.rollup(this.cb_block('test_mode',0,100))+this.rollup(this.cb_block('test_mode',100,200))+this.rollup(this.cb_block('test_mode',200)+
          (Debug? ' LastReloaded: '+new Date(Debug.lastReloaded).toLocaleString():'')+'<br>'+
          '<IC"test_mode.tips">'+
          '<ITB6"test_mode.num">'+
          '<ITBL30"test_mode.test_str">'+
          '<ITB6"test_mode.num_f">'+
          '&emsp;<BTN"button_test,test"><br>'+
          '1,<BTN"load_samples,setting_samples"><br>'+
          '<IF"test_mode.js_file" accept=".js,text/*"><BTN"test_mode.js_load,JS_load">')+'<br>';},
        'RSS dashboard': function(){
          return '<div>RSS boards (and attributes)'+this.rollupHelp('',
            '1,<IC"RSS.sec_auto">Auto adjust security level for new rss<br>'+
            '1,Dashboard:<br>'+
            '2,<IC"dashboard.rss_live">Live<br>'+
            '2,<IC"dashboard.rss_apply_global">Apply default "isTrusted" change to existing all entries<br>'+
            '2,<SEC"dashboard.rss_com,show,simple,no"> comment lines<br>'+
            '1,When a link of RSS is dropped...:<br>'+
            '2,<IC"RSS.dnd.nf">Don\'t fetch the RSS if the link is dropped onto dashboard<br>'+
            '2,<IC"RSS.dnd.f_rec">Fetch recommended settings prior to the RSS<br>'+
            '2,<IC"RSS.dnd.f_vbd">Fetch virtual board instead of the RSS<br>'
            )+'</div>'+
            '<table style="text-align:center">'+this.rss_dashboard_table()+
            '</table>'+ this.colorPicker() + (!pref.test_mode['202']? ''
              : '<hr>'+this.ta_with_help('RSS', 'RSS boards configuration', null, null, true)+
                '1,<TA"samples.dashboard.rss.auto" disabled><br>'+
                '<span class="JSON_result"></span><br>'+
                this.ta_with_help('catalog.style_general_list', 'Use global style', true)+
                this.userCSS(0,1));},
      rss_dashboard_table: function(){
        var data = site2['rss'].generate_registered_rss(pref.dashboard.rss && pref.dashboard.rss['new']);
        pref.dashboard.rss = data;
        var done_pg = {};
        var com_class = ' class="hr_sep'+(pref.dashboard.rss_com==='show'? '' : pref.dashboard.rss_com==='no'?' hidden':' no_text')+'"';
        var html = '<thead>'+this.expand_row(',,,,,,2:<BTN"P.unlock.isTrusted:Unlock">,<BTN"P.unlock.options:Unlock">','th');
        html += this.expand_row('Order,Color,2:Sticky,Board,Url(s) :page,2:isTrusted,Options,Del,Status','th')+'</thaed>';
//          var html = '<div>RSS boards (and attributes)</div><table style="text-align:center"><thead>'+this.expand_row(',,,,,3:isTrusted,options,','th')+
//            this.expand_row('Order,Color,Sticky,Board,Url(s),1,2,v,(read only),Del','th')+'</thaed><tbody>';
        html += '<tbody>'+this.expand_row('|||||DEFAULT||<SE"P.gl.0.isTrusted,0,1,2,3::disabled,4::disabled">|<ITBL10"P.gl.0.opt" style="opacity:0.6;background:transparent" readonly>|<SPB"P.gl.0.del,=">',null,'|')+'</tbody><tbody>';
        for (var i=0;i<data.arr.length;i++) html += this.dashboard_row('arr.'+i, data.arr[i], null, this.rss_dashboard_expand_close(data.arr,i), this.rss_dashboard_page(data.arr[i], done_pg, data.mult_dbs));
        html += '</tbody><tbody'+com_class+'>'+this.expand_row('2:<hr2></hr2>,9L:<hr2>Entries below this line WILL NOT BE SAVED</hr2>','th');
        html += this.expand_row('2L:<hr>&ensp;Add&ensp;</hr>,9L:<hr>&ensp;auto generated temporal configurations&ensp;</hr>','th')+'</tbody><tbody>';
        for (var i=0;i<data.auto.length;i++) html += this.dashboard_row('auto.'+i, data.auto[i], 'auto', this.rss_dashboard_expand_close(data.auto,i), this.rss_dashboard_page(data.auto[i], done_pg, data.mult_dbs));
        html += '</tbody><tbody'+com_class+'>'+this.expand_row('2L:<hr>&ensp;Add&ensp;</hr>,9L:<hr>&ensp;new entry&ensp;</hr>','th')+'</tbody><tbody>';
//          html += '</tbody><tbody>'+this.expand_row('Add,<hr>,2L:new entry,<hr>,<hr>,<hr>,<hr>')+'</tbody><tbody>';
        html += this.dashboard_row('new.0', data.new[0], 'new');
//          html += this.expand_row('<SPB"dashboard.new.0.add:+">'); // avoid delimiter ',' for expand_row
        return html.replace(/\"P\./g,'"dashboard.rss.').replace(/<hr(\d*)>([^<]*)<\/hr\d*>/g,(m,p1,p2)=>'<div class="'+pref.cpfx+'hr_text'+(p1||'')+'">'+p2+'</div>')+'</tbody>';
////          return html.replace(/\"P\./g,'"dashboard.rss.').replace(/<hr(\d*)>([^<]*)<\/hr\d*>/g,(m,p1,p2)=>(p2? '<div class="'+pref.cpfx+'hr_text">'+p2:'')
////                                                                                                              +'<div class="'+pref.cpfx+'hr_grow'+(p1||'')+'"></div>'+(p2?'</div>':''))+
////          return html.replace(/\"P\./g,'"dashboard.rss.')+ // .replace(/<hr(_\w*)*>/g,(m,p1)=>'<div class="'+pref.cpfx+'hr'+(p1||'_grow')+'">'+(p1!=='_text'?'</div>':''))+
//        '</tbody></table>'+ this.colorPicker() + (pref.test_mode['202']? '<TA"RSS.str"><br><span class="JSON_result"></span><br>':'');
////          var html = '<table style="text-align:center"><thead>'+this.expand_row(',,,,,,,,3:isTrusted,str,','th')+
////            this.expand_row('order,on,pattern,color,hide,sticky,board,url(s),1,2,test,(read only),del','th')+'</thaed><tbody>';
////          for (var i=0;i<data.arr.length;i++) html += this.dashboard_row('arr.'+i);
////          html += '</tbody><tbody>';
////          for (var i=0;i<data.auto.length;i++) html += this.dashboard_row('auto.'+i);
////          return html + '</tbody></table>';
      },
      rss_dashboard_expand_close: function(arr, i){
        var prev = arr[i-1];
        var row = arr[i];
        var row_dbf = row.db_formatted;
        var next = arr[i+1];
        return next && row_dbf===next.db_formatted && (!prev || prev.db_formatted!==row_dbf)? 'cl' : row.url[0]==='[' && 'ex';
      },
//      rss_dashboard_expand_close: function(row, next){
//        return next && row.db_formatted===next.db_formatted? 'cl' : row.url[0]==='[' && 'ex';
//      },
      rss_dashboard_page: function(row, done_pg, mult_dbs){
        var db = row.db_formatted;
        var pg = done_pg[db]||0;
        if (pg) mult_dbs[db] = null;
        var len = row.urls_arr.length;
        done_pg[db] = pg + len;
        return pg + (len===1? 0 : '-'+(pg+len-1)); // returns number (if 0) or string
      },
      rss_dashboard_status: function(row, prop){
        var urls = row.urls_arr;
        var done = {};
        var html = '';
        for (var i=0;i<urls.length;i++) {
          var dbpEtag = site2['rss'].rss2dbpEtag[urls[i]];
          var status = dbpEtag && dbpEtag[2] && dbpEtag[2][0];
          if (status && !(status in done)) {html += status+','; done[status] = null;}
        }
        return '<span data-name="'+prop+'" style="color:'+(html==='200,'?'mediumseagreen':'red')+'">'+html.slice(0,-1)+'</span>'; 
      },
      },
        html_common:
          '<div class="'+pref.cpfx+'errors" style="color:red"></div>'+
          '<span name="reload_recommended" style="display:none;background:yellow">&emsp;&emsp;RELOAD IS REQUIRED TO GET LIGHT FEELING&emsp;&emsp;<br></span>'+
          '<span name="reload_required" style="display:none;background:red;color:white">&emsp;&emsp;RELOAD IS REQUIRED&emsp;&emsp;</span>'+
          '<br><BTN"close">'+
          '&emsp;<BTN"save">'+
          '<BTN"load_default">'+
          '<BTN"load_default_of_this_page">',
//        onchange_event : function(){
//          pref_func.apply_prep(this,true);
//          if (pref_func.settings.onchange_funcs[this.name]) pref_func.settings.onchange_funcs[this.name]();
//        },
        options: [
          'Easy Setting2:',
          'Easy Setting:',
          'Virtual Board:',
          'Catalog:',
          'Index Page:',
          'Thread:',
          'Watcher/FC:',
          'Gerenal 0',
          'General 1',
          'Board Group',
          'RSS dashboard',
          'Live Tag',
          'Statistics',
//          'Design of FC',
          'Footer',
//          'Footer for watcher',
          'ThreadReader',
          'Notifiers',
          'JSON Archiver: Store',
          'JSON Archiver: Restore',
          'UIP tracker for 4chan',
          'Command Line Interface',
//          'Workaround for dollchan',
          'Networking',
          'General 2',
          'Styles',
          'Features',
          'About'
        ],
        tag_gen: null,
//        health_indicator: null,
        get onchange_funcs_formatted(){
          Object.defineProperty(this,'onchange_funcs_formatted',{value:pref_func.add_onchange_format(this.onchange_funcs), writable:true, enumerable:true, configurable:true});
          pref_func.add_onchange_format(this.onchange_funcs.archive);
          return this.onchange_funcs_formatted;
        },
        oninput_funcs: null,
        onchange_funcs_base: null,
        onchange_funcs: {
          __proto__: { // oninput_funcs
            __proto__: (function(){
              var obj = {
                SHOWALL: function(e){ // bound later.
                  this.TOGGLE(e.target,'HIDEALL','',null,'no');
                  Array.prototype.slice.call(e.target.parentNode.parentNode.querySelectorAll('[name=SHOW],[name=SHOW2]')).forEach(function(v){v.onclick({target:v, currentTarget:v});});
    //              Array.prototype.slice.call(e.target.parentNode.parentNode.parentNode.querySelectorAll('[name=SHOW],[name=SHOW2]')).forEach(function(v){v.onclick({target:v});});
                },
                HIDEALL: function(e){ // bound later.
                  this.TOGGLE(e.target,'SHOWALL','',null,'no');
                  Array.prototype.slice.call(e.target.parentNode.parentNode.querySelectorAll('[name=HIDE],[name=HIDE2]')).forEach(function(v){v.onclick({target:v, currentTarget:v});});
    //              Array.prototype.slice.call(e.target.parentNode.parentNode.parentNode.querySelectorAll('[name=HIDE],[name=HIDE2]')).forEach(function(v){v.onclick({target:v});});
                },
                SHOW: function(e){this.TOGGLE(e.target,'HIDE','');}, // bound later.
                HIDE: function(e){this.TOGGLE(e.target,'SHOW','none');}, // bound later.
                TOGGLE: function(et,name,disp, dst_dom, tgt_dom){
                  if (!dst_dom) dst_dom = et;
                  var str = et.getAttribute('data-str');
                  et.setAttribute('data-str',dst_dom.textContent);
                  dst_dom.textContent = str;
                  et.setAttribute('name',name);
                  if (tgt_dom==='no') return;
                  if (!tgt_dom) tgt_dom = et.nextSibling;
                  tgt_dom.style.display = disp;
                },
    //            NEXT: function(e){e.currentTarget.nextSibling.onclick({target:e.currentTarget.nextSibling});},
                SHOW2: function(e){this.TOGGLE2(e.target,'HIDE2','');}, // bound later.
                HIDE2: function(e){this.TOGGLE2(e.target,'SHOW2','none');}, // bound later.
                TOGGLE2: function(et,name,disp){ // bound later.
                  if (et.tagName!=='A') et = et.parentNode;
                  this.TOGGLE(et,name,disp,et.childNodes[2], et.nextSibling.nextSibling);
                },
                SHOW3: function(e){
                  var tgt = this.SEARCH_HIER(e.target, 'data-show3');
                  tgt.style.display = '';
                  e.target.style.display = 'none';
                },
                HIDE3: function(e){
                  var tgt = this.SEARCH_HIER(e.target.parentNode, 'data-show3');
                  tgt.style.display = '';
                  e.target.parentNode.style.display = 'none';
                },
                SEARCH_HIER: function(et, attr){
                  var val = et.getAttribute(attr);
                  var tgts;
                  var pn = et.parentNode;
                  while (pn && (tgts = pn.querySelectorAll('['+attr+'='+val+']')).length<2) pn = pn.parentNode; // hit myself also
                  return tgts[0]===et? tgts[1] : tgts[0];
                },
    //            SHOW3: function(e){ // bound later.
    //              if (e.target.previousSibling) {
    //                var div = document.createElement('div');
    //                div.setAttribute('style','clear:both');
    //                e.target.parentNode.insertBefore(div,e.target);
    //              }
    //              e.target.nextSibling.style.display = '';
    //              e.target.style.display = 'none';
    //            },
    //            HIDE3: function(e){ // bound later.
    //              e.target.parentNode.previousSibling.style.display = '';
    //              e.target.parentNode.style.display = 'none';
    //              var etPpp = e.target.parentNode.previousSibling.previousSibling;
    //              if (etPpp) e.target.parentNode.parentNode.removeChild(etPpp);
    //            },
//                SHOW3: function(e){ // bound later.
//                  var label = e.target.getAttribute('data-show3');
//                  var tgt = e.target.previousSibling;
//                  while (tgt.getAttribute('data-show3')!==label) tgt = tgt.previousSibling;
//                  tgt.style.display = '';
//                  e.target.style.display = 'none';
//                },
//                HIDE3: function(e){ // bound later.
//                  var label = e.target.parentNode.getAttribute('data-show3');
//                  var tgt = e.target.parentNode.nextSibling;
//                  while (tgt.getAttribute('data-show3')!==label) tgt = tgt.nextSibling;
//                  tgt.style.display = '';
//                  e.target.parentNode.style.display = 'none';
//                },
                SHOW2_I: function(e){var etp = e.target.previousSibling; if (etp.onclick) etp.onclick({target:etp, currentTarget:etp});},
    //            SHOW: function(e){
    //              e.target.style.display = 'none';
    //              e.target.nextSibling.style.display = '';
    //              e.target.nextSibling.nextSibling.style.display = '';
    //            },
    //            HIDE: function(e){
    //              e.target.style.display = 'none';
    //              e.target.nextSibling.style.display = 'none';
    //              e.target.previousSibling.style.display = '';
    //            },
                SHOW4: function(e){
                  e.target.style.display = 'none';
                  e.target.previousSibling.style.display = null;
                },
                HIDE4: function(e){
                  e.target.parentNode.style.display = 'none';
                  e.target.parentNode.nextSibling.style.display = null;
                },
                SUB: function(e){
    //              var pns = e.currentTarget.parentNode.querySelectorAll('span[name="' +e.target.getAttribute('name')+ '"]');
                  var pn = e.currentTarget.previousSibling.previousSibling;
                  if (pn && pn.querySelectorAll) {
                    var pns = pn.querySelectorAll('span[data-show_value]');
                    if (pns.length>0) pref_func.apply_prep(pns, false, null, null, null, this['.'] && this['.'].pref || null);
                  }
                },
                SHOW5: function(e){this.TOGGLE(e.target,'HIDE5','',     null, this.SEARCH_HIER(e.target, 'data-show5'));},
                HIDE5: function(e){this.TOGGLE(e.target,'SHOW5','none', null, this.SEARCH_HIER(e.target, 'data-show5'));},
//                TOGGLE5: function(et, name, disp){
//                  var label = et.getAttribute('data-show5');
//                  var tgts;
//                  var pn = et.parentNode;
//                  while (pn && (tgts = pn.querySelectorAll('[data-show5='+label+']')).length<2) pn = pn.parentNode; // hit myself also
//                  this.TOGGLE(et,name,disp,null,(tgts[0]===et)? tgts[1] : tgts[0]);
//                },
                SHOW_T: function(e){this.TOGGLE(e.target,'HIDE_T','',    null,this.TRAVERSE(e.target));},
                HIDE_T: function(e){this.TOGGLE(e.target,'SHOW_T','none',null,this.TRAVERSE(e.target));},
                TRAVERSE: function(pn){
                  var dir = pn.dataset.trav; // directions
                  var i=-1;
                  while (++i<dir.length) pn = (dir[i]==='n')? pn.nextSibling
//                    : (dir[i]==='p')? pn.previousSibling // not used yet
                    : (dir[i]==='P')? pn.parentNode : pn;
//                    : (dir[i]==='F')? pn.firstChild
//                    : (dir[i]==='L')? pn.lastChild : pn;
                  return pn;
                },
//                SHOW_PN: function(e){this.TOGGLE(e.target,'HIDE_PN','',null,e.target.parentNode.nextSibling);},
//                HIDE_PN: function(e){this.TOGGLE(e.target,'SHOW_PN','none',null,e.target.parentNode.nextSibling);},
              };
              for (var i in obj) obj[i] = obj[i].bind(obj);
              return obj;
            })(),
            'virtualBoard.search.str': function(){ // lighter
              pref.virtualBoard.search.rexps_remake();
              if (pref.virtualBoard.bl.show) liveTag.update_boardlist();
            },
//            'virtualBoard.search.str': function(){liveTag.filter_onchange(pref.virtualBoard.search);}, // working code
          },
          'uip_tracker.on' : uip_tracker_init,
          'healthIndicator.show' : function(){cataLog.healthIndicator.show();},
          'healthIndicator.expand_running' : function(){cataLog.healthIndicator.shrink_running();},
          get '..'(){return pref_func.settings;},
          'settings.*': function(e){
            var pObj = this['..'];
            var pn13_1 = pObj.pn13_1;
////            if (pn13_1.innerHTML) pref_func.tooltips.remove_hier(pn13_1);
//            pObj.files_store();
////            var files_archive;
////            if (pref.settings.indexing===17) files_archive = site.script_body.querySelectorAll('span[name="FILES_ARCHIVE0"]')[0]; // out of w3c, but works in chrome and FF.
            var tgt = pObj.htmls[pObj.options[pref.settings.indexing]];
            var html = ((typeof(tgt)==='function')?  tgt.call(pObj.htmls) : tgt) +pObj.html_common;
            pn13_1.innerHTML = '<form name="CatChan_settings">'+pref_func.format_html_str(html)+'</form>';
//            if (files_archive) {
//              var ref = pn13_1.querySelectorAll('span[name="FILES_ARCHIVE0"]')[0]; // out of w3c, but works in chrome and FF.
//              ref.parentNode.insertBefore(files_archive,ref);
//              ref.parentNode.removeChild(ref);
//            }
            pref_func.add_onchange(pn13_1,pObj.onchange_funcs_formatted);
            pObj.apply_pn13_1();
//            pref_func.tooltips.add_hier(pn13_1);
          },
          'pn13_1_warning_reload': function(){
            pref3.reload_required = true;
            this['pn13_1_warning']();
          },
          'pn13_1_warning': function(){
            var pn13_1 = this['..'].pn13_1;
            var pn = pn13_1.getElementsByTagName('span')['reload_required'];
            pn.style.display = (pref3.reload_required || pref3.stats.use!==pref.stats.use || pref.stats.estimate_posts && !pref3.stats.estimate_posts)? '' : 'none';
            var pn = pn13_1.getElementsByTagName('span')['reload_recommended'];
            pn.style.display = (!pref.stats.estimate_posts && pref3.stats.estimate_posts)? '' : 'none';
          },
          'tag.scan' : function(){
            pref.catalog.board.board_tags = true;
            pref_func.settings.onchange_funcs['tag.generate_caller']('tag.scan');
            liveTag.popup_filter.popup();
          },
          'tag.same_tag_refresh' : function(){
            pref.catalog.board.board_tags_same = true;
            pref_func.settings.onchange_funcs['tag.generate_caller']('tag.same_tag_refresh');
          },
          'tag.generate_caller' : function(src){
            site2[site.nickname].get_boards_json('manual_refresh_boards_json', null, null, true);
//            if (pref.test_mode['112']) {
//              http_req.get('tag','8chan','https://'+site2['8chan'].domain_url+'/boards.json',pref_func.settings.onchange_funcs['tag.generate_callback'],false,false,src);
//              if (pref_func.settings.pn13) pref_func.settings.apply_pn13_1();
//            }
          },
//          'tag.generate_callback' : function(key,value,arg){ // working code
//            site3['8chan'].boards = ('response' in value)? value.response : JSON.parse(value.responseText);
//            pref_func.settings.onchange_funcs['tag.re_generate'](arg);
//          },
//          'tag.re_generate' : function(arg){
//            var str = catalog_obj.scan_tags_common(site3['8chan'].boards,'name="tag.gen"');
//            if (!pref_func.settings['tag_gen']) {
//              if (arg!=='tag.same_tag_refresh') {
//                var html = '<div><button name="tag.scan">Refresh</button><br>'+
//                  '<textarea style="height:1em" cols="20" name="tag.gen_str"></textarea>'+
//                  '<button name="tag.gen_add">Add to list</button></div><div>' + str +'</div>';
//                cnst.make_popup(pref_func.settings,'tag_gen',html,pref_func.settings.onchange_funcs);
//                var pn = pref_func.settings['tag_gen'].childNodes[1];
////                pn.getElementsByTagName('textarea')[0].value='board_group_name';
//                pn.style.height = '200px';
//                pn.style.width  = '200px';
//                pn.style.overflow = 'auto';
//                pn.style.resize = 'both';
//                cnst.bottom_top(pn);
//              }
//            } else pref_func.settings['tag_gen'].childNodes[1].childNodes[1].innerHTML = str;
//            if (pref_func.settings['tag_gen']) pref_func.add_onchange(pref_func.settings['tag_gen'].childNodes[1].childNodes[1],pref_func.settings.onchange_funcs_formatted);
//
//            var str = ''
//            var obj = site3['8chan'].boards;
//            var myself = 0;
//            while (myself<obj.length-1 && site.board!=='/'+obj[myself].uri+'/') myself++;
//            for (var i=0;i<obj[myself].tags.length;i++) {
//              var key = obj[myself].tags[i];
//              str = str + '#' + key;
//              for (var j=0;j<obj.length;j++)
//                for (var m=0;m<obj[j].tags.length;m++)
//                  if (obj[j].tags[m]===key) str = str + ',8chan/' + obj[j].uri + '/';
//              str = str + '\n';
//            }
//            pref_func.catalog_board_list_str_bt_same = str;
//
//            pref_func.catalog_board_list_str_bt = '';
//            pref_func.settings.onchange_funcs['board_sel_refresh']();
//          },
//          'catalog.tag.ignore' : function(){pref_func.settings.onchange_funcs['tag.re_generate']();},
//          'catalog.tag.max' : function(){pref_func.settings.onchange_funcs['tag.re_generate']();},
//          'tag.gen_str' : function(){pref_func.settings.onchange_funcs['tag.gen']();},
          'tag.gen' : function(){
            var cbxes = pref_func.settings['tag_gen'].childNodes[1].getElementsByTagName('input');
            var str = '';
            var str_name = pref_func.settings['tag_gen'].childNodes[1].getElementsByTagName('textarea')[0].value;
            var obj = site3['8chan'].boards;
            for (var i=0;i<cbxes.length;i++)
              if (cbxes[i].checked) {
                var key = cbxes[i].nextSibling.textContent.replace(/ [0-9]*: /,'');
                if (str_name==='') str_name = '#' + key;
                for (var j=0;j<obj.length;j++)
                  for (var m=0;m<obj[j].tags.length;m++)
                    if (obj[j].tags[m]==key) str = str + '8chan/' + obj[j].uri + '/,';
              }
            pref_func.catalog_board_list_str_bt = str_name + ',' + str;
            pref_func.settings.onchange_funcs['board_sel_refresh']();
//            pref_func.str2obj('catalog_board_list_str');
//            if (pref_func.board_sel) pref_func.apply_prep(pref_func.board_sel,false);
          },
          'tag.gen_add' : function(){
            pref_func.settings.onchange_funcs['tag.gen']();
            pref_func.settings.onchange_funcs['tag.add_to_list'](pref_func.catalog_board_list_str_bt);
          },
          'tag.recommendation_add' : function(){
            pref_func.settings.onchange_funcs['tag.add_to_list'](pref_func.catalog_board_list_str_or);
          },
          'tag.add_to_list' : function(str){
            if (str!=='') {
              if (pref.catalog_board_list_str.search(/\n$/)==-1) pref.catalog_board_list_str = pref.catalog_board_list_str + '\n';
              pref.catalog_board_list_str = pref.catalog_board_list_str + str + '\n';
              if (pref_func.settings.pn13) pref_func.settings.apply_pn13_1();
//              pref_func.str2obj('catalog_board_list_str');
//              if (pref_func.board_sel) pref_func.apply_prep(pref_func.board_sel,false);
              pref_func.settings.onchange_funcs['board_sel_refresh']();
            }
          },
          'board_sel_refresh' : function(){
            pref_func.str2obj('catalog_board_list_str');
            cnst.auto_shrink_board_selector.str_changed();
          },
          'stats.use' : 'pn13_1_warning',
          'stats.estimate_posts' : 'pn13_1_warning',
          'stats.auto_acquisition' : function(){cnst.auto_shrink_board_selector.color();},
          'stats.auto_acquisition_all' : 'stats.auto_acquisition',
          'catalog.board.recommendation': 'board_sel_refresh',
          'catalog.board.board_tags': 'board_sel_refresh',
          'catalog_board_list_str': function(){cnst.auto_shrink_board_selector.str_changed();},
          'catalog.board.all_boards': 'board_sel_refresh',
          'CLI.*W/': function(e, [,cmd]){
            var pns = this['..'].pn13.getElementsByClassName('JSON_result');
            if (cmd==='apply') pref_func.queryOrSet_params_by_JSON(false, pref.cli.json_str, null, pns[2]);
            else pns[0].value = pref.cli.json_out_str = (cmd.indexOf('ex')===0)? pref_func.site2_json_ex(cmd==='ex_full', cmd==='ex_filter')
              : pref_func.queryOrSet_params_by_JSON(cmd==='query_default'? pref_default() : true,
                  (cmd==='query_boards')? '{"site2":{"'+site.nickname+'":{"boards_json":""}}}' : pref.cli.json_query_str||pref.cli.json_out_str, null, pns[1]);
          },
//          'CLI.*W/': function(e, [,cmd]){
//            var str = cmd.indexOf('query')===-1? pref.cli.json_str : (cmd==='query_boards')? '{"site2":{"'+site.nickname+'":{"boards_json":""}}}' : pref.cli.json_query_str;
//            var pn = this['..'].pn13.getElementsByClassName('JSON_result')[cmd==='apply'?1:0];
//            pref_func.queryOrSet_params_by_JSON(cmd==='apply'? false : cmd==='query_default'? pref_default() : true, str, null, pn);
//            this['..'].apply_pn13_1();
//          },
//          'CLIe.*W/': function(e, [,cmd]) {
//            pref.cli.json_out_str = pref_func.site2_json_ex(cmd==='full', cmd==='filter');
//            this['..'].apply_pn13_1();
//          },
          'RSS.str': function(e){
            pref_func.parseJSONCs_out(this['..'].pn13.getElementsByClassName('JSON_result')[0], pref_func.RSS_json()[0], true);
            if (pref.test_mode['202']) this['dashboard.rss_redraw'](); // for debug
          },
//          'RSS.dashboard': function(e){
//            e.target.parentNode.getElementsByClassName('dashboard')[0].innerHTML = pref_func.format_html_str(this['..'].htmls['RSS dashboard']());
//            this['..'].apply_pn13_1();
//          },
//          'JSON' : function() { // working code
////          pref_func.apply_prep(pref_func.settings.pn13.getElementsByTagName('TEXTAREA')['cli.json_str'],true);
//            pref_func.site2_json();
//          },
//          'JSON_query_boards': function() {
//            pref.cli.json_str = '{"site2":{"'+site.nickname+'":{"boards_json":""}}}';
//            pref_func.queryOrSet_params_by_JSON(true);
//          },
//          'JSON_query': function() {
//            pref_func.queryOrSet_params_by_JSON(true);
//          },
//          'JSON_query_default': function() {
//            pref_func.queryOrSet_params_by_JSON(pref_default());
//          },
//          'JSON_ex' : function() {
//            pref_func.site2_json_ex(false);
//            pref_func.settings.apply_pn13_1();
//          },
//          'JSON_ex_full' : function() {
//            pref_func.site2_json_ex(true);
//            pref_func.settings.apply_pn13_1();
//          },
//          'JSON_ex_filter' : function() {
//            pref_func.site2_json_ex(false, true);
//            pref_func.settings.apply_pn13_1();
//          },
          'EVAL' : function() {
//            pref_func.apply_prep(pref_func.settings.pn13.getElementsByTagName('TEXTAREA')['cli.eval_str'],true);
            pref_func.site2_eval();
          },
          'close': function(){pref_func.settings.show_hide();},
//          'save' : function(){if (localStorage) localStorage[pref.script_prefix+'.pref']=JSON.stringify(pref);},
          'save' : function(){
            if (localStorage) localStorage[pref.script_prefix+'.pref']=JSON.stringify(pref_func.site2_json_ex_remove());
          },
          'load_default' : function(e){
            var pref_def = pref_default();
            delete pref_def.settings;
            delete pref_def.catalog_board_list_sel;
            delete pref_def.filter;
            if (e.target.name==='load_default_of_this_page') {
              var nodes = e.target.parentNode.getElementsByTagName('*');
              for (var i=0;i<nodes.length;i++)
                if (nodes[i].name) {
                  var src = pref_func.get_tgt(nodes[i].name, pref_def, true);
                  if (src && src[0]) {
                    var dst = pref_func.get_tgt(nodes[i].name);
                    dst[0][dst[1]] = src[0][src[1]];
                  }
                }
            } else {
              if (e.target.name.indexOf('easy2')===0) delete pref_def.easy2;
              pref_func.pref_overwrite(pref,pref_def, undefined, undefined, true); // test_new_func to delete DOMs of files
              pref3.reload_required = true;
            }
//            pref = pref_default();
            pref_func.obj_init();
            pref_func.settings.onchange_funcs['settings.*'](); // remake html to initialize DOMs of files.
//            pref_func.settings.apply_pn13_1();
            pref_func.settings.apply_pn13_1(true, e.target.name==='load_default_of_this_page'); //  && cataLog.threads);  // writing to sessionStrage.
          },
          'load_default_of_this_page' : 'load_default',
          'load_samples' : function(){pref_func.pref_samples.init();},
          'catalog_triage_str': function(){if (cataLog.triage) cataLog.triage.remake();},
          'triage.hide_toggle': 'catalog_triage_str',
          'triage.menu_str': 'catalog_triage_str',
          'triage.postMenu_str': 'catalog_triage_str',
          'catalog.footer.*w/': function(e,src){if (cataLog.Footer) cataLog.Footer.update_all_force(e,src);},
          'float.footer.*w/': 'catalog.footer.*w/',
          'proto.footer.*w/': 'catalog.footer.*w/',
          'page.footer.*w/': 'catalog.footer.*w/',
          'thread.footer.*w/': 'catalog.footer.*w/',
          'headline.footer.*w/': 'catalog.footer.*w/',
          'liveTag.style.*w/': function(e,src){
            liveTag.redraw_boardlist();
            this['catalog.footer.*w/'](e,src);
          },
          'thread_reader.use' : thread_reader_init,
          'catalog.appearance.titleBar.*w/': function(e, name){
            var mode = name.split('.')[0];
            var clgs = gClg.Clgs;
            for (var i=0;i<clgs.length;i++) if (clgs[i].mode===mode) clgs[i].components.titleBar.getElementsByTagName('*')[name.split('.').pop()].style.display = (e.currentTarget.checked)? '' : 'none';
//            if (catalog_obj && catalog_obj.catalog_func()!==null) catalog_obj.catalog_func().pn12_0_2.getElementsByTagName('*')[name.split('.').pop()].style.display = (e.currentTarget.checked)? '' : 'none';
          },
//          'catalog_size_text_height': 'catalog_size_width', 
//          'catalog_size_text_width': 'catalog_size_width',
//          'catalog_size_height': 'catalog_size_width',
//          'catalog_size_width': function(e){
//            if (catalog_obj && catalog_obj.catalog_func()!==null) catalog_obj.catalog_func().catalog_resized(e.currentTarget);
//          },
////////          'catalog.click_area': function(){ // working code.
////////            if (pref_func.style_sheet) {
////////              if (pref_func.style_sheet.cssRules[2]) pref_func.style_sheet.deleteRule(2);
////////              pref_func.settings.onchange_funcs['catalog.click_area_add_rule']();
////////            }
////////          },
////////          'catalog.click_area_add_rule': function(){
////////////            var tgt  = site2[site.nickname].parse_funcs[site.whereami+'_html']['class_'+((pref.catalog.click_area==='entire')? 'thread' : 'thumbnail')]; // working code.
////////////            if (!tgt) tgt = 'dummy';
////////////            pref_func.style_sheet.insertRule('.'+tgt+' {cursor: pointer}',2);
////////            pref_func.style_sheet.insertRule('.'+pref.cpfx+((pref.catalog.click_area==='entire')? 'thread' : 'thumbnail')+' {cursor: pointer}',2);
////////          },
//          'sound.beep_length_f' : notifier.sound.make_beep,
//          'sound.beep_volume_f' : notifier.sound.make_beep,
//          'sound.beep_freq'     : notifier.sound.make_beep,
//          'sound.src'           : notifier.sound.src,
          'virtualBoard.bl.*W/': function(e,[,,src]){
            liveTag.update_boardlist(true);
            if (src==='show') this['virtualBoard.search.show'](e,true); // patch, this can be consolidate with initial lines in update_boardlist().
          },
//          'virtualBoard.p_board' : function(){liveTag.update_boardlist(true);},
//          'virtualBoard.p_remove' : 'virtualBoard.p_board',
//          'virtualBoard.max' : 'virtualBoard.p_board',
//          'virtualBoard.show': function(e){this['virtualBoard.p_board'](); this['virtualBoard.search.show'](e);},
//          'virtualBoard.dumb' : 'virtualBoard.p_board',
//          'virtualBoard.bl.showActives': 'virtualBoard.p_board',
          'easy.*w/': function(e,name){this['pn13_1_warning_reload'](); pref_func.pref_samples.onclick_event.call(e.target);},
          'easy2.beginner' : function(e){
            pref.easy2.presets = 1;
            this['easy2.presets'](e,pref.easy2.presets);
            this['easy2.apply'](e, true);
            this['reload'](e);
          },
          'easy2.presets' : function(e,idx){
            pref_func.pref_samples['easy2.presets']((idx!==undefined)? idx : pref.easy2.presets);
            pref_func.settings.apply_pn13_1();
          },
          'easy2.limits' : function(e,idx){
            pref_func.pref_samples['easy2.limits']();
            pref_func.settings.apply_pn13_1();
          },
          'easy2.apply' : function(e, force_reset){
            if (pref.easy2.reset || force_reset) this['load_default'](e);
            pref_func.pref_samples['easy2.presets']();
            this['pn13_1_warning_reload']();
            pref_func.pref_samples.onclick_event(null,pref.easy2);
          },
          'reload' : function(){location.reload();},
          'triage.sampleLoader': function(e){
            var tgt = e.target.dataset.tgt;
            if (e.target.selectedIndex===0) return; // for load_default_of_this_page
            if (tgt==='catalog_triage_str') this['triage.sample.*w/'](e, e.target.selectedOptions[0].value || e.target.selectedOptions[0].textContent);
            else {
              var samples = tgt.indexOf('footer')!=-1? {
                kill: 'KILL,x,,SPC, ,',
                simple: 'KILL,x,,SPC, ,,WATCH,w,,SPC, ,,UNWATCH,uw,,SPC, ,',
                menu: 'SPC, ,,MENU,\u25b6,',
                hl: 'KILL,x,,SPC, ,,NONE, H,background:lightcyan,NONE,R,opacity:0.7;background:gray,NONE,N,,',
                ex: 'EXPAND,\u25b6,\u25c0,SPC, ,,',
                mr: 'MERGEB,B,border:1px solid red,MERGE,M,,',
                sp: 'SPC, ,',
                get headline(){return this.hl+this.sp;},
                get headline_ex(){return this.ex+this.hl+this.sp;},
                get headline_merge(){return this.hl+this.mr+this.sp;},
                get headline_merge_ex(){return this.ex+this.hl+this.mr+this.sp;},
              } : tgt.indexOf('post')!=-1? {
                simple: 'PHIDE,hide,\n'+
                  'PHIDEAFTER,hide+,\n'+
                  'PUNHIDE,unhide,\n',
                get basic(){return this.simple+
                            'PCLEARHIDE,clear,\n';},
                get full(){return this.basic+
                           'PDISABLEHIDE,disable,\n'+
                           'PENABLEHIDE,enable,\n'+
                           'PWATCH,Watched,\n';},
              } : {
                menu_core: 'KILL,Hide permanently,\n'+
                  'TIME,Hide until new replies,\n'+
                  'WATCH,Watch,\n'+
                  'UNWATCH,Unwatch,\n'+
                  'STICKY,Sticky,\n'+
                  'UNSTICKY,Unsticky,\n',
                menu_undo: 'UNDO,Undo,',
                get menu_basic(){return this.menu_core + this.menu_undo;},
                get menu_attr(){return this.menu_core +
                  'SHOW,Show always,\n'+
                  'UNSHOW,Remove "show" mark,\n'+
                  'ATTR,Red border,border:solid 2px red\n'+
                  'ATTR,Red background,background:#fac2c5\n'+
                  'NONE,Remove style,\n' + this.menu_undo;},
                get menu_full(){return this.menu_attr + '\n'+ this.menu_arcMerge;},
                menu_arcMerge: 'ARC1,Take one-shot archive,\n'+
                  'ARC,Start archiving,\n'+
                  'UNARC,Stop archiving,\n'+
                  'MERGEB,Set merge base,\n'+
                  'MERGE,Merge,\n'+
//                  'MERGEOP,Merge threads in op,\n'+
                  'UNMERGE,Unmerge,\n'+
                  'UNDOMERGE,Undo last merge,\n',
              };
              var opt = e.target.selectedOptions[0];
              var pn = this['..'].pn13.getElementsByTagName('TEXTAREA')[tgt];
              pn.value = samples[(opt.value || opt.textContent)];
              pref_func.apply_prep(pn, true, true);
            }
            e.target.selectedIndex = 0;
          },
          'triage.sample.*w/': function(e, name){
            var samples = {
              simple_kill: 'KILL,X,',
              simple: 'KILL,X,,TIME,v,,WATCH,W,,UNWATCH,UW,,UNDO,U,',
              basic: 'KILL,X,,TIME,v,,WATCH,W,,UNWATCH,UW,,SHOW,\u2605,,UNSHOW,\u2606,,STICKY,S,,UNSTICKY,US,,UNDO,U,',
              basic_color:
                'KILL_N,X,,TIME_N,v,,WATCH,W,,UNWATCH,UW,,SHOW,\u2605,,UNSHOW,\u2606,,STICKY,S,,UNSTICKY,US,,UNDO,U,\n'+
                'NONE,O,,ATTR,O,background:#c3dcf9,ATTR,O,background:#b8efc2,'+
                'ATTR,O,background:#efedbe,ATTR,O,background:#fbd5fb,ATTR,O,background:#fac2c5\n'+
                'ATTR,O,border:3px solid blue,ATTR,O,border:3px solid lime,'+
                'ATTR,O,border:3px solid yellow,ATTR,O,border:3px solid orange,ATTR,O,border:3px solid red\n',
              colorful:
                'KILL,X,,KILL,X,background:#c3dcf9,KILL,X,background:#b8efc2,'+
                'KILL,X,background:#efedbe,KILL,X,background:#fbd5fb,KILL,X,background:#fac2c5\n'+
                'TIME,v,,TIME,v,background:#c3dcf9,TIME,v,background:#b8efc2,'+
                'TIME,v,background:#efedbe,TIME,v,background:#fbd5fb,TIME,v,background:#fac2c5\n'+
                'NONE,O,,NONE,O,background:#c3dcf9,NONE,O,background:#b8efc2,'+
                'NONE,O,background:#efedbe,NONE,O,background:#fbd5fb,NONE,O,background:#fac2c5\n'+
                'UNDO,U,,WATCH,W,,UNWATCH,UW,,GO,G,\n',
              borders:
                'KILL,X,,KILL,X,border:3px solid blue,KILL,X,border:3px solid lime,'+
                'KILL,X,border:3px solid yellow,KILL,X,border:3px solid orange,KILL,X,border:3px solid red\n'+
                'TIME,v,,TIME,v,border:3px solid blue,TIME,v,border:3px solid lime,'+
                'TIME,v,border:3px solid yellow,TIME,v,border:3px solid orange,TIME,v,border:3px solid red\n'+
                'NONE,O,,NONE,O,border:3px solid blue,NONE,O,border:3px solid lime,'+
                'NONE,O,border:3px solid yellow,NONE,O,border:3px solid orange,NONE,O,border:3px solid red\n'+
                'UNDO,U,,WATCH,W,,UNWATCH,UW,,GO,G,\n',
              toggles: 'KILL,X,,TIME,v,,WATCH,W,,UNWATCH,UW,,UNDO,U,,SHOW,\u2605,,UNSHOW,\u2605,border:2px inset,STICKY,\u2020,,UNSTICKY,\u2020,border:2px inset;,NONE,n,',
              samples:
                'KILL,delete forever,\n'+
                'TIME,hide until new replies,\n'+
                'NONE,back to default design,background:;border:;\n'+
                'NONE,border and background,background:#c3dcf9;border:3px solid blue\n'+
                'NONE,border 3px,border:3px solid blue\n'+
                'NONE,background,background:#c3dcf9\n'+
                'NONE,border 5px,border:5px solid blue\n'+
                'NONE,border 1px,border:1px solid blue\n'+
                'NONE,transparent,background:\n'+
                'NONE,shrink,width:100px;height:100px\n'+
                'GO,open this thread,\n',
              test: 'KILL,X,,TIME,v,,WATCH,W,,UNWATCH,UW,,SHOW,\u2605,,UNSHOW,\u2606,,STICKY,S,,UNSTICKY,US,,UNDO,U,,ARC,A+,,ARC1,A1,,UNARC,A-,',
            };
            pref.catalog_triage_str = samples[name.split('.').pop()];
//            pref_func.pref_overwrite(pref_func.tooltips.str,src);
            var pn = pref_func.settings.pn13.getElementsByTagName('*')['catalog_triage_str'];
            if (pn) pref_func.apply_prep(pn, false); // , true, null, true);
            this['catalog_triage_str'](e);
          },
//          'triage_simple': function(e){pref_func.pref_samples.onclick_event.call(e.currentTarget);},
//          'triage_basic' : 'triage_simple',
//          'triage_basic_color' : 'triage_simple',
//          'triage_colorful' : 'triage_simple',
//          'triage_borders' : 'triage_simple',
//          'triage_samples' : 'triage_simple',
//          'triage_test' : 'triage_simple',
          'virtualBoard.scanStart' : function(){if (catalog_obj && catalog_obj.catalog_func()!==null) catalog_obj.catalog_func().catalog_liveTag_scan_site();},
          'virtualBoard.scanStop' : function(){scan.abort();},
          'virtualBoard.scan_domains.*W/': function(e,[,,domain]){if (pref.virtualBoard.instant_scan) scan.keyword_load_1(domain);},
          'virtualBoard.search.show': function(e, init){
            var pn_bl = site.components.boardlist;
            if (!pn_bl) return;
            var bar = document.getElementById(pref.cpfx+'VBSearch');
//            var tgt_name = 'virtualBoard.search';
//            var bar = site.components.boardlist.getElementsByTagName('input')[tgt_name+'.str'];
//            if (bar) bar = bar.parentNode;
            if (pref.virtualBoard.bl.show && pref.virtualBoard.search.show) {
              if (!bar) {
                bar = cnst.dom('<span id="'+pref.cpfx+'VBSearch" style="float:right">'+ pref_func.format_html_str(pref_func.settings.html_funcs.show_hide4(pref_func.settings.html_funcs.show_hide4('&ensp;max:<ITB2"virtualBoard.bl.max"> <ICN"virtualBoard.bl.showActives">actives <ICN"virtualBoard.bl.dumb">dumb ',true,true)+' <ICN"virtualBoard.search.re">RE ',true)) + '</span>');
                var pn_vb = bar.appendChild(cnst.dom(pref_func.format_html_str('<ITBL10"virtualBoard.search.str"'+(!pref.virtualBoard.assb || pref.virtualBoard.search.str?'':' style="width:1em"')+' placeholder="filter tags...">')));
                var pn_blp = site.components.topnav || pn_bl;
                pn_vb.onfocus = function(){
                  if (this.style.width==='1em') this.style.width = null;
                  if (pref.virtualBoard.assb) pn_blp_addmouse(pn_blp);
                };
                pn_vb.onblur = function(e){if (pn_blp_fm) pn_blp_mouse(e);};
//                pn_vb.onblur = function(){if (!this.value) (site.components.topnav || pn_bl).addEventListener('mouseleave',pn_vb_shrink,false);};
//                pn_vb.onblur = function(){if (!this.value) setTimeout(function(){this.style.width = '1em';}.bind(this),100);}; // doesn't work for GUI, slow clicking is ignored.
////                bar = document.createElement('span');
////                bar.innerHTML = pref_func.format_html_str(pref_func.settings.html_funcs.show_hide4(pref_func.settings.html_funcs.show_hide4('&ensp;max:<ITB2"virtualBoard.bl.max"> <ICN"virtualBoard.bl.showActives">actives <ICN"virtualBoard.bl.dumb">passive ',true,true)+' <ICN"'+tgt_name+'.re">RE ',true)+'<ITBL10"'+tgt_name+'.str" placeholder="filter tags...">');
////                bar.style = 'float:right';
                pref_func.apply_prep(bar,false, undefined, 'never');
                pref_func.add_onchange(bar, pref_func.settings.onchange_funcs_formatted, pref_func.settings.oninput_funcs);
//                bar.oninput = function(e){pref.virtualBoard.search.str = e.target.value;liveTag.filter_onchange(pref.virtualBoard.search);}; // not storing to sessionStorage
//                var pn_settings = bar.childNodes[1];
//                var pn_open = bar.childNodes[2];
//                var pn_close = bar.childNodes[1].childNodes[2];
//                pn_open.onclick = function(){pn_open.style.display='none';pn_settings.style.display=null;};
//                pn_close.onclick = function(){pn_open.style.display=null;pn_settings.style.display='none'};
                pn_bl.insertBefore(bar, site.settings);
                pref_func.mirror_targets.boardlist = bar;
                Tooltips.add_root(bar);
                if (!init) liveTag.update_pn(); // activate filter
              }
            } else if (bar) {
              pn_bl.removeChild(bar); // 'if' for initial
              pref_func.mirror_targets.boardlist = null;
              liveTag.update_pn(); // disable filter
              Tooltips.remove_root(bar);
            }
            var pn_blp_fm = 0; // 1:focus, 0:mouse
            function pn_blp_mouse(e){
              pn_blp_fm = e.type==='blur'? pn_blp_fm & 0x01 : pn_blp_fm & 0x02 | (e.type==='mouseenter'? 0x01 : 0);
              if (pn_blp_fm) return;
              pn_blp.removeEventListener('mouseleave',pn_blp_mouse,false);
              pn_blp.removeEventListener('mouseenter',pn_blp_mouse,false);
              if (!pn_vb.value) pn_vb.style.width = '1em';
            }
            function pn_blp_addmouse(pn){
              if (!pn_blp_fm) {
                pn.addEventListener('mouseleave',pn_blp_mouse,false);
                pn.addEventListener('mouseenter',pn_blp_mouse,false);
              }
              pn_blp_fm = 3;
            }
//            function pn_vb_shrink(e){
//              e.currentTarget.removeEventListener('mouseleave',pn_vb_shrink,false);
//              pn_vb.style.width = '1em';
//            }
          },
          'virtualBoard.search.*': 'virtualBoard.search.str', // for 'search.re'. checkboxes don't fire oninput, though MDN says they fire. https://developer.mozilla.org/ja/docs/Web/HTML/Element/Input/checkbox
          'liveTag.ex_list_str' : function(){liveTag.ex_list_changed();},
          'liveTag.inherit_board_name' : function(){liveTag.inherit_board_name_changed();},
          'liveTag.inherit_board_tags' : function(){liveTag.inherit_board_tags_changed();},
          'page.mark_new_posts' : function(){if (gClg.Clgs) for (var i=0;i<gClg.Clgs.length;i++) gClg.Clgs[i].view_time_filter_changed();},
          'recovery.comment' : function(){recovery.setup();},
          '*.scroll_lock': function(){cataLog.general_event_handler.setup();},
          'button_test': function(){
//            site2['meguca'].catalog_native_prep(cataLog.components.pn12_0_4, cataLog.components.pn12_0, cataLog.embed_mode==='catalog');
            function callback(domain, board, no, result){
              console.log(result);
            }
            IDB.req_raw('meguca', 'meguca', 'mine', null, callback, 'get_all', true);
          },
          'archive': {
            'restore2': function(e, files, posts){
              if (!cataLog.threads) return;
              if (e && !pref.archive.restore_auto && e.target.name!=='archive.restore_button') return;
              if (!posts) {
                files = Array.prototype.slice.call(pref3.archive.dir.files);
                posts = files.filter(function(v,i,a){return v.name.search(/\.(json|html*)$/)!=-1;});
                posts = posts.filter(function(v,i,a){return v.name.indexOf('_deleted.json')==-1;}); // PATCH
              }
              var imgs = files.filter(function(v,i,a){return v.name.search(/\.(json|html*)$/)==-1;});
              if (posts.length===0) return;
              archiver.restore_entry(posts,imgs, this['clear_files_button'], files.filter(function(v,i,a){return v.name.indexOf('_deleted.json')!=-1;}));
            },
            'dir': 'restore2',
            'files_sel': function(e){
              e.target.parentNode.querySelectorAll('span[name="FILES_ARCHIVE0"]')[0].style.display = (pref.archive.files_sel===0)? '' : 'none';
              e.target.parentNode.querySelectorAll('span[name="FILES_ARCHIVE1"]')[0].style.display = (pref.archive.files_sel===1)? '' : 'none';
              e.target.parentNode.querySelectorAll('span[name="FILES_ARCHIVE2"]')[0].style.display = (pref.archive.files_sel===2)? '' : 'none';
              if (pref.archive.files_sel===2) this['queryList']();
            },
            'restore_button': function(e){
              if (pref.archive.files_sel===0) this['restore2'](e);
              if (pref.archive.files_sel===2) this['IDB_thread_sel'](e);
              else this['restore'](e);
            },
            'restore': function(e){
//            if (!cataLog.threads) return;
//            if (!pref.archive.restore_auto && e.target.name!=='archive.restore') return;
              var posts= pref3.archive.jsons.files;
              var imgs = pref3.archive.imgs.files;
//            if (pref.archive.force_json) files = Array.prototype.filter.call(files,function(v){return v.name.substr(-5,5)==='.json';});
              if (posts.length===0 || (pref.archive.load_img && imgs.length===0)) return;
//            archiver.restore_entry(files,imgs, this['clear_files_button']);
              this['restore2'](null, Array.prototype.slice.call(imgs), posts);
            },
            'jsons': 'restore',
            'imgs': 'restore',
            'clear_files_button': function(){
              var doms = [pref3.archive.jsons, pref3.archive.imgs];
              for (var i=0;i<doms.length;i++) if (doms[i] && doms[i].parentNode) {
                var dom_new = doms[i].parentNode.insertBefore(doms[i].cloneNode(doms[i]),doms[i]);
                dom_new.onchange = doms[i].onchange;
                doms[i].parentNode.removeChild(doms[i]);
                dom_new.onchange({target:doms[i], currentTarget:doms[i]});
              }
            },
            get pn_IDB_board_sel(){return pref_func.settings.pn13 && pref_func.settings.pn13.querySelector('select[name="archive.IDB_board_sel"]');},
            get pn_IDB_thread_sel(){return pref_func.settings.pn13 && pref_func.settings.pn13.querySelector('select[name="archive.IDB_thread_sel"]');},
            'IDB_select_multiple': function(){
              this.pn_IDB_board_sel.multiple = pref.archive.IDB_select_multiple;
              this.pn_IDB_thread_sel.multiple = pref.archive.IDB_select_multiple;
            },
            'queryQuota': function(){
              if (navigator.webkitTemporaryStorage) navigator.webkitTemporaryStorage.queryUsageAndQuota(this['showQuota'].bind(this));
            },
            'showQuota': function(usage, limit){
              var tgt = pref_func.settings.pn13.querySelector('span[name="SHOW_QUOTA"]');
              if (tgt) tgt.textContent = usage.toLocaleString() + ' / ' + limit.toLocaleString() + ' (' + (usage*100/limit).toString().slice(0,5) + '%)';
            },
            'queryList': function(){
              if (pref.features.IDB) if (!pref4.archive.IDB_board_sel_options)
              if (!brwsr.ff) window.indexedDB.webkitGetDatabaseNames().onsuccess = this['showList'].bind(this);
                else setTimeout(function(){this.showList({target:{result:Object.keys(liveTag.mems[site.nickname])}})}.bind(this),0);
            },
            'showList': function(e){
              if (e) {
                var results = e.target.result;
                var options = ['Select board'];
                for (var i=0;i<results.length;i++) options[options.length] = results[i].replace(/^[^\/]*/,'');
                options[options.length] = 'ALL (' + results.length + ')';
              } else options = null;
              pref4.archive.IDB_board_sel_options = options;
              pref_func.apply_prep(this.pn_IDB_board_sel, false);
            },
            'IDB_board_sel': function(){
              pref4.archive.IDB_thread_sel_options = null;
              var pn = this.pn_IDB_thread_sel;
              pref_func.apply_prep(pn, false);
              pn.style.display = '';
              var boards = this['IDB_selected_boards']();
              for (var i=0;i<boards.length;i++) IDB.req(site.nickname, boards[i], null, null, this['showThreadList_fromIDB'].bind(this), 'list_os');
            },
            'IDB_selected_boards': function(){
              if (pref.archive.IDB_board_sel===0) return [];
//            var board = pref4.archive.IDB_board_sel_options[pref.archive.IDB_board_sel];
//            return (board[0]==='A')? pref4.archive.IDB_board_sel_options.slice(1,-1) : [board]; // ALL
              return (pref4.archive.IDB_board_sel_options && pref.archive.IDB_board_sel===pref4.archive.IDB_board_sel_options.length-1)? pref4.archive.IDB_board_sel_options.slice(1,-1) :
                Array.prototype.slice.call(this.pn_IDB_board_sel.options).filter(function(v){return v.selected;}).map(function(v){return v.textContent;});
            },
            'showThreadList_fromIDB': function(domain, board, nos){
              var boards = this['IDB_selected_boards']();
              if (boards.length==1) board = '';
              var options = pref4.archive.IDB_thread_sel_options || ['ALL'];
              for (var i=0;i<nos.length;i++) options[options.length] = board + nos[i];
              options[0] = ' '; // assure the top before sort.
              options.sort();
              options[0] = 'ALL (' + (options.length-1) +')';
              pref4.archive.IDB_thread_sel_options = options;
              pref_func.apply_prep(this.pn_IDB_thread_sel, false);
            },
            'IDB_thread_sel': function(e){
              if (!cataLog.threads) return;
              if (!pref.archive.restore_auto && e.target.name!=='archive.restore_button') return;
              if (pref.archive.clear_threads && cataLog.threads) pClg.clear_threads(0);
              this['IDB.selected_threads'](IDB.req, null, this['restore3'].bind(this), 'get_all');
            },
            'IDB.selected_threads': function(func, arg3, arg4, arg5){
              var ths = (pref.archive.IDB_thread_sel===0)? pref4.archive.IDB_thread_sel_options.slice(1) : // ALL
                Array.prototype.slice.call(this.pn_IDB_thread_sel.options).filter(function(v){return v.selected;}).map(function(v){return v.textContent;});
//                                                      [pref4.archive.IDB_thread_sel_options[pref.archive.IDB_thread_sel]];
              var boards = this['IDB_selected_boards']();
              var board = (boards.length===1)? boards[0] : '';
              if (func) for (var i=0;i<ths.length;i++) func(site.nickname, board || ths[i].replace(/[^\/]*$/,''), ths[i].replace(/\/[^\/]*\//,''), arg3, arg4, arg5);
              else {
                var proto = {domain:site.nickname, boards:boards};
                for (var i=0;i<ths.length;i++) ths[i] = {board:board || ths[i].replace(/[^\/]*$/,''), no:ths[i].replace(/\/[^\/]*\//,''), __proto__:proto};
                return ths;
              }
            },
            'restore3': function(domain, board, no, result, refresh_tgt){
              var obj = this['consolidate_IDB_result'](result, domain);
              if (obj.posts.length>0) {
                var th = archiver.restore({name:(site.nickname+board+no).replace(/\//,'-')}, site2[domain].parse_funcs.thread_json.prep_to_archive(obj.posts), obj.tn, true, refresh_tgt);
                if (obj.posts_deleted.length>0) {
                  if (pref.test_mode['80']) board = board.slice(0,-1)+'_IDB/';
                  var lth = liveTag.mems.init({domain:domain, board:board, no:no});
                  archiver.prep_deleted_posts(th, lth, true, obj.posts_deleted, refresh_tgt);
                }
              }
            },
            'consolidate_IDB_result': function(result, domain){
              var posts = [];
              var posts_deleted = [];
              var images = {};
              var thumbs = Object.create(images);
              var keys = Object.keys(result);
              for (var i=0;i<keys.length;i++) {
                if (keys[i].indexOf('posts_deleted')===0) posts_deleted = posts_deleted.concat(result[keys[i]]);
                else if (keys[i].indexOf('posts')===0) posts = posts.concat(result[keys[i]]);
                else if (keys[i]==='pruned_time') continue; // patch for old files.
                else {
                  var ext = keys[i].replace(/[^\.]*./,'');
                  var type = (ext==='webm')? 'video/webm' : 'image';
                  var file_obj = {type:type, file:result[keys[i]]};
                  if (keys[i].indexOf('tn_')===0) thumbs[keys[i].substr(3)] = file_obj;
                  else images[keys[i].substr(4)] = file_obj;
                }
              }
              var i = 0;
              while (i<posts.length-1 && posts[i].no<posts[i+1].no) i++;
              if (i!==posts.length-1) { // out of order or duplication may due to queueing system of IDB, retry or reload.
                posts.sort(this['consolidate_IDB_sort_func']);
                i = 0;
                while (i<posts.length-1) {
                  if (posts[i].no===posts[i+1].no) {
                    var j=i+2;
                    while (j<posts.length && posts[i].no===posts[j].no) j++;
                    if (domain==='meguca') {
                      var editing = posts[i].editing;
                      var k=i+1;
                      while (k<j && posts[k].editing==editing) k++;
                      if (k<j) {
                        k=j;
                        while (--k>i) if (posts[k].editing) {posts.splice(k,1);j--;}
                      }
                    }
                    while (--j>i) posts.splice(j,1); // 4chan has different coms when anchor refers deleted posts.
//                    while (--j>i) if (posts[j-1].com===posts[j].com) posts.splice(j,1); // BUG. posts[x].no must be unique and simple increase at 'check_deleted_posts'.
                  }
                  i++;
                }
              }
              j=0;
              for (var i=0;i<posts_deleted.length;i++) {
                while (j<posts.length && posts[j].no<posts_deleted[i].no) j++;
                if (j<posts.length && posts[j].no===posts_deleted[i].no) posts.splice(j,1);
              }
              var func_init = domain && site2[domain].parse_funcs.thread_json.consolidate_IDB_result_sub;
              if (func_init && posts.length!==0) func_init(posts);
              return {posts:posts, posts_deleted:posts_deleted, img:images, tn:thumbs};
            },
            'consolidate_IDB_sort_func': function(a,b){return (a.no!==b.no)? a.no - b.no : (a.com<b.com)? -1:1},
            'IDB.delete_board': function(){
              var boards = this['IDB_selected_boards']();
              for (var i=0;i<boards.length;i++) window.indexedDB.deleteDatabase(pref.script_prefix+'.'+boards[i]);
//              this['IDB.delete_board_pn']();
//            },
//            'IDB.delete_board_pn': function(){
              var pn = this.pn_IDB_thread_sel;
              if (pn) pn.style.display = 'none';
              this['showList'](null);
              this['queryList']();
            },
            'IDB.delete_thread': function(){
              this['IDB.selected_threads'](IDB.req, null, null, 'delete_th');
              this['IDB_board_sel'](null);
            },
            'IDB.delete_imgs': function(){
              this['IDB.selected_threads'](IDB.req, IDBKeyRange.bound('img_', 'img`', false, true), null, 'delete');
            },
            'IDB.export_thread': function(){
              var ths = this['IDB.selected_threads']();
              var filename = 'CatChan_IDB_'+ths[0].domain+'-'+((ths[0].boards.length==1)? ths[0].board.slice(1,-1) : ths[0].boards.length)+'-'+
                                                              ((ths.length===1)? ths[0].no : ths.length) +'.tar';
              var file_id = 'IDB_extract_'+Date.now();
              var callback = (function(this_obj){
                var count = ths.length;
                return function(domain, board, no, result){
                  this_obj['export_thread'](domain, board, no, result, file_id);
                  if (--count===0) archiver.tar.flush(file_id, filename);
                }
              })(this);
              for (var i=0;i<ths.length;i++) IDB.req(ths[i].domain, ths[i].board, ths[i].no, null, callback, 'get_all');
            },
            'export_thread': function(domain, board, no, result, file_id, filename){
              var obj = this['consolidate_IDB_result'](result, domain);
//            var file_id = 'CatChan_IDB_'+domain+'-'+board.slice(1,-1)+'-'+no+'.tar';
              var props = Object.keys(obj);
              for (var i=0;i<props.length;i++) {
                var props2 = (Array.isArray(obj[props[i]]))? ['dummy'] : Object.keys(obj[props[i]]);
                for (var j=0;j<props2.length;j++) {
                  var isPosts = props[i].indexOf('posts')===0;
                  if (isPosts && obj[props[i]].length===0) continue;
                  var data = (isPosts)? new Blob([JSON.stringify(site2[domain].parse_funcs.thread_json.prep_to_archive(obj[props[i]]))], {type:'text/plain'}) : obj[props[i]][props2[j]].file;
                  var suffix = (isPosts)? domain+'-'+board.slice(1,-1)+'-'+no+ props[i].substr(5)+'.json' : props[i] +'_'+ props2[j];
                  archiver.tar.add_blob(data, {domain:domain, board:board, no:no}, suffix, file_id, true);
                }
              }
              if (filename) archiver.tar.flush(file_id, filename);
            },
            'IDB.reset_time': function(){
              this['IDB.selected_threads'](archiver.clean_list);
            },
            'start': function(e){archiver.store_entry((e.target.name==='archive.start')? 'ARC' : 'ARC1');},
            'oneshot': 'start',
            'kwd.*': function(){pref.archive.kwd.rexps = comf.kwd_prep_regexp(pref.archive.kwd);},
          },
          'test_mode.js_load': function(){
            var file = pref_func.settings.pn13.getElementsByTagName('input')['test_mode.js_file'].files[0];
            var url = window.URL.createObjectURL(file);
            var scr = document.createElement('script');
            scr.src = url;
            site.script_body.appendChild(scr);
          },
//          '*.merge': function(e){site2['DEFAULT'].update_posts_merge_bases.onchange_merge(e);},
//          '*.merge_list': '*.merge',
//          '*.merge_list_str': '*.merge',
//          '*.merge_lv': function(e){site2['DEFAULT'].update_posts_merge_bases.onchange_lv(e);},
//          '*.merge_lv_str': '*.merge_lv',
          'scanBoard': function(e){if (cataLog.event_func) cataLog.event_func(e);},
          'scanBoardIf': 'scanBoard',
          'scanSite': 'scanBoard',
          'scanSiteIf': 'scanBoard',
          'tighten_loosed_limits': function(e){pref4.search_posts_active_once = false;},
          '*w/.t2h_sel': function(e,src){if (cataLog.Footer && pref.catalog.footer.use && pref.catalog.footer.flag) cataLog.Footer.update_all_force(e,src,true);},
          '*w/.t2h_num_of_posts': '*w/.t2h_sel',
          'debugWindow': function(){
            debugWindow = cnst.init3({func_str:'left:0px:top:0px:tb:width:400px:height:400px:overflow:auto:Show:bottom_top:tb_press:float'});
            debugWindow.connect = function(){return debugWindow.cn.appendChild(document.createElement('div'));};
          },
          'cleanMerge': function(){
//            var mems = pref.proto.merge_list_str.replace(/\+/g,',+').replace(/\n/g,',\n,').split(',');
//            var has_tails = false;
//            for (var i=mems.length-1;i>=0;i--) {
//              var tgt = mems[i].replace(/^\s*/,'').replace(/\s*$/,'');
//              if (tgt==='\n' || tgt==='') continue;
//              if (tgt.search(/[^/+]+\/[^/]+\/[^/]+/)===0) { // head with fullname
//                if (!has_tails) mems.splice(i,1); // removes single heads
//                has_tails = false;
//              } else has_tails = true;
//            }
//            pref.proto.merge_list_str = pref_func.fmt4str2(mems.join(',').replace(/,\+/g,'+').replace(/,\n,/g,'\n'));
//            pref_func.apply_prep_1n('proto.merge_list_str', false, true, null, true); // make obj, call event
            var req = {
              key:'cleanMerge',
              obj:Object.keys(pref3.proto.merge_list_obj2[':REV']).filter(function(key){
                var dbt = comf.name2dbt(key);
                var url = site2[dbt[0]].make_url4(dbt);
                return url && url[0] && url[1];}),
              found_threads: 0, max_threads:Infinity, // for emulation
              __proto__:cataLog.scan_boards.args_proto}.initialize();
            httpd.req_serial(req,2);
          },
          'catalog.style_general_list': function(){
            for (var i=0;i<gClg.Clgs.length;i++) gClg.Clgs[i].onchange_funcs['filter.attr_list_str']();
          },
          'catalog.style_general_list_str': function(){
            if (pref.catalog.style_general_list) this['catalog.style_general_list']();
            if (pref.test_mode['202']) this['dashboard.rss_redraw'](); // for debug
          },
          'postFilter.*w/': function(e,name){
            if (name==='postFilter.use' || pref.postFilter.use) for (var i=0;i<gClg.Clgs.length;i++) gClg.Clgs[i].onchange_funcs['filter.'+name+' w/'](e,name);
            pref.postFilter.obj7_old = null;
          },
          '*.hide_posts_without_images': function(){
            for (var i=0;i<gClg.Clgs.length;i++)
              for (var name in gClg.Clgs[i].threads) cataLog.format_html.hide_posts_without_images(gClg.Clgs[i], gClg.Clgs[i].threads[name][16].posts);
          },
          'MODEVIEW.format.thumb.*.*W/': function(e, [view,,,size,tgt]){
            if (gClg) gClg.Clgs.forEach(clg=>{
              if (clg.view===view && clg.pref[clg.view].format.thumb.size===size)
                if (tgt==='s2') {
                  clg.func_hide_all(clg.threads,true);
                  clg.show_catalog(0);
                } else clg.image_resize_all();
            });
          },
          'dashboard.rss.unlock.*W/': function(e, [,,,src]){
            var tgts = this['..'].pn13.querySelectorAll('table td '+(src==='isTrusted'? 'option[disabled]':'input[readonly]'));
            if (src==='isTrusted') for (var i=0;i<tgts.length;i++) tgts[i].disabled = null;
            else for (var i=0;i<tgts.length;i++) if (tgts[i].name.slice(-4)==='.opt') {
              tgts[i].readOnly = null;
              tgts[i].style = null;
            }
          },
          'dashboard.rss.redraw_threads': function(tgt){
            var dm = tgt.db_formatted.split('/')[0];
            var bd = '/'+tgt.db_formatted.split('/')[1]+'/';
            var rss_live = site2[dm].board2rss_options[bd];
            if (!rss_live) return;
//            rss_live[1].isTrusted = tgt.isTrusted; // this will be done by calling pref_func.RSS_json() for arr, but auto needs this.
            var ldb = liveTag.mems[dm] && liveTag.mems[dm][bd];
            if (!ldb) return;
            for (var t in ldb) ldb[t].th.parse_funcs.rm_cached_vals(ldb[t].th);
            var tgt_keys = Object.keys(ldb).map(no=>ldb[no].key);
            for (var i=0;i<gClg.Clgs.length;i++) gClg.Clgs[i].func_hide_all(tgt_keys, true);
//            for (var i=0;i<gClg.Clgs.length;i++) { // working code
//              var clg = gClg.Clgs[i];
//              var catalog_expand_with_hr = (clg.mode==='float' && pref.catalog_expand_with_hr) || pref[clg.mode].env.disp_filler; // patch, shold be removed
//              for (var t in ldb) {
//                var tgt_th = clg.threads[ldb[t].key];
//                if (tgt_th) {
//                  if (tgt_th[1]) if (clg.func_hide(ldb[t].key,catalog_expand_with_hr)) tgt_th[1] = false;
//                  clg.remake_html_prep(tgt_th);
//                }
//              }
//            }
          },
          'dashboard.rss_update_status': function(url){
            if (!this['..'].pn13 || this['..'].options[pref.settings.indexing].indexOf('RSS dashboard')!==0) return;
            var pf_data = pref.dashboard.rss;
            var tgt = null;
            for (var i=0;i<pf_data.arr.length;i++) if (pf_data.arr[i].urls_arr.indexOf(url)!==-1) {tgt = 'arr'; break;}
            if (!tgt) for (var i=0;i<pf_data.auto.length;i++) if (pf_data.auto[i].urls_arr.indexOf(url)!==-1) {tgt = 'auto'; break;}
            var tgt_name = tgt+'.'+i;
            this['..'].pn13.querySelector('span[data-name="'+tgt_name+'"]').parentNode.innerHTML = pref_func.settings.htmls.rss_dashboard_status(pf_data[tgt][i], tgt_name);
          },
          'dashboard.rss_redraw': function(append){ // redraw_table
            if (!this['..'].pn13 || this['..'].options[pref.settings.indexing].indexOf('RSS dashboard')!==0) return;
            var html = pref_func.format_html_str(this['..'].htmls.rss_dashboard_table()); // calls site2['rss'].generate_registered_rss
            if (append) this['..'].pn13.getElementsByTagName('tbody')[3].innerHTML = html.match(/<tbody.*?<\/tbody>/g)[3].slice(0,-8).replace(/<tbody[^>]*>/,'');
            else this['..'].pn13.getElementsByTagName('table')[0].innerHTML = html;
            pref_func.add_onchange(this['..'].pn13_1,this['..'].onchange_funcs_formatted);
            this['..'].apply_pn13_1();
          },
          'dashboard.rss_com': function(){
            var com_class = 'hr_sep'+(pref.dashboard.rss_com==='show'? '' : pref.dashboard.rss_com==='no'?' hidden':' no_text');
            Array.prototype.forEach.call(this['..'].pn13.getElementsByClassName('hr_sep'),v=>v.className=com_class);
          },
          'dashboard.rss_live': function(){
            if (pref.dashboard.rss_live) this['dashboard.rss_redraw']();
          },
          'dashboard.rss.remake_all': function(){
            this['dashboard.rss_redraw'](); // remake myself, updates pf_data in this. // reload color/sticky at changing url or board
            var done_init = pref_func.RSS_json(null, 'arr')[1];
            site2['rss'].rss_appended.forEach(v=>site2['rss'].add_rss_append(v, done_init, 'auto'));
//            var done = comf.Array_toObj(pref.dashboard.rss.arr.map(v=>v.db_formatted),null);
//            for (var i of site2['rss'].rss_appended.values()) {
//              site2['rss'].add_rss_append(i, (i[0] in done), true);
//              done[i[0]] = null;
//            }
          },
          'dashboard.rss.gl.*.*W/': 'dashboard.rss.arr.*.*W/',
          'dashboard.rss.new.*.*W/': 'dashboard.rss.arr.*.*W/',
          'dashboard.rss.auto.*.*W/': 'dashboard.rss.arr.*.*W/',
          'dashboard.rss.arr.*.*W/': function(e, [,,arr, order_str, src]){
            var order = parseInt(order_str,10);
            var et = e.target;
            var pf_data = pref.dashboard.rss;
            var tgt = pf_data[arr][order];
            var pn13 = this['..'].pn13;
            var def = pf_data.DEFAULT[pf_data.DEFAULT.length-1];
            if (src==='col') {
              // color picker needs physical click, https://stackoverflow.com/questions/17729165/trigger-click-on-html-5-color-picker-with-jquery
              // https://stackoverflow.com/questions/29676017/open-browser-standard-colorpicker-with-javascript-without-type-color
              this['colorPicker.args'] = {db:tgt.db_formatted, pn:et};
              var popup = pn13.querySelector('[data-color_picker=popup]');
              popup.style.display = '';
              popup.style.top = et.parentNode.offsetTop + et.parentNode.parentNode.parentNode.parentNode.offsetTop;
              popup.style.left = et.parentNode.offsetLeft + et.parentNode.offsetWidth;
              this['colorPicker.*W/'](null, [null, 'init']);
            } else if (src==='sticky') {
              if (tgt.db_formatted) this['dashboard.rss.revise_general_list_str'](tgt.db_formatted, et.checked?'STICKY':'UNSTICKY', '');
            } else {
              if (src==='isTrusted' || /*src==='t1' || src==='t2' ||*/ src==='url' || src==='db' || src==='opt'){ //  || src==='isTrusted_gl' || src==='opt_gl') {
                var redraw = arr!=='gl' && src!=='url'? [tgt] : []; // contains case of (src==='isTrusted' && arr==='arr')
                if (src==='isTrusted' && arr==='gl') {
                  var tgts = pf_data.arr.concat(pf_data.auto, pf_data.new);
                  for (var i=0;i<tgts.length;i++) tgts[i].isTrusted_back = tgts[i].isTrusted;
                  tgt.isTrusted_opt_def = tgt.isTrusted;
                  for (var i=tgts.length-1;i>=0;i--) { // reverse ordering is required to revise each entry of RSS.str
                    var t = tgts[i];
                    t.isTrusted = pref.dashboard.rss_apply_global? tgt.isTrusted : t.isTrusted_back; // invoke setter to make appropriate obj.
                    if (t.end) pref.RSS.str = pref.RSS.str.slice(0,t.start) + t.stringify() + pref.RSS.str.slice(t.end); // tgt.end>0 if the extry exists
                    if (pref.dashboard.rss_apply_global && t.isTrusted!==t.isTrusted_back) redraw.push(t);
                  }
//                } else if (src==='isTrusted') { // consolidated to redraw = ....
////                  tgt.isTrusted = et.selectedIndex;
//                  this['dashboard.rss.redraw_threads'](tgt);
////              } else if (src==='t1' || src==='t2') {
////                  tgt.isTrusted = !(src==='t1'? et : et.parentNode.previousSibling.firstChild).checked? 0 : !(src==='t1'? et.parentNode.nextSibling.firstChild : et).checked? 1 : 2;
////                  et.parentNode.parentNode.querySelector('[data-show_value]').textContent = tgt.isTrusted;
                } else if (src==='url' && et.value[0]==='[' || src==='opt'){ //  || src==='opt_gl') { // JSON strings of url or opt were discarded
                  try {
                    tgt[src] = JSON.parse(src==='url'? et.value : et.value||'{}'); // '{'+et.value+'}');
                  } catch(e){
                    et.style.background = 'pink';
                    return;
                  }
                }
//                } else if (src==='url') { // value was set as text already
//                  et.style.background = null;
//                  if (tgt.url[0]==='[') {
//                    try {
//                      tgt.url = JSON.parse(tgt.url);
//                    } catch(e){
//                      et.style.background = 'pink';
//                      return;
//                    }
//                  }
////                this['..'].pn13.getElementsByTagName('INPUT')['dashboard.rss.'+arr+'.'+order+'.db'].placeholder = tgt.db_def;
////                et.parentNode.previousSibling.firstChild.placeholder = tgt.db_def;
//                } else if (src==='opt') {
//                  et.style.background = null;
//                  try {
//                    tgt.opt = JSON.parse('{'+et.value+'}');
//                  } catch(e){
//                    et.style.background = 'pink';
//                    return;
//                  }
//                }
                if (arr==='arr') pref.RSS.str = pref.RSS.str.slice(0,tgt.start) + tgt.stringify() + pref.RSS.str.slice(tgt.end);
                else if (arr==='gl') {
                  var fst_os = pf_data['arr'].length>0? pf_data['arr'][0].outer_start : pref.RSS.str.length;
                  var str_obj = JSON.stringify(tgt.obj[2]);
                  var out = str_obj.length>2;
                  pref.RSS.str = pref.RSS.str.slice(0, def? (out?def.start:def.outer_start):fst_os) + (out?(def?'':'DEFAULT:')+str_obj+(def?'':'\n'):'') +
                                 pref.RSS.str.slice(   def? (out?def.end:def.outer_end):fst_os);
                }
              } else if (src==='del' || src==='up' || src==='add') {
                if (src==='del') {
                  pref.RSS.str = pref.RSS.str.slice(0,(arr==='arr'? tgt:def).outer_start) + pref.RSS.str.slice((arr==='arr'? tgt:def).outer_end); // tgt.end+(pref.RSS.str[tgt.end]==='\n'?1:0));
                  if (arr==='arr') site2['rss'].rss_appended.unshift(tgt.obj); // set(tgt.obj[0], tgt.obj);
                } else if (src==='up') { // up are only in arr
                  var prev = pf_data[arr][order-1];
                  if (prev) pref.RSS.str = pref.RSS.str.slice(0,prev.outer_start) + pref.RSS.str.slice(tgt.outer_start,tgt.outer_end) + pref.RSS.str.slice(prev.outer_start,prev.outer_end) + pref.RSS.str.slice(tgt.outer_end);
//                  var next = pf_data[arr][parseInt(order,10)+1];
//                  if (next) pref.RSS.str = pref.RSS.str.slice(0,tgt.outer_start) + pref.RSS.str.slice(next.outer_start,next.outer_end) + pref.RSS.str.slice(tgt.outer_start,tgt.outer_end) + pref.RSS.str.slice(next.outer_end);
                } else if (src==='add') {
                  if (arr==='new' && (!tgt.db && !tgt.url)) return;
                  pref.RSS.str += tgt.stringify()+'\n';
//                  pref.RSS.str = pref.RSS.str.slice(0,fst_os) + tgt.stringify()+'\n' + pref.RSS.str.slice(fst_os);
                  if (arr==='auto') site2['rss'].rss_appended.splice(order,1); // delete(tgt.obj[0]);
                }
                if ((tgt.db_formatted in pf_data.mult_dbs) && (src!=='up' // may change which is the top
                                                            || prev && tgt.db_formatted===prev.db_formatted && (tgt.isTrusted!==prev.isTrusted || tgt.opt!==prev.opt))) redraw = [tgt];
              } else if (src==='expand') {
                var urls = tgt.urls_arr;
                var str = '';
                for (var i=0;i<urls.length;i++) {
                  tgt.url = urls[i];
                  if (arr==='arr') str += tgt.stringify() + (i===0? pref.RSS.str.slice(tgt.end, tgt.outer_end) : '\n');
                  else site2['rss'].rss_appended.splice(order+i,i===0?1:0, tgt.obj.slice());
                }
                if (arr==='arr') pref.RSS.str = pref.RSS.str.slice(0,tgt.start) + str + pref.RSS.str.slice(tgt.outer_end);
              } else if (src==='close') {
                var urls = tgt.urls_arr;
                var end = tgt.outer_end;
                var t;
                var next_idx = order;
                while (++next_idx<pf_data[arr].length && (t = pf_data[arr][next_idx], tgt.db_formatted===t.db_formatted)) {
                  Array.prototype.push.apply(urls, t.urls_arr);
                  end = t.outer_end;
                  if (arr==='auto') site2['rss'].rss_appended.splice(order+1,1);
                }
                tgt.url = urls;
                if (arr==='arr') pref.RSS.str = pref.RSS.str.slice(0,tgt.start) + tgt.stringify() + pref.RSS.str.slice(tgt.end, tgt.outer_end) + pref.RSS.str.slice(end);
              }
              this['dashboard.rss.remake_all'](); // remake myself, updates pf_data in this. // reload color/sticky at changing url or board
              if (redraw && redraw.length>0) {
                for (var i=0;i<redraw.length;i++) this['dashboard.rss.redraw_threads'](redraw[i]);
                for (var i=0;i<gClg.Clgs.length;i++) gClg.Clgs[i].show_catalog(0);
              }
            }
            pref_func.apply_prep_save(null, true);
          },
          'colorPicker.args': null,
          'colorPicker.colBtn.*W/': 'colorPicker.*W/',
          'colorPicker.*W/': function(e, [,src, col_in]){
            var pf = pref.colorPicker;
            var col = pf.col = src==='colBtn'? col_in : pf[src==='txt'||src==='picker'?src:'col'];
            var str = src==='Clear'? '' : (pf.bgbd==='bg'? 'background:' : 'border:'+(pf.bds||'2px solid')+' ')+col+';';
            if (src==='Set' || src==='Clear' || src==='colBtn') {
              var args = this['colorPicker.args'];
              if (args.db) this['dashboard.rss.revise_general_list_str'](args.db, src==='Clear'?'NONE':'ATTR', str);
              var pn = args.pn;
            } else pn = this['..'].pn13.querySelector('[data-color_picker=sample]');
            pn.setAttribute('style',(pn.getAttribute('style')||'').replace(/(background|border)\s*:[^;]*(;|$)/,'')+str);
            if (src==='Set' || src==='Cancel' || src==='Clear' || src==='colBtn') this['..'].pn13.querySelector('[data-color_picker=popup]').style.display = 'none';
          },
          'dashboard.rss.revise_general_list_str': function(name,cmd,attr){ // emulation of Clg's triage
            pClg.triage_exe_1_attr.call(
              {pref:{filter:{get attr_list_str(){return pref.catalog.style_general_list_str;}, // emulation of Clg
                             set attr_list_str(v){pref.catalog.style_general_list_str = v;;},
                             get attr_list_obj2(){return pref.catalog.style_general_list_obj2;}},
                     catalog:{}}}, {}, {'16':{}},
              name, cmd, attr, new RegExp('(^|,)'+name.replace(/\+/,'\\+')+'([\\^@;][^,\n]*)*(,|\n|$)','mg')); // see Clg.prototype.triage_exe_1
            pref_func.apply_prep_save(null,true);
            this['catalog.style_general_list_str']();
//            pref_func.apply_prep_1n('catalog.style_general_list_str', false, this['..'].pn13_onchange_wrapped, null, pref.test_mode['190']); // make obj, call event, but can't save
          },
        },
      },
//      bind_myself: function(obj, args){for (var i of args) obj[i] = obj[i].bind(obj);},
      format_html_str: function(html){
        html = html.replace(/(<br>|<\/div>)([0-9]),/g,function(match,p1,p2){ // function myself(match,p1){return (!p1)? '<br>' : myself(null,p1-1)+'&emsp;';}
          var str = p1;
          for (var i=0;i<p2;i++) str += '&emsp;';
          return str;
        });
        html = html.replace(/<SE(C*)"([^"]*)"([^>]*)>/g,function(m,p1,p2,p3){
//          var os = (m.indexOf('\\,')===-1)? p2.split(',') : p2.replace(/\\,/g,'&#44;').split(',').map(v=>v.replace(/&#44;/g,',')); // patch for not having negative lookbehind
          var os = p2.split(','); // /(?<!\\),/ for later chrome
          if (m.indexOf('\\,')!==-1) for (var i=1;i<os.length;i++) while (os[i][os[i].length-1]==='\\') os[i] = os[i].slice(0,-1)+','+os.splice(i+1,1)[0]; // patch for not having negative lookbehind
          return '<select name="'+os[0]+'"'+p3+'>'+os.slice(1).map(function(v){
            var a = v.split(':');
            if (p1 && !a[1]) a[1] = a[0][0].toUpperCase()+a[0].slice(1);
            return '<option' + (a[1]?' value="'+a[0]+'"':'') + (a[2]?' '+a[2]:'') + '>' + (a[1]||a[0]) + '</option>';}).join('')+'</select>';
//            var a = v.split(':');
//            return '<option' + (a.length>1? ' value="'+a[0]+'"'+(a.length>2? ' '+a[2]:''):'')+'>'+(a.length>1? a[1]:a[0])+'</option>';}).join('')+'</select>';
//            var idx = v.indexOf(':');
//            return '<option' + (idx>=0? ' value="'+v.slice(0,idx)+'">':'>')+v.slice(idx+1)+'</option>';}).join('')+'</select>';
          });
        html = html.replace(/<TA"([\w.]+)"([^>]*)>/g,'<textarea style="height:1em" rows="1" cols="40" name="$1"$2></textarea>');
        html = html.replace(/<IC(N)?"([\w.]+)"([^>]*)>(\w)?/g,function(m,p0,p1,p2,p3){return '<input type="checkbox" name="'+p1+'"'+p2+'>'+(p3&&!p0?' '+p3:(p3||''));}); // N for no space
        html = html.replace(/<ITB(N)?(L)?(\d+)"([\w.]+)"([^>]*)>(\w)?/g,function(m,p0,p1,p2,p3,p4,p5){return '<input type="text"'+(p1?'':' style="text-align: right;"')+' size="'+p2+'" name="'+p3+'"'+p4+'>'+(p5&&!p0?' '+p5:(p5||''));}); // with single space
//        html = html.replace(/<ICBX"/g,'<input type="checkbox" name="'); // used for filter.kwd
//        html = html.replace(/<ITBL(\d+)"/g,'<input type="text" size="$1" name="');
//        html = html.replace(/<ITB(\d+)"/g,'<input type="text" style="text-align: right;" size="$1" name="');
//        html = html.replace(/<IR("[\w\.]+):([^:]+):([^"]+)">/g,'<input type="radio" name=$1" value="$2">$3');
        html = html.replace(/<IR"([\w.]+),([^"]+)"([^>]*)>/g,'<input type="radio" name="$1" value="$2"$3>');
        html = html.replace(/<SPB"([\w.]+)(,|:)([^"]+)"([^>]*)>/g,'<span class="'+pref.cpfx+'button" data-name="$1"$4>$3</span>');
        html = html.replace(/<SSV("[\w.]+")([^>]*)>/g,'<span data-show_value=$1$2></span>');
//        html = html.replace(/<BTN"([\w.]+),([^"]+)"([^>]*)>/g,'<button type="button" name="$1"$3>$2</button>');
        html = html.replace(/<BTN"([\w.\s]+)([,:][^"]+)*"([^>]*)>/g,function(m,name,cap,others){return '<button type="button" name="'+name+'"'+others+'>'+(cap? cap.slice(1) : name.split('.').pop())+'</button>';});
        html = html.replace(/<IF"/g,'<input type="file" name="');
        return html.replace(/<input type="(checkbox|radio)[^>]+>[^<]+/g,'<label>$&</label>');
      },
  };
  pref_func.settings.onchange_funcs_base = pref_func.settings.onchange_funcs.__proto__.__proto__;
  pref_func.settings.oninput_funcs = pref_func.add_onchange_format(pref_func.settings.onchange_funcs.__proto__);
///  pref_func.settings.onchange_funcs.entry_func = (function(myself){ // working code.
///    return function(e){
///      pref_func.apply_prep(this,true);
///      var pn_name = this.getAttribute('name'); // for <span>
///      if (myself[pn_name]) myself[pn_name].call(this,e);
///      else if (pn_name.indexOf('.')!=-1) {
///        var name = '*'+pn_name.substr(pn_name.indexOf('.')); // *.XXX
///        if (myself[name]) myself[name].call(this,e);
///        else if (pn_name.lastIndexOf('.')!=-1) {
///          var name = pn_name.substr(0,pn_name.lastIndexOf('.')+1)+'*'; // XXX.YYY.*
///          if (myself[name]) myself[name].call(this,e);
///        }
///      }
///      var pn = e.target.parentNode;
///      while (pn && pn!==site.script_body) {
///        if (pn.getAttribute('name')==='SUB') myself['SUB'].call(pn,{currentTarget:pn});
///        pn = pn.parentNode;
///      }
///    }
///  })(pref_func.settings.onchange_funcs);

//  pref_func.settings.onchange_funcs['catalog.tag.ignore'] = pref_func.settings.onchange_funcs['tag.re_generate'];
//  pref_func.settings.onchange_funcs['catalog.tag.max']    = pref_func.settings.onchange_funcs['tag.re_generate'];
  pref_func.settings.onchange_funcs['tag.gen_str']        = pref_func.settings.onchange_funcs['tag.gen'];
//  pref_func.bind_myself(pref_func.settings.oninput_funcs.__proto__, ['SHOW','HIDE','SHOW2','HIDE2','SHOW3','HIDE3','TOGGLE2','SHOWALL','HIDEALL']);
  pref_func.settings.html_funcs = pref_func.settings.htmls.__proto__;

  var options = {
    func0_prep: function(){},
    func0_exe : function(){}
  };

  comf = { // common_func
    __proto__:comf,
    name2domainboardthread: function(name,fill){
      var s = name.indexOf('/');
      var e = name.lastIndexOf('/');
      var d_or_t = s===-1 && name.search(/\D/)!==-1;
      var domain = s>0? name.slice(0,s) : d_or_t? name : fill? site.nickname : '';
      var board  = s+1<e? name.slice(s,e+1) : fill? site.board : '';
      var thread = !d_or_t? name.slice(e+1) : '';
      return [domain,board,thread];
    },
//    name2domainboardthread: function(name,fill){ // working code, but substr is depricated.
////      var thread = name.replace(/[^\/]*\//g,'');
////      var domain = name.replace(/\/.*/,'');
////      var board  = name.replace(new RegExp('^'+domain),'').replace(new RegExp(thread+'$'),'');
////      name = new String(name); // not tested.
//      var thread = name.substr(name.lastIndexOf('/')+1);
//      var domain = name.substr(0,name.indexOf('/'));
//      var board  = name.substr(domain.length,name.length-thread.length-domain.length);
//      if (name===thread && thread.search(/[^0-9]/)!=-1) {
//        domain = thread;
//        thread = '';
//      }
////      if (thread===domain) // BUG PATCH, SHOULD BE REMOVED.
////        if (thread.search(/[^0-9]/)!=-1) thread ='';
////        else domain = '';
//      if (fill) {
//        if (domain==='') domain = site.nickname;
//        if (board==='') board = site.board;
//      }
//      return [domain,board,thread];
//    },
    name2dbt: (function(){
      var type = { c:'catalog_html',
                   j:'catalog_json',
                   p:'page_html',
                   q:'page_json',
                   t:'thread_json',
//                   0:'thread_html' // default
                 };
      return function(name){
        var dbt = this.name2domainboardthread(name,true);
        var t = dbt[2][0];
        dbt[3] = (t in type)? (dbt[2] = dbt[2].slice(1), type[t]) : 'thread_html';
//        dbt[3] = type[dbt[2][0]]; // working code, but substr is depricated.
//        if (dbt[3]) dbt[2] = dbt[2].substr(1);
//        else dbt[3] = 'thread_html';
////        if (dbt[2][0].search(/[A-z]/)!=-1) {
////          dbt[3] = type[dbt[2][0]];
////          dbt[2] = dbt[2].substr(1);
////        } else dbt[3] = 'thread_html';
        return dbt;
      };
    })(),
    fullname2dbt: function(name){
      var s = name.indexOf('/');
      var e = name.lastIndexOf('/')+1;
      return [name.slice(0,s), name.slice(s,e), name.slice(e)]; // this may not be optimized...
    },
////    fullname2dbt: function(name){ // slow
////      var dbt = name.split('/');
////      dbt[1] = '/'+dbt[1]+'/';
////      return dbt;
////    },
//    fullname2dbt: function(name){ // working code, but substr is deprecated.
//      var thread = name.substr(name.lastIndexOf('/')+1);
//      var domain = name.substr(0,name.indexOf('/'));
//      var board  = name.substr(domain.length,name.length-thread.length-domain.length);
//      return [domain,board,thread];
//    },
    fullname2domain: function(name){
      return name.slice(0,name.indexOf('/'));
    },
    dom_addEventListener: function(ary, dom, kwd, func){
      dom.addEventListener(kwd, func, false);
      ary[ary.length] = [dom, kwd, func];
    },
    dom_removeEventListener: function(ary, dom, kwd, func){
      for (var i=ary.length-1;i>=0;i--) {
        if ((!dom || dom===ary[i][0]) && (!kwd || kwd===ary[i][1]) && (!func || func===ary[i][2])) {
          var tgt = ary.splice(i,1)[0];
          tgt[0].removeEventListener(tgt[1], tgt[2], false);
        }
      }
    },
    dom_addAttribute: function(dom,attr,val){
      var val_old = dom.getAttribute(attr);
      if (val_old) val = val_old + ' ' + val;
      dom.setAttribute(attr,val);
    },
//    modify_bookmark: function(name,add){ // working code
//      var key = new RegExp('(^|,)'+name.replace(/\+/,'\\+')+'([\^@*!][^,\n]*)*(,|\n|$)','mg');
//      var str = pref.filter.bookmark_list_str;
//      str = str.replace(key,',') + ((add)? ','+name+'\n' : '');
//      pref.filter.bookmark_list_str = str.replace(/,,+/g,',').replace(/^,/g,'').replace(/\n,/g,'\n').replace(/\n\n+/g,'\n').replace(/^\n/,'')
//      if (catalog_obj.catalog_func()) {
//        var tgt = catalog_obj.catalog_func().pn12_0_4.getElementsByTagName('textarea')['filter.bookmark_list_str'];
//        pref_func.apply_prep(tgt,false);
//      } else if (pref.catalog.auto_save_filter) { // see 'save' in catalog.
//        if (localStorage) {
//          key = pref.script_prefix + '.filter.' + pref.catalog_board_list_obj[pref.catalog_board_list_sel][0].key;
//          var obj = JSON.parse(localStorage.getItem(key));
//          obj.bookmark_list_str = pref.filter.bookmark_list_str;
//          localStorage.setItem(key,JSON.stringify(obj));
//        }
//      }
//    },
    perf_out: (function(){
      var count = 0;
      var history = [];
      var records = [];
      var show = Watchdog.prototype.restart.bind(new Watchdog(function(){
        history.push(out_sum('now',records));
        out_sum('Hist: '+count, history.slice(-10), true);
        records = [];
        if (history.length>1000) history.shift();
        count++;
      },20000));
//      function out_1(arr){
//        var str = arr[0]+': '+(arr[arr.length-1]-arr[1])+': ';
//        var last_ts = arr[1];
//        for (var i=2;i<arr.length;i++) {
//          if (typeof(arr[i])==='number') {
//            str += (arr[i]-last_ts) + ', ';
//            last_ts = arr[i];
//          } else str += arr[i] + ', ';
//        }
//        console.log(str);
//      }
      function out_sum(cap,arr,avg){
        var arr_t = arr.map(v=>v[v.length-1]-v[1]);
        var sum = arr_t.reduce((a,c)=>a+c);
        if (avg) console.log(cap+': '+(avg?'Avg':'Sum')+arr2str(arr_t,arr[0][0]+': '+Math.round(avg? sum/arr.length : sum)+': ')+(avg? ', '+arr2str(arr[arr.length-1][2]):''));
        return [arr[0][0], 0, arr_t, sum];
      }
      function arr2str(arr,cap){
        return arr.length+': '+(cap||'')+'['+arr.map(Math.round)+']';
      }
//      function round1(float){
//        var str = (float+0.05).toString();
//        var idx = str.indexOf('.');
//        return idx===-1? str : str.slice(0,idx+2);
//      }
      return function(arr){
        arr.push(performance.now());
        records.push(arr);
        show();
      };
    })(),
//    obj_scopy_from_key: function(obj){
//      for (var i in obj)
//        if (typeof(i)==='string' && Object.prototype.hasOwnProperty.call(obj,obj[i])) obj[i] = obj[obj[i]];
//      return obj;
//    },
//    set_value_to_root: function(leaf,key,val){ // working code.
//      if (Object.prototype.hasOwnProperty.call(leaf,key)) leaf[key] = val;
//      else this.set_value_to_root(Object.getPrototypeOf(leaf),key,val);
//    },
//    init_set_style: function(dom,src){
//      dom.style = {};
//      if (src) for (var i in src) dom.style[i] = src[i];
//      if (src===null) {
//        src = dom.getAttribute('style');
//        if (src) {
//          src = pref_func.style2obj(src);
//          for (var i in src) dom.style[i] = null;
//        }
//      }
//    },
    shallow_copy_1: function(src){ // to be obsoleted.
      var dst = Object.create(null);  
      for (var i in src) if (src.hasOwnProperty(i)) dst[i] = src[i];
      return dst;
    },
    deep_copy: function deep_copy(src, dst){
      if (!dst) dst = {};
      for (var i in src)
        if (src.hasOwnProperty(i))
          if (Array.isArray(src[i])) dst[i] = src[i].slice();
          else if (typeof(src[i])==='object' && src[i]!==null) dst[i] = deep_copy(src[i]);
//          else if (typeof(src[i])==='object') dst[i] = deep_copy({},src[i]); // BUG, null becomes {}, 
          else dst[i] = src[i];
      return dst;
    },
    kwd_prep_regexp: function(kwd, exact) {
      if (!kwd.str) return null;
      var kwds = (kwd.sentence)? [kwd.str] : kwd.str.split(' ');
      var rexps = [];
      for (var i=0;i<kwds.length;i++) {
        if (kwds[i]) {
          if (!kwd.re) {
            var rstr = kwds[i].replace(/[\.\(\)\[\]\+\^\$\{\}\\]/g,'\\$&').replace(/\*/g,'.*').replace(/\?/g,'.'); // should be consolidated with str2rstr
            rstr = (exact)? '^' + rstr + '$' : (kwd.ew)? '(^|\\s|\\n)' + rstr + '(\\s|\\n|$)' : rstr.replace(/([^\\])\.\**$/,'$1');
          } else rstr = kwds[i].replace(/\\$/,''); // for incremental search, trailing '/' causes error
          while (rstr.length>0) {
            try {
              rexps[rexps.length] = new RegExp(rstr,(kwd.ci)? 'i' : '');
              break;
            } catch(e){ // user input causes errors usually, ex. trailing \ or [ in incremental search.
              rstr = rstr.slice(0,-1); // for incremental regexp search
            }
          }
//          try {
//            rexps[rexps.length] = new RegExp(rstr,(kwd.ci)? 'i' : '');
//          } catch(e) {} // user input causes errors usually, ex. trailing \ or [ in incremental search.
        }
      }
      return (rexps.length>0)? rexps : null;
    },
    escape:(function(){
      var entities = {
        '<': '&lt;',
        '>': '&gt;',
        '&': '&amp;',
        '"': '&quot;',
        '\'':'&#039;', // '&apos;'
      };
      function escape(str){
        return entities[str];
      }
      return function(txt){
        return txt.replace(/[<>"'&]/g,escape);
      }
    })(),
    escape_text: function(txt){ // http://d.hatena.ne.jp/ockeghem/20070510/1178813849
      return txt.replace(/&/g,'&amp;').replace(/</g,'&lt;');
    },
    arraybuffer2blob: (function(){
      var mime_types = {jpg:'image/jpeg', jpeg:'image/jpeg', gif:'image/gif', png:'image/png', pdf:'application/pdf',
                        svg:'image/svg+xml', svgz:'image/svg+xml', 
                        htm:'text/html', html:'text/html', txt:'text/plain',
                        webm:'video/webm', mpg:'video/mpeg', mpeg:'video/mpeg', mp4:'video/mp4', ogv:'video/ogg',
                        mp3:'audio/mp3', 
                        gz:'application/x-gzip', tar:'application/x-tar', tgz:'application/x-tar', zip:'application/x-compress',
                        '7z':'application/x-7z-compressed', xz:'application/x-xz',
                       };
      return function(filename, data){
        var ext = filename.substr(filename.lastIndexOf('.')+1);
        var type = mime_types[ext] || 'text/plain';
        return new Blob([data], {type:type});
      }
    })(),
  };

  var common_obj = {
    thread_reader: null,
    events_beforeunload: [],
  };
  comf.dom_addEventListener(common_obj.events_beforeunload, window, 'beforeunload', function(){comf.dom_removeEventListener(common_obj.events_beforeunload)});
  comf.dom_addEventListener(common_obj.events_beforeunload, window, 'focus', function(){DelayBuffer.prototype.hasFocus = true;});
  comf.dom_addEventListener(common_obj.events_beforeunload, window, 'blur', function(){DelayBuffer.prototype.hasFocus = false;});

  var site = { // krautchan/int/
    max_page   : 20,
    autosage   : 300,
    page: 0,
//    board_name : '/int/',
    url_prefix : 'https://krautchan.net/int/',
    url_prefix2 : {},
    nickname   : 'KC',
    nicknames  : ['CatChan_tgt'],
    thread_keyword: 'thread',
    postform: null,
    postform_rules: null,
    get_ops    : null,
    get_posts  : null,
////////    make_url   : null,
////////    make_url2  : function(bn,idx){
////////      var nickname  = (bn.indexOf('/')==0)? site.nickname : bn.substr(0,bn.indexOf('/'));
////////      var boardname = '/' + bn.replace(/[^\/]*\//,'').replace(/\/.*/,'/');
////////      return [nickname, boardname, idx, site2[nickname].make_url(boardname,idx)];
////////    },
//    protocol : (document.location.href.search(/https/)!=-1)? 'https:' : 'http:',
    catalog_threads_in_page : null,
//    catalog_posts_in_thread : null,
    remove_posts : function(src){return src;},
    remove_files_info : function(src){return src;},
    remove_checkboxes : function(src){return src;},
////////    get_thread_link : function(src){return '_blank';},
    get_time_of_last_post : function(doc){return null;},
    patch: null,
    get key(){return site.nickname + site.board + ((site.whereami==='thread')? site.no : '');}, // for dynamic change of site.whereami in meguca.
    header_height: function(){return this.components.header_height;},
    components: Object.create(null,{
      query:{value:function(name){
        var query = site2[site.nickname].components[name];
        return (typeof(query)==='string')? document.querySelector(query) :
                                           document.querySelectorAll(query[0])[query[1]];}, configurable:true, writable:true},
      query_set:{value:function(name){
        var comp = this.query(name);
        if (comp) this[name] = comp;
        else delete this[name];}, configurable:true, writable:true},
      header_height:{get:function(){
        var header = this.query('header')
        return header? header.offsetHeight : 0;}, configurable:true},
    }),
    config: function(keyword,nickname, reentry){ // url='*site/board/etc', keyword='site'
      var href = window.location.href;
      site.protocol = href.slice(0,href.indexOf(':')+1);
      var url_site = href.slice(0,href.indexOf(keyword)+keyword.length);
      site.board = '/' + decodeURI(href.slice(url_site.length+1)).replace(/[\/\?].*/,'') + '/'; // add '/' to the last for case of ending without slash.
//      var url_board = decodeURI(href.substr(url_site.length+1)) + '/'; // add '/' to the last for case of ending without slash.
//      site.board = '/' + url_board.substr(0,url_board.indexOf('/')+1);
      site.url_prefix = url_site + site.board;
      site.nickname = nickname;
//      site.isthread = window.location.href.search(site2[nickname].thread_keyword)!=-1;
//      for (var i in site2[nickname]) site[i] = site2[nickname][i];
//      site.features = site2[site.nickname].features;
      if (site.no===undefined) site.no = (site.whereami==='thread')? site2[site.nickname].get_ops(document)[0] : '';
//      site.myself = site2[site.nickname].get_ops(document)[0];
//      site.no = (site.whereami==='thread')? site.myself : '';
      if (reentry) return;
      if (!site0.isStep && site2[nickname].features) pref_func.pref_overwrite(site.features,site2[nickname].features);
      for (var i in site2[site.nickname].components) if (i!=='header') site.components.query_set(i);
      if (!site.settings && site.components.boardlist) {
        site.settings = site.components.boardlist.appendChild(document.createElement('span'));
        site.settings.setAttribute('style','float:right;');
        if (site.components.boardlist2) site.settings3 = site.components.boardlist2.appendChild(site.settings.cloneNode());
      }
//      if (site2[site.nickname].pref_default) pref_func.pref_overwrite(pref,site2[site.nickname].pref_default);
      site.patch = site2[site.nickname].patch;
    },
    features : {page: false, graph: true, setting: true, setting2: true, postform: true, catalog: true, listener : false, uip_tracker: false, thread_reader: true, debug: false},
    owners_recommendation: '',
    catalog : false,
    whereami: null,
    embed_to: {},
    root_body : document.getElementsByTagName('body')[0],
//    root_body : (document.getElementsByTagName('body')[0])? document.getElementsByTagName('body')[0] :
//                                                            document.getElementsByTagName('frame')[0].contentDocument.getElementsByTagName('body')[0] // KC root
    embed_frame:'CatChan_embed_frame',
    embed_frame_win: null,
    boardlist: null,
    settings: null,
    mobile: document.documentElement.clientWidth<=480,
    __proto__: {
      get myself(){
        Object.defineProperty(this,'myself',{value:site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'thread_html',{thread:site.no})[0],
                                             writable:true, enumerable:true, configurable:true});
        return this.myself;
      }
    }
  };
  site.protocol = (document.location.href.search(/https/)!=-1)? 'https:' : 'http:'; // patch for Tampermonkey.
  site.root_body2 = site.root_body;

  var CCAPI = window.CCAPI || {}; // for interaction with native, this script must be after the native script.
  var site2 = {};
  site2['DEFAULT'] = { // skeleton for default
    nickname : 'DEFAULT',
    home : '', // home is used url for iframe, so it MUST BE THE SAME ORIGIN, OR LEAVE IT BLANK.
    check_func: function(){ // for background iframe
      if (window.location.href.indexOf(this.domain_url)!=-1) {
        site.whereami = 'other';
        site.config(this.domain_url,this.nickname);
        return true;
      } else return false;
    },
//    check_func: function(){return false;}, // return true if the script is running in this site.
    protocol: site.protocol,
    components: {},
    spoiler_text: {
      spoiler: /span.spoiler$/,
      open: /span.spoiler:hover/,
    },
    make_tack: function(){
      var tack = document.createElement('i');
      tack.setAttribute('class','fa fa-thumb-tack');
      return tack;
    },
    make_showAlways: function(){
      var icon = document.createElement('i');
      icon.setAttribute('class','fa');
      icon.textContent = '\u2605';
      return icon;
    },
    get_icon_showAlways: function(pn){
      return pn.getElementsByClassName('fa')[0] || null;
    },
    set_icon : function(pn,view,kind,tgt_th16, set){
      if (pn) {
        var icon = tgt_th16['icon_'+kind] || kind==='sticky' && this.get_icon(pn,view,kind) || kind=='showAlways' && this.get_icon_showAlways(pn); // , view, kind); // showAlways for headline_merge
        if (set) {
          if (icon && kind=='sticky' && tgt_th16.systemSticky) icon.style.opacity = null; // for initial
          else if (!icon) icon = this.add_icon(pn,view,kind);
          if (pref.test_mode['194'] && kind=='sticky') {
            icon.classList.add(pref.cpfx+'embeddedTriage');
//            icon.dataset.cmd='UNSTICKY'; // >chrome55 and also READONLY!!!
          }
          return icon;
//        if (set && icon && kind=='sticky') { // for initial
//          if (tgt_th16.systemSticky) icon.style.opacity = null;
//          return icon;
//        } if (set) {if (!icon) return this.add_icon(pn,view,kind);}
////        if (!icon) return this.add_icon(pn,view,kind,tgt_th16); // CAUTION: this is toggle, not set.
        } else {
          if (kind=='sticky' && tgt_th16.systemSticky) {
            if (!icon) icon = this.add_icon(pn,view,kind); // patch for unmereged threads with systemSticky
            icon.style.opacity = '0.4';
          } else if (icon.parentNode) icon.parentNode.removeChild(icon); // for merge
//          else icon.parentNode.removeChild(icon);
        }
      }
      return null;
    },
    postform:{},
//    postform:(function(){
//      function onoff(ref){
////        if (this.tack && ref) ref.parentNode.insertBefore(this.tack, ref);
//        if (this.tack) this.tack.style.display = (this.tack.style.display==='none')? '' : 'none';
//      }
//      return {
//        tack: null,
//        init:function(){
//          if (site.postform) {
//            this.tack = site2[site.nickname].make_tack();
//            this.tack.setAttribute('style','float:right;font-size:2em');
//            site.postform.parentNode.insertBefore(this.tack,site.postform);
//          }
//          return this.tack;
//        },
//        on: onoff,
//        off:onoff,
//      };
//    })(),
    postform_prep: function(){ // the same as vichan.
      site.components.postform_submit2 = null;
      site.components.postform_submit2_observer = new MutationObserver(this.postform_submit2_find);
      site.components.postform_submit2_observer.observe(document.getElementsByTagName('body')[0], {childList: true});
    },
    postform_submit2_find: function(){
      var state_old = site.components.postform_submit2;
      site.components.query_set('postform_submit2');
      site.components.query_set('postform_comment2');
      if (common_obj.thread_reader) common_obj.thread_reader.setup_submit2();
      if (pref.features.recovery) recovery.setup2();
    },
    general_event_handler:(function(){
//      function func_false(){return false;}
      var obj = {
        common: {
          subscribe: function(catalog_root){
            if (this.mouseover) catalog_root.addEventListener('mouseover',this.mouseover,false);
          },
          unsubscribe: function(catalog_root){
            if (this.mouseover) catalog_root.removeEventListener('mouseover',this.mouseover,false);
          },
          get_mark: function(pn, clientY){
            return this.get_mark_from_height(clientY, document.getElementByTagName('div'));
          },
          get_mark_from_height: function(now_height, pns){
            var len = pns.length;
            var step = len >> 1;
            var i = 0;
            while (step>0) {
              if (pns[i+step].offsetTop<now_height) i += step;
              step >>= 1;
            }
            while (i+1<len && pns[i+1].offsetTop<now_height) i++;
            return pns[i];
          },
          isThumbnail: function(e){
            var et = e.target;
            return et.tagName==='IMG' && et.parentNode && et.parentNode.tagName==='A'; // et.parentNode for shrink_thumbnails, expanded image was removed already.
          },
          recSearch_thread: function(tgt,ecT, className){
            if (!className) className = 'thread';
            while (tgt && tgt!=ecT) {
              if (tgt.classList && tgt.classList.contains(className)) return tgt;
              else tgt = tgt.parentNode;
            }
            return null;
          },

          getID: function(pn){},
          getCountry: function(pn){},
          getTrip: function(pn){},
          getName: function(pn){return pn.classList.contains('name') && pn.textContent || undefined;},
          an2srcNo: function(pn, th){
            while (pn) {
              if (pn.classList && pn.classList.contains('post')) return site2[th.domain_html].parse_funcs['post_html'].no({pn:pn});
              if (gGEH.pns_all_keys.get(pn)) return site2[th.domain_html].parse_funcs['catalog_html'].no({pn:pn});
              pn = pn.parentNode;
            }
          },
          menu2keyTime: function(pn){
            var post = this.recSearch_thread(pn, null, 'post');
            var key = gGEH.get_key_recursive(post, document);
            var pfunc = site2[site.nickname].parse_funcs['post_html'];
            var no = pfunc.no({pn:post});
            var time = pfunc.time({pn:post})*pfunc.time_unit;
            return [key, no, time];
          },
        },
      };
      obj.thread = Object.create(obj.common);
      obj.page = Object.create(obj.common);
      obj.catalog = Object.create(obj.common);
      return obj;
    })(),
//    catalog_background : '#b5ccf9',
//    catalog_bordercolor : '#000000',
    horizontal_separator_in_index: function(){
//      var pn = document.createElement('div');
//      pn.innerHTML = '<br><br class="clear"><hr>';
      var pn = document.createElement('hr');
      pn.setAttribute('class', pref.cpfx+'hs');
      return pn;
    },
//    get_next_image: function(img,name){return null;}, // return next image for prefetch3
    get_next_image: function(img,top, imgs){
      if (!imgs) imgs = cataLog.parent.getElementsByTagName('img');
      var idx = Array.prototype.indexOf.call(imgs,img);
      if (top) while (imgs[idx+1] && imgs[idx+1].offsetTop<top) idx++;
      return (idx>=0)? imgs[idx+1] : null;
    },
//    get_time_of_posts : function(thread){return [0,0];}, // returns parsed UTC time of last and op posts from element of the thread.
//    get_time_of_post_in_utc : function(post){return 0;}, // returns parsed UTC time of posts.
////////    get_thread_link : function(thread){return null;}, // returns href from element of the thread. THIS SHOULD BE MERGED WITH modify_thread_link.
    catalog_threads_in_page : function(doc){return [];}, // returns array of thread elements in the page from the document.
    remove_posts : function(thread,end){}, // removes posts from the thread with a certain remains.
    remove_files_info : function(thread){return thread;}, // removes file information from the thread, and returns itself.
//    remove_checkboxes : function(thread){return thread;}, // removes checkboxes from the thread, and returns itself.
    postform_rules : null,
    thread_keyword : 'thread', // thread keyword in URL.
    max_page : function(board){return 10;}, // returns max_page
//    max_page : 10, // maximum page number.
//    make_url : function(board,no,key){return ['_blank','raw'];}, // returns URL and type from board name and page number.
//    make_url3: function(board,th){return '_blank';}, // returns URL from board name and thread number.
//    make_url4: function(dbt){return '_blank';}, // returns URL from dbt.
////////////    enumerate_boards_to_scan:function(){ // 4chan // working code.
////////////      var obj = [];
////////////      var end = (site3[site.nickname].boards.length > pref.scan.max)? pref.scan.max : site3[site.nickname].boards.length;
////////////      for (var i=0;i<end;i++) obj[obj.length] = '/'+site3[site.nickname].boards[i].board+'/';
////////////      return obj;
////////////    },
////////    enumerate_boards_to_scan:function(){ // working code.(2016.01.09)
////////      var obj = [];
////////      var end = (site3[site.nickname].boards.length > pref.scan.max)? pref.scan.max : site3[site.nickname].boards.length;
////////      for (var i=0;i<end;i++) obj[obj.length] = site3[site.nickname].boards[i];
////////      return obj;
////////    },
    generate_boards_json: function(src, page_def){
      var proto = {pages:page_def};
      var arr = [];
      for (var i=0;i<src.length;i++)
        arr[arr.length] = (Array.isArray(src[i]))? {board:src[i][0], pages:src[i][1], __proto__:proto}
                                                 : {board:src[i],                     __proto__:proto};
      return {boards:arr};
    },
    url_boards_json: function(){return '';}, // returns url of boards.json.
    get_boards_json: function(key,callback,callback_always,  dummy_indicator){
      var key = 'boards_json_'+this.nickname;
      if (this.boards_json) this.get_boards_json_callback(this.nickname,{status:200, response:this.boards_json},[callback,callback_always]);
      else httpd.req({initiator:key,
//                      INDICATOR: dummy_indicator? {shift:function(){}, report:function(){}} : null, IDX:0, RUNNING:0, SUC:0, max:1, // patch for 8chan, see tag.generate_caller
                      tgts:[{domain:this.nickname, url:this.url_boards_json()[0], responseType:'json', data_type:'json'}],
                      callback_1:function(req,val){this.get_boards_json_callback(key,val,[callback,callback_always,true])}.bind(this)},6);
    },
    get_boards_json_callback: function(key,value,args){
      if (value.status==200) {
        this.postprocess_board(value.response);
        if (!site3[this.nickname].boards) {liveTag.update_pn_buf.cancel();liveTag.update_pn_buf.do_tgt();}
        site3[this.nickname].boards = (site.nickname==='8chan' && site.whereami==='boards')? value.response : true;
      }
      if (args[0] && (args[1] || value.status===200))
        if (args[2]) args[0]();
        else setTimeout(args[0],0);
    },
////    get_boards_json_indicator: null, // working code.
////    get_boards_json: function(key,callback,callback_always,health_indicator){
////      var key = 'boards_json_'+this.nickname;
////      if (this.get_boards_json_indicator) {
////        this.get_boards_json_indicator.report({end:Date.now(), prog_str:'Aborted.', err:'Aborted by next request.'});
////        this.get_boards_json_indicator = null;
////      }
////      if (this.boards_json) this.get_boards_json_callback(this.nickname,{status:200, response:this.boards_json},[callback,callback_always]);
////      else {
////        var indicator;
////        if (health_indicator) { // patch
////          indicator = health_indicator.shift('limegreen','b',key);
////          indicator.report({start:Date.now(), prog_str:'Loading boards\' information'});
////          this.get_boards_json_indicator = indicator;
////        }
////        http_req.get(key,this.nickname+',dummy,dummy',this.url_boards_json(),this.get_boards_json_callback.bind(this),0,true,[callback,callback_always,indicator,key]);
////      }
////    },
////    get_boards_json_callback: function(key,value,args){
////      if (value.status==200) {
////        this.postprocess_board(value.response);
////        if (!site3[this.nickname].boards) liveTag.update_pn_buf.do_tgt();
////        site3[this.nickname].boards = true;
////      }
////      if (args[2]) {
////        args[2].report({end:Date.now(), prog_str: (value.status==200)? 'boards.json is loaded.' : '',
////                        err:  (value.status==200)? '' : 'Error at loading board\'s infomation.'});
////        this.get_boards_json_indicator = null;
////      }
////      if (args[3]) http_req.close(args[3]);
////      if (args[0] && (args[1] || value.status===200))
////        if (args[2]) args[0]();
////        else setTimeout(args[0],0);
////    },
    postprocess_board: function(val){ // for 4chan and all 
      for (var i=0;i<val.boards.length;i++) {
        var bd = liveTag.mems.init({domain:this.nickname, board:'/'+val.boards[i].board+'/'});
        bd.o = i;
        if (val.boards[i].pages && !bd.pgs) bd.pgs = val.boards[i].pages;
        if (val.boards[i].tags && val.boards[i].tags.length!=0) liveTag.postprocess_board_add_btag(val.boards[i].tags,bd);
      }
    },
////////    postprocess_board: function(val){ // 4chan // working code.
////////      site3[this.nickname].boards = [];
////////      for (var i=0;i<val.boards.length;i++) {
////////        var bd = liveTag.mems.init({domain:this.nickname, board:'/'+val.boards[i].board+'/'});
////////        site3[this.nickname].boards.push(bd); // BUG. increase length forever.
//////////        Object.defineProperty(bd,'bump_limit',{value:val.boards.bump_limit});
////////      }
////////    },

////    show_boardlist_replaced: false, // working code.
////    show_boardlist_physical_board: function(tags,func_onclick){
////      var as = site.components.boardlist.getElementsByTagName('a');
////      if (pref.virtualBoard.p_board==='replace') {
////        for (var i=pn.childNodes.length-2;i>=0;i-=2) { // skip every delimiter // working code.
//////          var key = pn.childNodes[i].textContent.substr(1); // working code.
//////          for (var j=0;j<as.length;j++) {
//////            if (as[j].textContent===key) {
//////              if (!as[j].style) as[j].style = {};
//////              if (as[j].style.display!=='none') {
//////                as[j].style.display='none';
//////                pn.childNodes[i].tagName = 'a'; // can't change by specification.
//////                pn.childNodes[i].setAttribute('href',as[j].getAttribute('href'));
//////                as[j].parentNode.insertBefore(pn.childNodes[i],as[j]);
//////              } else pn.removeChild(pn.childNodes[i]); // remove '#XXX'(tag)
//////              pn.removeChild(pn.childNodes[i]); // remove '/'(delimiter)
//////            }
//////          }
////          var key = pn.childNodes[i].textContent;
////          for (var j=0;j<as.length;j++) {
////            if (as[j].textContent===key || as[j].textContent===key.substr(1)) {
////              if (!as[j].classList.contains(pref.cpfx+'tag')) {
////                var tmp_node = liveTag.update_tag_string([pn.childNodes[i].textContent], '', pn.childNodes[i].onclick, 'a').childNodes[0];
////                tmp_node.setAttribute('href',as[j].getAttribute('href'));
////                as[j].parentNode.insertBefore(tmp_node,as[j]);
////                as[j].parentNode.removeChild(tmp_node.nextSibling);
////              } else pn.removeChild(pn.childNodes[i]); // remove '#XXX'(tag)
////              pn.removeChild(pn.childNodes[i]); // remove '/'(delimiter)
////            }
////          }
////        }
////        this.boardlist_replaced = true;
////      } else if (this.boardlist_replaced) {
////        for (var i=0;i<as.length;i++) {
////          if (as[i].style && as[i].style.display==='none') {
////            as[i].style.display='';
////            as[i].parentNode.removeChild(as[i].previousSibling);
////          }
////        }
////        this.boardlist_replaced = false;
////      }
////    },
    boardlist_boards: Object.create(null,{sel:{value:null, writable:true}}),
    show_boardlist_physical_board: function(tags,func_onclick){ // tuned for 8chan
      var pfv = pref.virtualBoard.bl;
      var tgts = this.boardlist_boards;
      if ((tgts.sel===true  && pfv.p_board==='replace') ||
          (tgts.sel===false && pfv.p_board==='both')) {
        var src = (tgts.sel)? 'v' : 'p';
        var dst = (tgts.sel)? 'p' : 'v';
        for (var i in tgts) {
          var tgt = tgts[i];
          if (tgt[src] && tgt[dst]) {
            tgt[dst].parentNode.insertBefore(tgt[src],tgt[dst]);
            tgt[dst].parentNode.removeChild(tgt[dst]);
          }
        }
        tgts.sel = !tgts.sel;
      }
      if (this.boardlist_boards.sel===null && pfv.p_board==='replace') {
        var as = site.components.boardlist.getElementsByTagName('a');
        for (var i=0;i<as.length;i++) {
          var key = as[i].textContent;
          if (this.show_boardlist_physical_extract_func) key = this.show_boardlist_physical_extract_func(key);
          if (key[0]!=='#') this.boardlist_boards['#'+ ((pref.liveTag.ci)? key.toLowerCase() : key)] = {p:as[i]};
        }
        this.boardlist_boards.sel = false;
      }
      if (pfv.p_board==='replace') {
        var boardlist_hit = {};
        for (var i=tags.length-1;i>=0;i--) {
          var key = (pref.liveTag.ci)? tags[i].toLowerCase() : tags[i];
          var tgt = this.boardlist_boards[key];
          if (tgt) {
            if (!tgt.v) {
              var p = tgt.p;
              var v = liveTag.create_tag_node_1(key, func_onclick, 'a');
              v.setAttribute('href',p.getAttribute('href'));
              p.parentNode.insertBefore(v,p);
              p.parentNode.removeChild(p);
              tgt.v = v;
            }
            tags.splice(i,1);
            boardlist_hit[key] = null;
          }
        }
        if (pfv.v_remove) {
          for (var i in this.boardlist_boards)
            if (this.boardlist_boards[i].v && boardlist_hit[i]===undefined) {
              var p = this.boardlist_boards[i].p;
              var v = this.boardlist_boards[i].v;
              v.parentNode.insertBefore(p,v);
              v.parentNode.removeChild(v);
              delete this.boardlist_boards[i].v;
            }
        }
      }
      return tags;
    },
    boardlist_tags: null,
    boardlist_tags_pn: null,
    show_boardlist: function(tags,func_onclick){
//      for (var i=0;i<site.boardlist.length;i++) {
//        if (site.boardlist[i].lastChild.className===pref.cpfx+'tag') site.boardlist[i].removeChild(site.boardlist[i].lastChild);
//        site.boardlist[i].appendChild(pn); // can't clone events.
//      }
      var sbl = site.components.boardlist;
      var tags_bl = this.show_boardlist_physical_board(tags.slice(),func_onclick);
      if (pref.virtualBoard.bl.show && tags_bl.length!==0) {
        var pn = (pref.test_mode['118'])? liveTag.create_tag_nodes(tags_bl, ' / ', func_onclick)
                                        : liveTag.update_tag_nodes(tags_bl, ' / ', func_onclick, this.boardlist_tags_pn, this.boardlist_tags);
        if (!this.boardlist_tags_pn) {
          var ppn = document.createElement('span');
          ppn.appendChild(document.createTextNode('[ '));
          ppn.appendChild(pn);
          ppn.appendChild(document.createTextNode(' ]'));
          sbl.appendChild(ppn);
        } else if (pref.test_mode['118']) this.boardlist_tags_pn.parentNode.replaceChild(pn, this.boardlist_tags_pn);
        this.boardlist_tags_pn = pn;
        this.boardlist_tags = tags_bl;
      } else if (this.boardlist_tags_pn) {
        sbl.removeChild(this.boardlist_tags_pn.parentNode);
        this.boardlist_tags_pn = null;
        this.boardlist_tags = null;
      }

//      this.boardlist_tags = this.show_boardlist_physical_board(tags.slice(),func_onclick);
//      if (this.boardlist_tags_pn) {
//        sbl.removeChild(this.boardlist_tags_pn);
//        this.boardlist_tags_pn = null;
//      }
////      if (sbl.lastChild.getAttribute && sbl.lastChild.getAttribute('name')===pref.cpfx+'tag_parent') sbl.removeChild(sbl.lastChild);
//      if (pref.virtualBoard.show && this.boardlist_tags.length!==0) {
//        var pn = liveTag.create_tag_nodes(this.boardlist_tags, ' / ', func_onclick);
//        pn.insertBefore(document.createTextNode('[ '),pn.firstChild); // referred from query_tag_node
//        pn.appendChild(document.createTextNode(' ]'));
//        this.boardlist_tags_pn = sbl.appendChild(pn);
//      }
    },
    query_tag_node: function(tag){
      var bd_bl = this.boardlist_boards[tag];
      if (bd_bl) return bd_bl.v;
      else if (this.boardlist_tags_pn) {
        var idx = this.boardlist_tags.indexOf(tag);
        if (idx>=0) return this.boardlist_tags_pn.childNodes[idx*2]; // [idx*2+1];
      }
    },
    get_ops : function(doc){return [];}, // returns array of op numbers from the document.
    get_posts : function(doc) {return [];}, // returns array of posts numbers from the document.
    absolute_link: function(pn){
      this.absolute_link_exe(pn, this.protocol + '//' + this.domain_url);
    },
    absolute_link_exe: function(pn, prefix){
      var tgts = ['src','href'];
      var all = pn.getElementsByTagName('*'); //'*[src],*[href]'
      for (var i=0;i<all.length;i++) {
        for (var j=0;j<tgts.length;j++) {
          var tgt = all[i].getAttribute(tgts[j]);
          if (tgt && tgt.indexOf('http')!=0 && tgt.indexOf('mailto:')!=0 && tgt.indexOf('blob')!=0 && tgt.indexOf('data')!=0 && tgt.substr(0,2)!='//')
            all[i].setAttribute(tgts[j], prefix + tgt);
//            all[i].setAttribute(tgts[j],site2[doc.domain].protocol + '//' + site2[doc.domain].domain_url + tgt); // working code.
        }
      }
    },
    absolute_link_1: function(href){return site2[this.nickname].protocol + '//' + site2[this.nickname].domain_url + href;},
    link_dbtp2href_abs: function(dbtp, quote){
      return (this.nickname!=site.nickname? this.protocol + '//' + this.domain_url : '') + this.link_dbtp2href(dbtp, quote);
    },
//    prep_own_posts: function(){}, // prepare own_post object.
//    prep_own_posts_event: function(e){}, // event entry for preparing own_post object.
    ls_key_own_posts: pref.script_prefix + '.own_posts.',
    prep_own_posts_event : function(e){
      if (e) site2[site.nickname].prep_own_posts_1(e.key); // gives 'this' value to 'prep_own_posts_1'. MUST BE site2[site.nickname].
      if (window.name===site.nickname) send_message('parent',['OWN_POSTS', window.name, site3[window.name].own_posts]);
    },
    prep_own_posts : function(bt){
      site3[this.nickname].own_posts = {};
      if (localStorage) {
        var keys = (bt)? [this.ls_key_own_posts + bt] : Object.keys(localStorage);
        for (var i=0;i<keys.length;i++) this.prep_own_posts_1(keys[i]);
      }
//console.log(site3[this.nickname].own_posts);
    },
    prep_own_posts_1 : function(key){
      if (key.indexOf(this.ls_key_own_posts)==0) {
        var bt = key.substr(this.ls_key_own_posts.length);
        var board = bt.substr(0,bt.lastIndexOf('/')+1);
        if (!site3[this.nickname].own_posts[board]) site3[this.nickname].own_posts[board] = {};
        var nos = JSON.parse(localStorage[key] || '[]');
        for (var j=0;j<nos.length;j++) site3[this.nickname].own_posts[board][nos[j]] = null;
      }
    },
//    clean_up_own_posts : function(ths,board){ // working code.
////console.log('clean up');
//      if (localStorage) {
//        var nos = {};
//        for (var i=0;i<ths.length;i++) nos[ths[i].key.substr(ths[i].key.lastIndexOf('/')+1)] = null;
//        var own_key_bd = this.ls_key_own_posts + board;
//        var keys = Object.keys(localStorage);
//        for (var i=0;i<keys.length;i++)
//          if (keys[i].indexOf(own_key_bd)==0 && nos[keys[i].substr(own_key_bd.length)]!==null) delete localStorage[keys[i]]
//      }
////console.log(site3[this.nickname].own_posts);
//    },
    clean_up_LS: function(domain, board, nos){
      if (domain!==site.nickname) return; // PATCH.
      if (localStorage) {
        var keys = Object.keys(localStorage); // to minimize LS access.
        if (pref.thread_reader.own_posts_tracker && pref.thread_reader.clean_up_own_posts) this.clean_up_LS_th(keys, this.ls_key_own_posts, board, nos);
        if (pref[cataLog.embed_mode].deleted_posts.auto_clean) this.clean_up_LS_th(keys, this.ls_key_deletedPosts, board, nos);
        if (pref.recovery.auto_clean) {
          this.clean_up_LS_th(keys, this.ls_key_comment, board, nos);
//          this.clean_up_LS_th(pref.script_prefix+'.comment.'+site.nickname, board, nos); // patch for half year, from 2016.09 to 2020.10
        }
        if (pref.test_mode['162']) this.clean_up_LS_th(keys, this.ls_key_hiddenPosts, board, nos);
      }
    },
    ls_key_deletedPosts: pref.script_prefix + '.deletedPosts.',
    clean_up_deleted_posts_1: function(bt){if (localStorage) delete localStorage[this.ls_key_deletedPosts + bt];},
//    ls_key_comment: pref.script_prefix + '.draft.',
    ls_key_comment: pref.script_prefix + '.draft.',
    ls_key_hiddenPosts: pref.script_prefix + '.hiddenPosts.',
    clean_up_LS_th: function(keys, ls_key, board, nos){
      var ls_key_bd = ls_key + board;
//      var keys = Object.keys(localStorage);
      for (var i=0;i<keys.length;i++)
        if (keys[i].indexOf(ls_key_bd)==0 && nos[keys[i].substr(ls_key_bd.length)]===undefined) delete localStorage[keys[i]]; // console.log('delete_LS: '+keys[i]);
    },
    check_reply: (function(){
      var com_or_txt = false;
      var pn_tags = document.createElement('div');
      var regexp_anchor      = '';
      var regexp_anchor_len  = 0;
      var regexp_anchor_cb   = '';
      var regexp_anchor_txt_1  = /^>>[0-9]+/; // RegExp.test executes RegExp.exec once.
      var regexp_anchor_txt    = />>[0-9]+/g;
      var regexp_anchor_cb_txt_1 = /^>>>\/[0-9A-z_\+]+\/[0-9]+/;
      var regexp_anchor_cb_txt = />>>\/[0-9A-z_\+]+\/[0-9]+/g;
      var regexp_anchor_com    = /&gt;&gt;[0-9]+/g;
      var regexp_anchor_cb_com = /&gt;&gt;&gt;\/[0-9A-z_\+]+\/[0-9]+/g;
      var remake_own_posts = true;
      var own_posts;
      var own_posts_cb;
      var tag_ex_list;
      var str_rm_list;
//      var time_offset = Math.floor(Date.now()/1000)*1000; // BUG.
////////      var count_replies = 0; // for direct call to check_t1 from native catalog.
      function add_you(post, add_name){
        var as = post.pn.getElementsByTagName('a');
        for (var i=0;i<as.length;i++) {
          var to_me = false;
          var txt = as[i].textContent;
          if (own_posts && own_posts[txt.substr(2)]===null && regexp_anchor_txt_1.test(txt)) to_me = true; // SHOULD USE SEARCH INSTEAD OF TEST TO KEEP CONSISTENCY.
          if (!to_me && own_posts_cb && regexp_anchor_cb_txt_1.test(txt)) {
            var tgt = txt.split('/');
            var bd = '/'+tgt[1]+'/';
            if (own_posts_cb[bd] && own_posts_cb[bd][tgt[2]]===null) to_me = true;
          }
          if (to_me) {
            if (pref.thread_reader.show_reply_to_me_by==='plain') as[i].parentNode.insertBefore(document.createTextNode(' (You)'), as[i].nextSibling);
            else as[i].textContent = as[i].textContent + ' (You)'; // break dollchan.
          }
        }
        if (add_name && own_posts && own_posts[post.no]===null) {
          var pn_name = post.parse_funcs_html.pn_name(post);
          if (pref.thread_reader.show_own_post_by==='plain') pn_name.parentNode.insertBefore(document.createTextNode(' (You)'), pn_name.nextSibling);
          else pn_name.innerHTML = pn_name.innerHTML + ' (You)';
        }
      }
      function check_1(post, watch, extracted_tags, extracted_posts){
        var of_mine = own_posts && (post.no in own_posts);
        if (of_mine) post.reply_of_mine = true;
        if (!of_mine || !pref.catalog_footer_ignore_my_own_posts) {
          var com = (com_or_txt)? post.com : post.body;
          if (com) {
            var to_me  = false;
            if (own_posts) {
              var anchors = com.match(regexp_anchor);
              if (anchors!==null) {
                for (var j=0;j<anchors.length;j++) {
                  var tgt = anchors[j].substr(regexp_anchor_len);
                  if (own_posts[tgt]===null) {to_me = true; break;}
            }}}
            if (!to_me && own_posts_cb) {
              anchors = com.match(regexp_anchor_cb);
              if (anchors!==null) {
                for (var j=0;j<anchors.length;j++) {
                  var tgt = anchors[j].split('/');
                  var bd = '/'+tgt[1]+'/';
                  if (own_posts_cb[bd] && own_posts_cb[bd][tgt[2]]===null) {to_me = true; break;}
            }}}
            if (to_me) {
              post.reply_to_me = true;
              watch[1]+=0x10000;
            }
          }
          watch[1]++;
        }
        extracted_posts[extracted_posts.length] = post;
        if (extracted_tags) check_t1(post, extracted_tags);
      }
      function check_t1(post, extracted_tags){
        if (com_or_txt) {
          var mail = post.com; // 4chan doesn't have 'mail'.
          if (!mail) return;
          mail = mail.replace(/<[^>]*>|&[^;]*;/g,' ');
          if (pref.test_mode['47']) {
            pn_tags.innerHTML = mail;
            mail = pn_tags[brwsr.innerText];
          }
        } else {
          if (post.editing) {
            extracted_tags[extracted_tags.length] = post;// post.no;
            return;
          }
          mail = post.body || post.txt; // post.com; // com must be html to keep consistency at creating DOM, ex. post_json2html.
          if (!mail) return;
        }
        if (mail.indexOf('#')===-1) return; // short cut.
        if (str_rm_list) for (var i=0;i<str_rm_list.length;i++) mail = mail.replace(str_rm_list[i],'\n');
        var tags = mail.match(liveTag.scan_regex);
//        if (tags!==null) extracted_tags = extracted_tags.concat(tags);
        if (tags!==null)
          for (var i=0;i<tags.length;i++)
            if (tags[i].length<=pref.liveTag.maxstr) {
              if (tag_ex_list) {
                for (var j=tag_ex_list.length-1;j>=0;j--) if (tag_ex_list[j].test(tags[i])) break;
                if (j>=0) continue;
              }
              extracted_tags[extracted_tags.length] = tags[i];
            }
      }
      function prep_check_1(th){
        if (th.parse_funcs.type_com==='txt') {
          regexp_anchor     = regexp_anchor_txt;
          regexp_anchor_len = 2;
          regexp_anchor_cb  = regexp_anchor_cb_txt;
          com_or_txt        = false;
        } else {
          regexp_anchor     = regexp_anchor_com;
          regexp_anchor_len = 8;
          regexp_anchor_cb  = regexp_anchor_cb_com;
          com_or_txt        = true;
        }
      }
      function prep_check_t1(th){
        if (pref.liveTag.ex_list) {
          tag_ex_list = pref_func.merge_obj5a(th,pref.liveTag.ex_list_obj5,null);
          str_rm_list = pref_func.merge_obj5a(th,pref.liveTag.rm_list_obj5,null);
        } else {
          tag_ex_list = null;
          str_rm_list = null;
        }
//        tag_ex_list = (pref.liveTag.ex_list)? pref_func.merge_obj5(th.key,pref.liveTag.ex_list_obj5,null) : null;
      }
      return {
        remake_own_posts : function(){remake_own_posts = true;}, // couldn't get an event from myself, so don't miss posts from my thread.
        make_own_posts : function(){  // called also from catalog initialializer, prevent from being called twice at initial.
          site2[site.nickname].prep_own_posts();
          remake_own_posts = false;
        },
        add_you: add_you,
        set_own_posts: function(th){
          own_posts_cb = site3[th.domain].own_posts;
          own_posts = own_posts_cb[th.board] || own_posts_cb['ALL']; // 'ALL' for meguca.
//          own_posts = own_posts_cb[th.board];
          if (Object.keys(own_posts_cb).length==0) own_posts_cb = undefined; // patch for faster execution.
        },
//        check_t1: check_t1,
        check_t1_op: function(th){
          prep_check_1(th);
          prep_check_t1(th);
          var extracted_tags = [];
          check_t1(th, extracted_tags);
////          watch[0] = (watch[0]&0xfffe0000) | 1; // BUG, WHY???
//          watch[0] = (watch[0]&0xfffe0000);
////          watch[2] = th.time; // requires many changes
////          watch[2] = th.time_created; // requires many changes // BUG, WHY???
          return extracted_tags;
        },
        get_checked_time: function(watch){
//          return watch[2] + time_offset; // BUG.
          return watch[2];
        },
        set_watch_time: function(watch, time, key){
          if (time>0) {
            watch[0] |= time<0x10000? 0x04000000 : 0x00040000;
//            watch[0] |= pref.test_mode['167']? 0x04000000 : 0x00040000;
////          watch[2]  = time - time_offset; // to reduce memory usage, but doesn't effect. // BUG!!! watch[2] is initialized to 0, and not watched thread will enter with this. all threads must be initialized to '-time_offset'.
//            watch[2]  = time;
            if (watch[0]&0x00800000) watch[2] = time; // to reduce memory churn, this may require 'double' instead of Int32 for watch[2], CAUTION for compression.
            else {
              this.time_watch[key] = time; // not change watch[2] directry, because watch[2] has multiple meanings in active mode; time_watch(watch) and time_check(stat,tag,archive),
                                           // while passive mode uses lth.nof_posts only to track, watch[2] can be changed directry, but I should try to use the same flow as much as possible.
////              if (!track) this.time_watch[key] = time;
////              else this.time_track[key] = time;
//              watch[0] &= 0xff7fffff;
            }
          }
        },
        set_watched_to_last: function(watch, time, key, track){
          if (watch[0]&0x00800000 || !(watch[0]&0x00080000)) this.set_watch_time(watch, time, key);
//          if (!track) this.set_watch_time(watch, time, key, track);
          watch[1] = track || 0;
        },
        set_unwatch: function(watch){
          watch[0] &= 0xf373ffff; // 0xff73ffff; // includes passive
          watch[1] = 0;
        },
        time_watch:{},
//        time_track:{},
//        check: function(th, watch, make_pn){
        check: function(th, lth, watch, notify, ext_posts, passive){ // watch[0]>0 && watch[3]>0 : usual check,
          // watch[0]>0 && watch[3]<0 : retag, initialize tag but doesn't show new replies. (watch[3] must retain its value), and entry
          // watch[0]<0 && watch[3]>0 : watchtime is updated. rescan new replies and show them. (watch[0] can be destroyed), BUG, can't entry.
          // watch[0]<0 && watch[3]<0 : not used.
          //
          // watch[0] [15:0]: num of posts so far, // SHOULD SWAP UPPER AND LOWER, [31:12]: nof_replies, [11:0]: flags.
          //          [16]: initial,
          //          [17]: initial tag, retag,
          //          [18]: watch_req, [19]:watching, [22:20]:archive,
          //          [23]: watch time not initialized. (23,18) -> 10: not initialized, 11: watch_req, use watch[2], 01: watch_req, use time_watch, 00: usual state.
          //          [24]: reentry of detection of deleted posts
          //          [25]: posts_saved.
          //          [26]: watch_req(passive).
          //          [27]: watching(passive).
          // watch[1] number of unread replies to me << 16 + number of unread replies
          // watch[2] checked time'
          var time_check = watch[2];
          var time_watch = watch[2];
          var ur_old = liveTag.generate_ur(watch[1]);
          var watchtime_changed = watch[0]&0x04040000; // if requested
          if (watchtime_changed) { // initial
//            time_watch = cataLog.get_watch_time_of_a_thread(th.key,th.time_created,null, true); // BUG, can't handle if time_watch===0(not watched).
            var actv = watch[0]&0x00040000;
            if (watch[0]&0x00800000) {
              time_check = 0;
              if (actv) watch[2] = 0;
            } else {
              time_watch = this.time_watch[th.key];
//              var time_track = !this.time_watch[th.key] && this.time_track[th.key];
//              time_watch = this.time_watch[th.key] || time_track;
              delete this.time_watch[th.key];
//              delete this.time_track[th.key];
            }
//            watch[2] = 0;
            watch[1] = 0;
            watch[0] = actv? (watch[0]&0xfb7b0000) | 0x00080000 // depatch legacy from safe487 and safe1090 // + ((th.omitted_posts)? th.omitted_posts+1 : 0) // number of replies so far, patch for 4chan catalog_json.
                           : (watch[0]&0xfbfb0000) | 0x08000000;// keep 0x00800000 flag
                      // &=0xff7fffff for entry by clicking WATCH triage.
//            init     = true;
          }
          var time_check_old = time_check;
//console.log('start: '+dbt[0]+dbt[1]+dbt[2],watch[5]);
          var watching = watch[0]&0x0c0c0000; // includes passive
if (!passive) {
          var i = th.posts.length-1;
//          if (remake_own_posts) { // working code.
//            site2[site.nickname].prep_own_posts(); // couldn't get an event from myself, so don't miss posts from my thread.
//            remake_own_posts = false;
//          }
//          watch[2] = (th.posts[i].time*th.parse_funcs.time_unit || watch[2]) - time_offset; // BUG.
          watch[2] = th.posts[i].time*th.parse_funcs.time_unit || watch[2];
          if (th.parse_funcs.time_unit!=1) {
            time_check = Math.floor(time_check/th.parse_funcs.time_unit);
            time_watch = (watchtime_changed)? Math.floor(time_watch/th.parse_funcs.time_unit) : time_check;
          }
          var extracted_tags = (pref.liveTag.from==='post')? [] : null;
          if (extracted_tags) prep_check_t1(th);
//          var ur_old = liveTag.generate_ur(watch[1]);
//          var watching = watch[0]&0x000c0000;
          if (watching || extracted_tags) prep_check_1(th);
          if (watching) {
            if (remake_own_posts) this.make_own_posts();
//            liveTag.mems[th.domain][th.board].nr = -1; // mark as dirty. // moved to out to include case of passive.
//            prep_check_1(th);
            this.set_own_posts(th);
            while (i>=0 && th.posts[i].time>time_watch) check_1(th.posts[i--], watch, extracted_tags, ext_posts); // extracting tags in op is redundant, because they are ALWAYS extracted.
          }
          if (extracted_tags) {
            var tag_init = watch[0]&0x00020000; // patch for retag.
            while (i>0 && (tag_init || th.posts[i].time>time_check)) check_t1(th.posts[i--], extracted_tags); // tuned for initial loop. don't extract tags in op.
            var tags = extracted_tags.length!=0? liveTag.extract_tags(th, extracted_tags, null, !tag_init) : undefined; // 'check_update_tags_color' is called in this.
          }
//          if (!common_obj.thread_reader) { // Why is this reqiured?
//            if (extracted_tags && extracted_tags.length!=0) var tags = liveTag.extract_tags(th, extracted_tags, null, !tag_init); // 'check_update_tags_color' is called in this.
//            else if (pref.liveTag.style.use && watching) {
//              var ur = liveTag.generate_ur(watch[1]);
//              if (ur_old!=ur) liveTag.update_ur(th.key,ur,ur_old!=0 && watchtime_changed); // can choose faster function if ur==0.
//            }
//          }
////            if (pref3.stats.use && stats) stats.aggregate(th); // '&& stats' is a patch for embed_thread. // can't count deleted posts
          if (time_watch!==time_check || !watching) {
            i = th.posts.length;
            while (i>0 && th.posts[i-1].time>time_check) i--;
            var new_posts = i===0? th.posts : th.posts.slice(i);
          }
          if (pref3.stats.use) stats.aggregate(th, new_posts || ext_posts); // can count deleted posts
} else { // passive is the same as nof_new_posts
          if (!(watch[0]&0x00800000)) {
            if (watching) watch[1] += passive; // relative counting for deletion
//            if (watching) watch[1] += passive - (((watch[0]&0x0000ffff)===0 && watch[2]<0x10000)? watch[2] : 0); // means lth.nr += nof_new_posts - (lth.nof_posts===0? watch[2] : 0);
//            watch[2] = th.nof_posts || watch[2]; // tracking for archive // th.nof_psots is stored in watch[0] 
          } else if (th.nof_posts>=time_watch) {
            if (watching) watch[1] = th.nof_posts - time_watch;
            watch[0] &= 0xff7fffff;
//            watch[2] = th.nof_posts;
          }
//          if (pref3.stats.use && stats) stats.aggregate_passive(th, passive, date); // moved to outside of this function.
}
//          else if (pref.liveTag.style.use && watching) {
          if (watching) {
            liveTag.dirtify_ur(th); // mark as dirty.
            if (pref.liveTag.style.use) {
              var ur = liveTag.generate_ur(watch[1]);
              if (ur_old!=ur) liveTag.update_ur(lth,ur,ur_old!=0 && watchtime_changed); // can choose faster function if ur==0 or limited to usual change(0->1->2)
            }
          }
          watch[0] = (watch[0]&0xfff90000) + th.nof_posts;
//console.log('end:   '+dbt[0]+dbt[1]+dbt[2]);
//if (pref.debug_mode['3'] && extracted_tags.length!=0) console.log(th.key+': '+extracted_tags);
          if (notify && watching) {
//            if (ext_posts.length!=0 && pref.notify.desktop.notify && th.parse_funcs.add_op_img_url) th.parse_funcs.add_op_img_url(ext_posts,th.board,th.domain); // can't get 'com' if 'meguca'
            if (pref.notify.desktop.notify) {
              if (ext_posts.length!=0) site2[th.domain].wrap_to_parse.posts({posts:ext_posts, __proto__:th});
              else if (passive) ext_posts[ext_posts.length] = {com: passive==1? 'a new reply.' : passive+' of new replies.', time:watch[2]};
            }
            notifier.changed(th, ext_posts); // time_track? [] : ext_posts);
          }
          return {tags:tags, posts:new_posts || ext_posts, time_check_old:time_check_old};
        },
      }
    })(),

////////    get_posts2 : function(doc,pool){return {}}, // subfunction of check_reply_to_me, parse html to json.
    get_post_offsetTop : function(doc,num) {}, // get offsetTop of Nth object.
    favicon: {
      get_favicon: function(){
        var links = document.getElementsByTagName('head')[0].getElementsByTagName('link');
        for (var i=0;i<links.length;i++) if (links[i].getAttribute('rel')=='shortcut icon') return links[i];
        var pn = document.createElement('link');
        pn.setAttribute('rel','shortcut icon');
        pn.setAttribute('href',this.none);
        pn.setAttribute('type','image/x-icon');
        return document.getElementsByTagName('head')[0].appendChild(pn);
      },
      get_title: function(){
        var title = document.getElementsByTagName('head')[0].getElementsByTagName('title')[0];
        if (!title){
          title = document.createElement('title');
          title = document.getElementsByTagName('head')[0].appendChild(title);
        }
        return title;
      },
    }, // object for favicon
//    get_op_image_url: function(th,type){}, // get op image's url.
    get_icon : function(){}, // get sticky icon.
    add_icon : function(){}, // add sticky icon.
    time_revised_check: function(nof_ths){return false;},

//    format_thread_layout   : function(thread){}, // formats its layout   for catalog.
//    format_thread_style    : function(thread){}, // formats its style    for catalog.
//    format_thread_contents : function(thread){}, // formats its contents for catalog.
//    format_thread_always   : function(thread){}, // formats its contents for catalog, always executed.
////////    format_time            : function(thread){}, // formats its timestamp to local time.
//    format_remove_tn_area_size: function(thread){}, // remove thumbnail area size
//    mark_newer_posts       : function(thread,time){return null;},  // mark newer posts, and returns marked first post.
//    unmark_post_from_event : function(post){},  // unmark post.
    mark_newer_posts2: function(posts,date,unmark, short_cut) {
      date = date/this.parse_funcs.post_json.time_unit;
      var marked_first_idx = null;
      for (var i=posts.length-1;i>=0;i--) {
        var pn = posts[i].pn
        if (date<posts[i].time) {
          pn.classList.add(pref.cpfx+'post_new');
//          pn.setAttribute('style', pref.style.post_new);
          if (unmark) pn.addEventListener('mouseleave', this.unmark_post_from_event, false);
          marked_first_idx = i;
        } else {
          if (short_cut) break;
          pn.classList.remove(pref.cpfx+'post_new');
//          if (pn.getAttribute('style')!=null) pn.removeAttribute('style');
        }
      }
      return marked_first_idx;
    },
//    mark_newer_posts : function(nickname,posts,date, func, unmark, short_cut){ // working code
////      var offset_top = 0;
//      var marked_first_post = null;
//      for (var i=posts.length-1;i>=0;i--) {
////        var reply = posts[i];
//////        posts[i] = posts[i].parentNode;
//////        if (class_or_tag=='class') while (posts[i].className.search(key)==-1) posts[i] = posts[i].parentNode;
//////        if (class_or_tag=='tag') while (posts[i].tagName.search(key)==-1) posts[i] = posts[i].parentNode;
////        if (class_or_tag=='class') reply = posts[i].getElementsByClassName(key)[0];
////        if (class_or_tag=='tag')  reply = posts[i].getElementsByTagName(key)[0];
//        var reply = (func)? func(posts[i]) : posts[i];
//        if (reply)
//          if (date<site2[nickname].parse_funcs.post_html.time_pn(posts[i])) {
//            reply.setAttribute('style', pref.style.post_new);
////            if (!reply.style) reply.style = {};
////            reply.style.border = '2px solid red'; // ca'nt use !important
//            if (unmark) reply.addEventListener('mouseover', site2[nickname].unmark_post_from_event, false);
////            offset_top = reply.offsetTop;
//            marked_first_post = reply;
//          } else {
//            if (reply.getAttribute('style')!=null) reply.removeAttribute('style');
//            if (short_cut) break;
////            if (reply.style) reply.style.border = 'none';
//          }
//      }
////      return offset_top;
//      return marked_first_post;
//    },
    format_pn: function(pn,thq_no, pref_env, post, th){
      if (!pref_env) pref_env = pref[cataLog.embed_mode];
      if (pref_env.colorID) this.colorID(pn);
      if (pref_env.backlink) this.backlink(pn,thq_no, th); // th for short href.
      if (pref_env.localtime) this.localtime(pn);
      if (post) { // BUG, CALLED TWISE TIMES for deleted posts in embed_mode==='thread'
//        if (cataLog.embed_mode==='page' || cataLog.embed_mode==='thread') site2['DEFAULT'].check_reply.add_you(post, true); // "cataLog.embed_mode==='page'" is a patch.
        site2['DEFAULT'].check_reply.add_you(post, true);
        if (post.deleted_after) pn.classList.add('CatChan_deleted');
        if (post.search_result!==undefined) site2[post.domain_html].update_posts0_class(pn, post.search_result);
      }
    },
    colorID: function(){},
    localtime: function(){},
    unmark_post_from_event: function unmark_post_from_event(e) {
      e.currentTarget.removeEventListener('mouseleave', unmark_post_from_event, false);
//      site2['DEFAULT'].unmark_post(e.target);
//    },
//    unmark_post: function(pn) {
      e.currentTarget.classList.remove(pref.cpfx+'post_new');
//      pn.removeAttribute('style');
////      pn.setAttribute('style','border: none');
    },
    modify_thread_link     : function(thread){return [];}, // modify thread link and returns information to add event listener.
    preprocess_html        : function(doc_txt){return doc_txt;},  // pre-process document from txt. // cause memory leak.
//    preprocess_doc         : null, //function(doc){},  // pre-process document.
////////    thread2headline : function(doc){return [0,0];},  // make headline from entire thread, returns num of [posts, images].
//    add_thread_link : function(doc,url){}, // add link to this thread.
    check_thread_archived : function(thread){return false;}, // check the thread is archived.
    get_owners_recommendation: function(){return '';}, // return string of owner's recommendation.
    get_board_tags : function(){return {};}, // return object of board tags.
//    get_json_url_thread : function(board,thread){return '';}, // return url of JSON API.
    get_json_url_catalog : function(board){return '';}, // return url of JSON API.
//    parse_json_thread: function(txt,from_http){return JSON.parse(txt);}, // parser of JSON API.
    parse_json_thread: function(obj,from_http){}, // parser of JSON API.
//    parse_json_catalog: function(txt){return JSON.parse(txt);},  // parser of JSON API.
    uip_tgt_post : function(no){return null;}, // returns uip target post.
    uip_post_num : function(post){return null;}, // returns num in posts.
    uip_check: function(callback){}, // hook for uip_tracker.
    catalog_native_prep_sel: function(pn_filter, pn_tb, selector_native){
      if (selector_native) {
        if (selector_native.selectedIndex!=0) {
          selector_native.selectedIndex = 0;
          var evt = document.createEvent('UIEvents');
          evt.initUIEvent('change', false, true, window, 1);
          selector_native.dispatchEvent(evt);
        }
        selector_native.style.display = 'none';
      }
      var selector_catchan = pn_filter.getElementsByTagName('select')['catalog.order.ordering'];
      selector_catchan.parentNode.removeChild(selector_catchan.nextSibling); // remove <br>
      if (selector_native) selector_native.parentNode.insertBefore(selector_catchan,selector_native);
      else pn_tb.childNodes[3].insertBefore(selector_catchan,pn_tb.childNodes[3].firstChild);
    },
    catalog_native_prep_swap_sortSel: function(sortSel){
      sortSel.parentNode.insertBefore(cataLog.components.ordering, sortSel.nextSibling);
      sortSel.setAttribute('style', 'display:none');
    },
    catalog_frame_prep: function(pn12){}, // prepare for frame mode.
    catalog_native_frame_prep: function(){ // pn12_button){ // prepare for frame mode in native catalog.
      var mode = false;
      var ifrm;
      var pn_catalog;
      return function(){ //  insert_remove_frame(){
        if (!mode) {
          pn_catalog = site2['common'].absorb_children(site.root_body);
          pn_catalog.style.width = pref.catalog_size_frame0_width + '%';
          pn_catalog.style.float = 'left';
          pn_catalog.style.height = '' + window.innerHeight + 'px';
          pn_catalog.style.overflow = 'scroll';
          ifrm = site2[site.nickname].catalog_native_frame_prep_frame(site.root_body,null);
        } else {
          site.root_body.removeChild(ifrm);
          ifrm = null;
          site.embed_frame_win = null;
          site2['common'].disgorge_children(pn_catalog);
//          this.removeEventListener('click', insert_remove_frame, false);
        }
        mode = !mode;
      };
//      pn12_button.addEventListener('click', insert_remove_frame, false);
//      return insert_remove_frame;
    },
    catalog_native_frame_prep_frame: function(parent,ref_node){
      ifrm = document.createElement('iframe');
      ifrm.setAttribute('name',site.embed_frame);
      ifrm.style.height = '' + window.innerHeight + 'px';
      ifrm.style.margin = '0px';
      ifrm.style.width = pref.catalog_size_frame1_width + '%';
      parent.insertBefore(ifrm,ref_node);
      site.embed_frame_win = ifrm.contentWindow;
      return ifrm;
    },
    catalog_embed_prep: function(pn12){ // https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/style, reset by null.
      pn12.style.position = null;
      pn12.childNodes[1].style.width = null;
      pn12.childNodes[1].style.height = window.innerHeight + 'px';
      pn12.childNodes[0].childNodes[0].style.display = 'none';
      pn12.childNodes[0].childNodes[1].style.display = 'none';
      pn12.style.border = null;
      pn12.style.left = null;
      pn12.style.resize = null;
      pn12.style.top = null;
    },
    thread2search_obj : function(thread){return [thread[brwsr.innerText],'','','','','','',''];}, // return search_obj from thread.
////    get_click_area: function(pn){
////      var img = pn.getElementsByTagName('img');
////      return (img.length!=0)? img : pn;
////    },
//    get_click_area: function(pn){
//      var img0 = pn.getElementsByTagName('img')[0];
//      return (img0)? img0 : pn;
//    }
    popups_posts: (function(){
      function up_timer_factory(){
        var up_timer = null;
        var up_e = null;
        var up_func = null;
        var up_pf = null;
        var up_src = null;
        function over_0(pf, e, func, src){
          if (pf.popdown==='imm') func(e);
          else {
            if (!src) src = e.target;
            if (pf.popup_mMove) src.addEventListener('mousemove', over_1, false);
            up_func = func;
            up_pf = pf;
            up_src = src;
            over_1(e, true);
          }
        }
        function over_1(e, force){
          if (!up_timer && !force) return; // for racing condition
          if (up_timer) clearTimeout(up_timer);
          up_timer = setTimeout(timer_end, up_pf.popup_delay);
          up_e = e;
        }
        function timer_end(stop){ // this may be fired twice, because this has a racing condition between finish of up_timer and over_1 event. // FIXED.
          if (stop) clearTimeout(up_timer);
          up_timer = null;
          if (up_pf.popup_mMove) up_src.removeEventListener('mousemove', over_1, false);
          if (!stop) up_func(up_e);
        }
        return {
          over: over_0,
          stop: function(){
            if (up_timer) {timer_end(true);return true;}
            return false;
          },
          get up_timer(){return up_timer;},
          out: function(e){
            if (e.relatedTarget===erT_done) {erT_done = null; return e.relatedTarget;}
            else return this.stop();
          },
        };
      }
      var pns = new Map();
      var erT_done = null;
      function out_1_if_exists(src, pn_ex, imm){
        var pn = pns.get(src);
        if (pn)
          if (!Array.isArray(pn)) {if (pn!==pn_ex) out_1(pn, imm);}
          else for (var i=0;i<pn.length;i++) if (pn[i] && pn[i]!==pn_ex) out_1(pn[i], imm);
      }
      function out_1(pn, imm){
        var info = pns.get(pn);
        var pf = info.pf && info.pf() || pref.proto;
        if (imm || pf.popdown==='imm') pop_down(pn);
        else info.timer = setTimeout(pop_down.bind(null,pn), pf.popdown_delay);
      }
      function pop_setup(src, pn, obj, pna){
        pns.set(src, pna || pn);
        pns.set(pn, obj || {src:src}); // bi-directional
        pn.addEventListener('mouseenter',pop_over, false);
        pn.addEventListener('mousedown',cnst.div_mousedown,false);
      }
      function pop_down(pn, snatch){
        if (pn && pn.parentNode) {
          if (!snatch) pn.parentNode.removeChild(pn);
          var info = pns.get(pn);
          if (info.key) posts.pop_load_req_delete(info.key, pn);
          if (info.chart && !snatch) info.chart.destroy();
          if (info.scroll) pn.removeEventListener(brwsr.mousewheel, cnst.div_scroll, false);
          var src = info.src;
          var pna = pns.get(src);
          if (Array.isArray(pna)) {
            pna[pna.indexOf(pn)] = null; // keep position, not splice
            for (var i=0;i<pna.length;i++) if (pna[i]) break;
            if (i==pna.length) pns.delete(src);
          } else pns.delete(src);
          pns.delete(pn);
          pn.removeEventListener('mouseenter', pop_over, false);
          pn.removeEventListener('mousedown',cnst.div_mousedown,false);
          if (snatch) {
            pn.removeEventListener('mouseleave', pop_out, false);
            return info;
          }
        }
      }
      function pop_over(e){
        var ecT = e.currentTarget;
        var info = pns.get(ecT);
        var pf = info.pf && info.pf() || pref.proto;
        if (pf.popdown==='imm') pop_down(ecT);
        else {
          if (info.timer) clear_down_timer(info);
          erT_done = e.relatedTarget;
          ecT.addEventListener('mouseleave', pop_out, false);
        }
      }
      function pop_out(e){
        var ecT = e.currentTarget;
        ecT.removeEventListener('mouseleave', pop_out, false);
        out_1(ecT);
      }
      function clear_down_timer(info){clearTimeout(info.timer); info.timer=null;}

      var hist_x = 'left';
      var hist_y = 'top';
      var ddEcW = 0;
      var ddEcH = 0;
      function set_pos(pn, e, hist, narrow_x, narrow_y){
        var factor_x = (!hist)? 0.5 : (hist_x==='left')? 3/4 : 1/4;
        var factor_y = (!hist)? 0.5 : (hist_y==='top')?  4/5 : 1/5;
        ddEcW = document.documentElement.clientWidth;
        ddEcH = document.documentElement.clientHeight;
        var oX = pref.proto.popup3.oX;
        var oY = pref.proto.popup3.oY;
        if (ddEcW*factor_x-e.clientX>0 ^ narrow_x) {pn.style.left = e.clientX + oX + 'px'; hist_x='left';}
        else {pn.style.right = ddEcW - e.clientX + oX + 'px'; hist_x='right';}
        if (ddEcH*factor_y-e.clientY>0 ^ narrow_y) {pn.style.top = e.clientY + oY + 'px'; hist_y='top';}
        else {pn.style.bottom = ddEcH - e.clientY + oY + 'px'; hist_y='bottom';}
//        if (document.documentElement.clientWidth/2-e.clientX>0) pn.style.left = e.clientX + 10 + 'px';
//        else pn.style.right = document.documentElement.clientWidth - e.clientX + 10 + 'px';
//        if (document.documentElement.clientHeight/2-e.clientY>0) pn.style.top = e.clientY + 10 + 'px';
//        else  pn.style.bottom = document.documentElement.clientHeight - e.clientY + 10 + 'px';
      }
      function adjust_pos(pn, e){
        var s = pn.style;
        var tb = parseInt(s.top?s.top:s.bottom,10);
        var visible_v = pn.offsetHeight + tb < ddEcH;
        if (!visible_v && tb > ddEcH*0.5) {
          var h = ddEcH - pn.offsetHeight;
          var pos = (h<0? 0:h)+'px';
          if (s.top) {s.top = pos; hist_y = 'bottom';}
          else {s.bottom = pos; hist_y = 'top';}
        }
        var lr = parseInt(s.left?s.left:s.right,10);
        var visible_h = pn.offsetWidth + lr < ddEcW;
        if (!visible_h && lr > ddEcW*0.5) {
          var pos = ddEcW - parseInt(s.left?s.left:s.right,10)+20 + 'px';
          if (s.left) {s.right = pos; s.left = null;}
          else {s.left = pos; s.right = null;}
        }
      }

      var blacklist = new WeakSet();
      var geh;
      function etHref2domain(et){
        return href2domain(et.getAttribute('href')) || href2domain(et.href); // et.href is patch for backlinks in 4chan page
      }
      function href2domain(href){
        if (href[0]==='/' || href[0]==='#') return site.nickname;
        else for (var d in liveTag.mems) if (href.indexOf(site2[d].domain_url)!=-1) return site2[d].nickname;
        for (var d in site2) if (href.indexOf(site2[d].domain_url)!=-1) return /*site2[d].nickname_href2domain ||*/ site2[d].nickname;
      }
      var up0 = up_timer_factory(); // surface
      var up1 = up_timer_factory(); // surface for infoPreview
      function over(e, force){ // image_hover can be merged to this, and popup2 also. e.path can be created easily, mouseenter can be replaced to mouseover.
        out(e);
        var et = e.target;
        var pf_mode = pref[cataLog.embed_mode];
        if (pf_mode.popup && et.tagName==='A' && site2['DEFAULT'].popups_link_regex.test(et.textContent)) posts.over(e, force);
        else if ((pf_mode.image_hover || pf_mode.infoPv.use) && geh.isThumbnail(e)) {
          if (pf_mode.image_hover) up0.over(pf_mode.thumbnail.hover, e, cataLog.DIH.image_hover_add);
          if (pf_mode.infoPv.use) up1.over(pf_mode.infoPv, e, cataLog.InfoPv.show);
        } else if (pf_mode.popupX.use) up0.over(pf_mode.popupX, e, popupSurface.X);
//        else if (pref.test_mode['121'] && geh.getID(et)) up0.over(pf_mode.popupID, e, popupSurface.ID);
//        else if (pref.test_mode['121'] && geh.getName(et)) up0.over(pf_mode.popupName, e, popupSurface.Name);
//        else if (pref.test_mode['121'] && geh.getTrip(et)) up0.over(pf_mode.popupTrip, e, popupSurface.Trip);
//        else if (pref.test_mode['121'] && geh.getCountry(et)) up0.over(pf_mode.popupFlag, e, popupSurface.Flag);
      }
      function out(e){
        cataLog.DIH.image_hover_remove(e);
        if (up0.out(e)) return;
        var erT = e.relatedTarget;
//        if (erT) out_1_if_exists(erT); // cause too many errors
        if (erT && (erT.tagName==='A' || popupSurface.et)) out_1_if_exists(erT);
        popupSurface.et= null;
//        if (erT && (erT.tagName==='A' || pref.test_mode['121'] && (geh.getID(erT) || geh.getName(erT) || geh.getTrip(erT) || geh.getCountry(erT)))) out_1_if_exists(erT);
//        if (erT && erT.tagName==='A') out_1_if_exists(erT);
//        if (erT===erT_done) erT_done = null;
//        else if (up0.stop()) return;
//        else if (erT && erT.tagName==='A') {
//          var info = pns.get(erT);
//          if (info) out_1(info);
//        }
        cataLog.InfoPv.hide();
        up1.out(e);
      }
      var posts = (function(){
        function over(e, force){
          if (pref.proto.popup_hlt && post_highlight_if_visible(e)) return;
          up0.force = force
          up0.over(pref.proto, e, pop_up);
          var domain = etHref2domain(e.target);
          if (domain!==site.nickname) e.stopPropagation(); // prevent striking(----) because of being taken as a deleted post in 4chan.
        }
        function pop_up(e){
          var force = up0.force;
          var et = e.target;
          if (pns.has(et)) {clear_down_timer(pns.get(pns.get(et))); return;}
          var domain = etHref2domain(et);
          if (!force && pref[cataLog.embed_mode].env.popup_native) {
            if (pref[cataLog.embed_mode].env.event_dynamic && !pref.test_mode['100']) {
              if (site.nickname===domain) return; // 4chan
            } else if (blacklist.has(et)) return // { // working code, but can't surpress dual popup.
  //            if (pref[cataLog.embed_mode].env.popup_native_kill) {
  //              var pn = blacklist.get(et);
  //              if (pn===null) {
  //                pn = et.cloneNode(true);
  //                pn.setAttribute('style','position:relative;left:-'+et.offsetWidth+'px;'); // to cause mouseout to popdown native popup.
  //                et.parentNode.insertBefore(pn,et.nextSibling);
  //                blacklist.set(pn,et);
  //              } else {
  //                et.parentNode.removeChild(et.previousSibling);
  //                et.removeAttribute('style');
  //                blacklist.delete(et);
  //              }
  //            } else return;
  //          }
          }
          pop_make(e, domain);
        }
        function pop_make(e, domain){
          var obj = popups_post_entry(e, domain);
          if (obj) {
            pop_style(e,obj.pn);
            pop_setup(e.target, obj.pn, obj.key? {src:e.target, key:obj.key} : null);
          }
          return obj.pn;
        }
        function inline_post(e, clg){
          var domain = etHref2domain(e.target);
          if (e.shiftKey || pref[clg.mode].inline_post==='jump') {
            var pn = clg.jump_to_post(site2[domain].popups_href2th_q(e.target.getAttribute('href'))[2]);
            if (pn) {
              pn.classList.add(site2[site.nickname].popups_posts_class_hlt); // 4chan uses post:target, this is the better
              pn.addEventListener('mouseleave', post_jump_remove_highlight, false);
            }
          } else inline_toggle(e, domain);
        }
        var pns_inline = new WeakMap(); // WeakMap for recursive remove
        function inline_toggle(e, domain){
          var et = e.target;
          var pn = pns_inline.get(et);
          if (pn) {
            pn.parentNode.removeChild(pn);
            pns_inline.delete(et);
          } else {
            var obj = popups_post_entry(e, domain);
            if (obj) {
              et.parentNode.insertBefore(obj.pn, et.nextElementSibling);
              pns_inline.set(et,obj.pn);
            }
          }
        }
        function post_jump_remove_highlight(e){
          e.currentTarget.removeEventListener('mouseleave',post_jump_remove_highlight, false);
          e.currentTarget.classList.remove(site2[site.nickname].popups_posts_class_hlt);
        }

        var keys_loading = new Map();
        function pop_load(key, domain){
          var tgts = keys_loading.get(key);
          if (tgts) {
            for (var i=tgts.length-1;i>=0;i--) { // must be backward, tgts[i] will be removed in pop_down
              var tgt = tgts[i]; // backup is needed, pop_down removes tgts[i] itself.
              var pn = pns.get(tgt.target);
              if (pn) {
                pop_down(pn); // removes tgts[i]
                var pn2 = pop_make(tgt,domain);
//                if (pn.style.left) pn2.style.left = pn.style.left; else pn2.style.right = pn.style.right; // BUG. creates too narrow popups.
//                if (pn.style.top) pn2.style.top = pn.style.top; else pn2.style.bottom = pn.style.bottom;
              }
              var pn_inline = pns_inline.get(tgt.target);
              if (pn_inline) {
                inline_toggle(tgt,domain); // toggle, remove
                inline_toggle(tgt,domain); // toggle, add
              }
            }
            keys_loading.delete(key);
          }
        }
        function pop_load_req(e, key, pn){
          var val = {target:e.target, clientX:e.clientX, clientY:e.clientY, pn:pn};
          var tgts = keys_loading.get(key);
          if (tgts) tgts.unshift(val); // reverse ordering.
          else keys_loading.set(key,[val]);
        }
        function pop_load_req_delete(key, pn){
          var tgts = keys_loading.get(key);
          if (tgts) {
            for (var i=tgts.length-1;i>=0;i--) if (tgts[i].pn===pn) tgts.splice(i,1);
            if (tgts.length===0) keys_loading.delete(key);
          }
        }
  
        function post_highlight_if_visible(e){
          var href = e.target.getAttribute('href'); // keep as it is, et.href may be %xx%yy.
          var domain = etHref2domain(e.target); // patch for backlinks in 4chan page
          var th_q = site2[domain].popups_href2th_q(href);
          var no = th_q[1];
          var tgt_th = cataLog.threads[th_q[0].key];
          var posts = tgt_th && tgt_th[16] && tgt_th[16].posts;
          if (posts) {
            for (var i=posts.length-1;i>=0;i--) if (posts[i].no===no) break;
            var pn = (i>=0)? posts[i].pn : undefined;
            if (pn && window.getComputedStyle(pn).display!=='none') {
              var oT = pn.offsetTop;
              var sT = brwsr.document_body.scrollTop;
              if (oT>sT && oT+pn.offsetHeight<sT+document.documentElement.clientHeight) { // temporal, embed only. see 'get_ref_height'.
                pn.classList.add(site2[site.nickname].popups_posts_class_hlt);
                e.target.addEventListener('mouseout',post_highlight_end, false);
                pns.set(e.target, pn);
                post_dotted_if_multi_anchors(pn, e, {domain_html:pref.catalog.mimic_base_site? site.nickname : domain});
                return true;
              }
            }
          }
          return false;
        }
        var an_dotted = null;
        function post_highlight_end(e){
          var et = e.target;
          et.removeEventListener('mouseout',post_highlight_end, false);
          pns.get(et).classList.remove(site2[site.nickname].popups_posts_class_hlt);
          pns.delete(et);
          if (an_dotted) {
            an_dotted.classList.remove('dotted');
            an_dotted = null;
          }
        }
        function post_dotted_if_multi_anchors(pn, e, th){
          if (!pref.test_mode['108']) {
            var ce = site2[th.domain_html].post_pn2ce(pn); // patch for catalog_html, whose th.posts[0].pn is the same as th.pn, this doesn't give ce, but the caller generates pn from it.
            var as = ce? ce.getElementsByTagName('a') : undefined;
            if (as && as.length>1) {
              var src_no = geh.an2srcNo(e.target,th);
//              var pn_post = site2[th.domain_html].post_an2pn(e.target);
//                var src_no = site2[th.domain_html].parse_funcs['post_html'].no({pn:pn_post});
              if (src_no) for (var i=0;i<as.length;i++) {
                var href = as[i].getAttribute('href');
                if (href && site2[th.domain_html].popups_href2dbtp(href)[3]==src_no) {
                  as[i].classList.add('dotted');
                  an_dotted = as[i];
                  break;
                }}}}
        }
        function popups_fetched_1(th, lth, no, post){
    //      post.parse_funcs = th.parse_funcs; // redundant???  // CAUSED A BUG, parse_funcs are archived and stop its prototype chain.
    //      if (th.type_data==='html') this.popups_fetched_html(post, th); // wrap_to_parse.post and prepare_html_extract_params should be used, but not debugged, left this.
          cataLog.format_html.prepare_html_prep_posts({posts:[post], __proto__:th}) // wrap_to_parse is in this.
//          site2[th.domain].wrap_to_parse.posts({posts:[post], __proto__:th});
          cataLog.format_html.prepare_html_post(th, post, lth.q);
//          site2['DEFAULT'].popups_add_1(th, post, false, lth.q, false); // cut quote link.
    //      this.popups_set(lth.q, no, post); // BUG, this doesn't update quotes, this will be appear in multiple popup mode, use popups_add_1.
    //                                               // In single popup mode, this cut quote links and reduce memory consumption.
    ////      this.popups_set(lth.q, i, (th.type_data==='json')? post : this.popups_fetched_html(post, th)); // BUG, this deletes quote.
        }
        function popups_try_fetch_from_lth_ta(lth, no, th){
          if (lth.ta) for (var i=lth.ta.posts.length-1;i>=0;i--) if (lth.ta.posts[i].no==no) {
            var post = lth.ta.posts[i];
            var th = {domain_html: th.domain_html, lth:lth, type_data:post.type_data || (post.pn? 'html':'json'), posts:[post], __proto__:lth};
            popups_fetched_1(th, lth, no, post);
            return;
          }
        }
        function popups_post_entry(e, domain){
          var th_q = site2[domain].popups_href2th_q(e.target.getAttribute('href'));
          if (!th_q) return;
          var dbt = th_q[2];
  //        if (pref[cataLog.embed_mode].env.event_dynamic && pref[cataLog.embed_mode].env.popup_native && site.nickname===th.domain && pref.test_mode['98']) return;
          var pn;
          var thqp = th_q[0][th_q[1]];
          var isOP = dbt[3] == dbt[2]; // thqp.no == dbt[2]; // == must be used, typeof(dbt[2]) is string.
          var lth = th_q[3];
          var th = {domain:domain, domain_html: pref.catalog.mimic_base_site? site.nickname : domain, posts:[thqp], lth:lth, __proto__:lth};
          if (thqp===undefined && pref.test_mode['80']) { // patch
            th.no = th_q[2][2];
            var boards = ['_IDB/', '_File/'];
            for (var i=0;i<2;i++) {
              th.board = th_q[2][1].slice(0,-1)+boards[i];
              var th_q0 = liveTag.mems[th.domain][th.board] && liveTag.mems[th.domain][th.board][th.no] && liveTag.mems[th.domain][th.board][th.no].q;
              if (th_q0) {
                th_q[0] = th_q0;
                break;
              }
            }
          }
          if (thqp===undefined || Array.isArray(thqp)) {
            popups_try_fetch_from_lth_ta(lth, parseInt(dbt[3],10), th); // may change thqp, th for domain_html(mimic)
            thqp = th_q[0][th_q[1]];
          } else if (thqp && !thqp.pn) popups_fetched_1(th, lth, dbt[2], thqp);
          if (thqp===undefined || Array.isArray(thqp)) { // 'thqp===undefined' for multilevel popups.
            site2[th.domain].popups_post_fetch(th, dbt);
  //          cataLog.scan_init('popup',[th.domain+dbt[1]+((pref.catalog.catalog_json)? 't':'')+dbt[2]], {priority:8});
            pn = site2[th.domain_html].post_json2html({time: Date.now(), com:'Loading...', domain:domain, board:dbt[1], no:dbt[2]}); // board and no is not used now, just a insurance.
            var key = dbt[0]+dbt[1]+dbt[2]+'#'+dbt[3];
            pop_load_req(e, key, pn);
  //          site2['DEFAULT'].popup_info = {node:et, clientX:e.clientX, clientY:e.clientY, key:dbt[0]+dbt[1]+dbt[2]+'#'+dbt[3], func_out:site2['DEFAULT'].popups_posts.pop_down.bind(null,pn)}; //out};
            site2['DEFAULT'].popups_set_waiting(th_q[0],th_q[1]);
          } else {
  //          if (site2['DEFAULT'].popup_info) site2['DEFAULT'].popup_info.func_out();
  //          if (site2['DEFAULT'].popup_info) et.onmouseout(); // BUG at editing, sometimes out is never issued because of being replaced.
  //          if (!thqp.pn || thqp.isOP || thqp.type_data==='html') {
            if (!thqp.pn || isOP && (site.nickname!=='4chan' || site.whereami!=='thread')) {
//            if (!thqp.pn || isOP && !pref.test_mode['105']) { // test
  //          if (!thqp.pn || thqp.isOP || pref.proto.popdown!=='imm') { // BUG, this requires exact extraction of country, country_name or data for images even from html.
              if (!thqp.domain) { // PATCH, use prototype of liveTag.mems.
                var tgt = thqp;
                tgt.domain = th.domain;
                if (tgt.extra_files) for (var i=0;i<tgt.extra_files.length;i++) tgt.extra_files[i].domain = th.domain;
              }
              pn = site2[th.domain_html].post_json2html(thqp);
            } else pn = thqp.pn.cloneNode(true);
            cataLog.format_html.prep_anchor_links(pn, th_q[0]);
            site2[th.domain_html].format_pn(pn, thqp, null, null, th_q[0]);
            post_dotted_if_multi_anchors(pn, e, th);
          }
          gGEH.pns_all_keys.set(pn,dbt[0]+dbt[1]+dbt[2]);
          if (pref.debug_mode['10']) console.log('popup: '+th_q[1]+': '+site2[th.domain].popups_debug(th_q[0]));
          return key? {pn:pn, key:key} : {pn:pn};
        }
        function pop_style(e,pn){
          pn.style = {};
          pn.style.position = 'fixed';
          set_pos(pn,e, true);
  //        pn.style.left = e.clientX + 10 + 'px';
  //        if (!pref.test_mode['25']) pn.style.top  = e.clientY -15 + 'px';
  //        else pn.style.bottom  = window.innerHeight - e.clientY + 20 + 'px';
          pn.style.zIndex = pref[cataLog.embed_mode].popup_zIndex;
          if (site.nickname!=='KC') pn.style.borderStyle = 'solid';
          pn.style.boxShadow = 'rgb(153, 153, 153) 1px 1px 1px';
          if (site.nickname==='4chan') {
            pn.classList.add('preview');
            pn.style.display = 'block';
          }
  //        if (thqp.isOP) site2[th.domain_html].popups_op_func_use(pn,th_q[0], th_q[1]);
          site2[site.nickname].popups_post_pnode(e.target).appendChild(pn);
  //        site2['DEFAULT'].popups_posts.set(et,pn);
  //        site2['DEFAULT'].popup_info = null;
  //        }
          adjust_pos(pn,e);
        }
        gGEH.triage_in2 = out;
        return {
          over: over,
          pop_load: pop_load,
          pop_load_req_delete: pop_load_req_delete,
          inline_post: inline_post,
        };
      })();

      var dnd_mode = false;
      var preview = (function(){
        var up_th = up_timer_factory();
        var on_th = null;
        var on_clg = null;
        function th_over(e, tgt, clg){
          var pf3 = pref[clg.mode].popup3;
          if (pf3.ww==='no' && pf3.wn==='no' && pf3.nw==='no' && pf3.nn==='no') return;
//          var tgt = geh.recSearch_thread(e.target, e.currentTarget);
          if (tgt && !dnd_mode) {
            if (on_th===tgt || up_th.up_timer && up_th.src===tgt) return;
            th_out(e);
            up_th.src = tgt;
            on_clg = clg;
            up_th.over(pf3, e, pop_up_th, tgt);
          } else th_out(e);
        }
        function th_out(e, imm){
          var pn_ex = up_th.out(e);
          if (pn_ex===true) return;
          var tgt = geh.recSearch_thread(e.relatedTarget,e.currentTarget) || on_th;
          if (tgt) out_1_if_exists(tgt,pn_ex,imm);
          on_th = null;
          on_clg = null;
        }
        function pop_up_th(e){
          if (!on_clg) return; // for racing condition.
  //        if (pns.has(up_th.src)) {clear_down_timer(pns.get(up_th.src)); return;}
          var name = gGEH.get_key_recursive(up_th.src, document);
//          var name = up_th.src.name;
//          var pf = pref[cataLog.embed_mode].popup3;
          var pna = pns.get(up_th.src) || [];
          var pf3 = pref[on_clg.mode].popup3;
          if (!pref.test_mode['110']) pop_up_th_1(pf3.ww, e, name, pna, 0, false, false);
          pop_up_th_1(pf3.wn, e, name, pna, 1, false, true);
          pop_up_th_1(pf3.nw, e, name, pna, 2, true, false);
          pop_up_th_1(pf3.nn, e, name, pna, 3, true, true);
          on_th = up_th.src
          up_th.src = null;
        }
        function pop_up_th_1(kind, e, name, pna, idx, narrow_x, narrow_y, kwd, kwd_src){ // kwd for popupID
          if (kind==='no') return;
          if (pna[idx]) {clear_down_timer(pns.get(pna[idx])); return;}
          var pn = document.createElement('div');
          pn.setAttribute('class', pref.cpfx+'window');
  //        pn.style.width = '200px';
  //        pn.style.height = '200px';
          pn.style.position = 'fixed';
  //        pn.style.backgroundColor = 'red';
  //        pn.innerHTML = up_th.src.getAttribute('id');
          pn.style.border = '1px solid blue';
          pn.style.zIndex = pref.proto.popup3.popup_zIndex;
          if (kind!=='chart') if (!on_clg.pop_up_set_contents(pn, kind, name, kwd)) return;
          pn.addEventListener(brwsr.mousewheel, cnst.div_scroll, false);
          set_pos(pn, e, false, narrow_x, narrow_y);
          pna[idx] = site.popup_body.appendChild(pn); // for chart, must be preceded before making chart.
          if (kind==='chart') {
            if (chart_obj && pref3.stats.use) {
              var base = on_clg.merge_bases.bases[name];
              var chart = new chart_obj.PostChart(pn, base && base.pn===up_th.src && base.keys || [name]); // must be after appendChild to draw. (just to call destroy?)
            } else pn.innerHTML = 'Statistics needs to be activated.';
          }
          pn.dataset.isPopup = 'true';
//          if (pref.catalog_popup_size_fix) {
//            pn.style.width  = pn.offsetWidth + 'px';
//            pn.style.height = pn.offsetHeight + 'px';
//          }
          cnst.tack_for_popup(pn, function(e){
//          var btn = document.createElement('button');
////          comf.overwrite_prop(btn.style, {position:'absolute', top:'0px', left:'0px'});
//          btn.classList.add(pref.cpfx+'autoTp');
//          btn.appendChild(site2[site.nickname].make_tack());
//          pn.appendChild(btn);
//          btn.onclick = function(e){
            var btn = e.currentTarget;
            var pn = btn.parentNode;
//            cnst.bottom_top(pn);
            var pn2;
            var chart = pop_down(pn,true).chart;
            if (chart) {
              var tn = document.createElement('div');
              var tn_imgs = site2[comf.name2dbt(name)[0]].parse_funcs['catalog_html'].tn_imgs({pn:cataLog.threads[name][0]});
              if (tn_imgs[0]) tn.appendChild(tn_imgs[0].cloneNode());
//              if (site.nickname==='4chan') tn.appendChild(cataLog.threads[name][0].getElementsByTagName('img')[0].cloneNode());
              else tn.innerHTML = name;
              comf.overwrite_prop(tn.style, {position:'absolute', bottom:'0px', left:'0px', opacity:0.4});
              pn.replaceChild(tn,btn);
              pn2 = chart_obj.PostChart.call(chart, null,null,chart);
              pn2.childNodes[1].replaceChild(pn, pn2.childNodes[1].childNodes[1]);
            } else {
              pn.removeChild(btn);
              pn2 = cnst.tack_for_popup_swap(pn);
//              pn2 = cnst.init3({func_str:'Show:tb'}).pn;
//              pn2.appendChild(pn);
            }
            cnst.tack_for_popup_swap_pos(pn2,pn);
//            pn2.style.top = parseInt(pn.style.top,10) - pn2.offsetHeight + 'px';
//            pn2.style.left = pn.style.left;
//            comf.overwrite_prop(pn.style, {position:'relative', border:null, top:null, left:null});
          });
          if (kwd) pop_setup(kwd_src, pn, {src:kwd_src, scroll:true, pf:pfX});
          else pop_setup(up_th.src, pn, chart? {src:up_th.src, chart:chart, scroll:true, pf:pf} : {src:up_th.src, scroll:true, pf:pf}, pna);
        }
        function pfX(){return pref.proto.popupX;}
        function pf(){return pref.proto.popup3;}
        return {
          th_over, th_over,
          th_out: th_out,
//          init: function(pn){
//            site.root_body.addEventListener('mouseover',th_over,false);
////            pn.addEventListener('mouseover',th_over,false);
//            pn.addEventListener('mouseleave',th_out,false);
//            gGEH.triage_in = th_out; // for triage
//          },
//          destroy: function(pn){
//            site.root_body.removeEventListener('mouseover',th_over,false);
////            pn.removeEventListener('mouseover',th_over,false);
//            pn.removeEventListener('mouseleave',th_out,false);
//          },
          pop_up_th_1: pop_up_th_1, // for popupID
          set on_clg(v){on_clg = v;}, // for popupX
        }
      })();
      var popupSurface = (function(){
        var et = null;
        function popup_make(e, kwd){
          var key = gGEH.get_key_recursive(e.target, e.currentTarget);
          if (!key) return;
          kwd.rexps = comf.kwd_prep_regexp(kwd, true);
          preview.pop_up_th_1('sr', e, key, [], 4, false, false, kwd, e.target);
          et = e.target;
        }
        return {
          X: function(e){
            var pf = pref[cataLog.embed_mode].popupX;
            if (pf.ID) {
              var id = geh.getID(e.target);
              if (id) return popup_make(e, {str:id, id:true});
            }
            if (pf.flag) {
              var country = geh.getCountry(e.target);
              if (country) return popup_make(e, {str:country, flag:true});
            }
            if (pf.name) {
              var name = geh.getName(e.target);
              if (!pref.anon && name==='Anonymous') return;
              if (name) return popup_make(e, {str:name, name:true, sentence:true});
            }
            if (pf.trip) {
              var trip = geh.getTrip(e.target, e);
              if (trip) return popup_make(e, {str:trip, trip:true});
            }
            if (pf.ID2) {
              var id2 = geh.getID2(e.target, e);
              if (id2) return popup_make(e, {str:id2, id2:true, sentence:true}); // for 5ch's wacchoi
            }
          },
          get et(){return et;},
          set et(v){et=v;},
//          ID: function(e){
//            var key = gGEH.get_key_recursive(e.target, e.currentTarget);
//            if (!key) return;
//            var id = geh.getID(e.target);
//            var kwd = {str:id, match:0, id:true, rexps:null};
//            kwd.rexps = comf.kwd_prep_regexp(kwd);
//            preview.pop_up_th_1('sr', e, key, [], 4, false, false, kwd, e.target);
//          },
//          Flag: function(e){
//            var key = gGEH.get_key_recursive(e.target, e.currentTarget);
//            if (!key) return;
//            var country = geh.getCountry(e.target);
//            var kwd = {match:0, flag:true, rexps:[new RegExp(country)]};
//            preview.pop_up_th_1('sr', e, key, [], 4, false, false, kwd, e.target);
//          },
//          Name: function(e){
//            var key = gGEH.get_key_recursive(e.target, e.currentTarget);
//            if (!key) return;
//            var name = geh.getName(e.target);
//            if (!pref.proto.popupName.anon && name==='Anonymous') return;
//            var kwd = {str:name, match:0, name:true, rexps:null};
//            kwd.rexps = comf.kwd_prep_regexp(kwd, true);
//            preview.pop_up_th_1('sr', e, key, [], 4, false, false, kwd, e.target);
//          },
//          Trip: function(e){
//            var key = gGEH.get_key_recursive(e.target, e.currentTarget);
//            if (!key) return;
//            var trip = geh.getTrip(e.target);
//            var kwd = {str:trip, match:0, trip:true, rexps:null};
//            kwd.rexps = comf.kwd_prep_regexp(kwd, true);
//            preview.pop_up_th_1('sr', e, key, [], 4, false, false, kwd, e.target);
//          }
        };
      })();
      var triage = (function(){
        function th_over(e){
          var clg = this['.'];
          var tgt = geh.recSearch_thread(e.target, e.currentTarget, clg.view==='headline'? pref.cpfx+'headline' : undefined);
          if (/*!pref.test_mode['127'] &&*/ (clg.mode==='catalog' || clg.mode==='float')) {
            if (tgt) preview.th_over(e,tgt, clg);
            else preview.th_out(e);
          } else preview.on_clg = clg;
          if (tgt) {
            var name = gGEH.pns_all_keys.get(tgt) || gGEH.pns_all_keys.get(tgt.parentNode); // tgt.parentNode for vichan catalog
            if (name) return cataLog.triage.thread_in(e, name, tgt, clg);
//            if (name) return cataLog.triage.thread_in(tgt, name,
//                                                      gClg.Clgs.length>=2 && geh.recSearch_thread(tgt, e.currentTarget, pref.cpfx+'catalog') || pClg.ppn);
          }
          cataLog.triage.thread_out2(e); // onTriage_or_off(e);
        }
        function th_out(e){
          var clg = this['.'];
          if (/*!pref.test_mode['127'] &&*/ clg.mode==='catalog' || clg.mode==='float') preview.th_out(e);
          else preview.on_clg = null;
          cataLog.triage.thread_out2(e);
        }
        return {
          init: function(pn, obj){
            pn.addEventListener('mouseover',th_over.bind(obj),false);
            pn.addEventListener('mouseleave',th_out.bind(obj),false);
          },
//          init: function(pn){
//            site.root_body.addEventListener('mouseover',th_over,false);
//            gGEH.triage_in = preview.th_out; // for triage
//          },
          destroy: function(pn){site.root_body.removeEventListener('mouseover',th_over,false);},
          th_over: th_over,
        }
      })();
      return {
        over: over,
        leave: function(){up0.stop; up1.stop();},
        pop_load: posts.pop_load,
        blacklist: blacklist,
        set_pos: set_pos, // to be removed.
        href2domain: href2domain,
        init: function(pn){
          geh = site2[site.nickname].general_event_handler[site.whereami];
//          if (!pref.test_mode['110'] && cataLog.embed_mode==='catalog') preview.init(pn);
          gGEH.triage_in = preview.th_out; // for triage
        },
        init2: function(pn, obj){
          /*if (!pref.test_mode['127'])*/ triage.init(pn, obj);
        },
        inline_post: posts.inline_post,
        menu_click: triage.th_over,
        pop_up_unlock: function(){dnd_mode = false;},
        pop_down_temporarily: function(e, lock){
          preview.th_out(e, true);
          if (lock) dnd_mode = true;
        },
      };
    })(),
    popups_posts_class_hlt: 'highlighted',
//    popups_op_func_set: function(){return 1;}, // vichan
//    popups_op_func_use: function(pn,thq,no){ // vichan
//      pn.setAttribute('class',pn.getAttribute('class')+' reply');
////      pn.insertBefore(thq[no].pn.parentNode.querySelector('.files').cloneNode(true),pn.firstChild);
//    },
    popups_post_fetch: function(th, dbt){
      cataLog.scan_init('popup',[th.domain+dbt[1]+((pref.catalog.catalog_json)? 't':'')+dbt[2]], {priority:8});
    },
//    popup_info:null,
//    popups_posts: new WeakMap(),
    popups_post_pnode: function(pnode){
      if (pref.test_mode['107']) return site.popup_body;
      if (!pref.test_mode['106']) {
        var p = pnode.parentNode;
        while (p) if (p===cataLog.parent) return site.popup_body; else p = p.parentNode;
      }
      var attr;
      while (attr = pnode.getAttribute('class'), (!attr || (attr.indexOf('post')==-1 && attr.indexOf('thread')==-1)) && pnode.parentNode) pnode = pnode.parentNode;
      return pnode;
    },
    
//    popups_href2dbtp: function(href, src, th){
//      if (href[0]==='#') {
//        href = th.board+'res/'+th.no+'.html'+href;
//        src.setAttribute('href',href);
//      }
//      var hrefs = href.split('/');
//      var p = hrefs[hrefs.length-1].substr(hrefs[hrefs.length-1].indexOf('#')+1);
//      var t = hrefs[hrefs.length-1].substr(0,hrefs[hrefs.length-1].indexOf('.'));
//      var b = (hrefs.length>=3)? '/'+hrefs[hrefs.length-3]+'/' : site.board;
//      var d = this.nickname;
//      return [d,b,t,p];
//    },
    popups_href2th_q: function(href){ //,src,th){
      var dbt = this.popups_href2dbtp(href); // ,src,th);
//      var th = liveTag.mems[d][b][t];
      if (!dbt[0] || !dbt[1] || !dbt[2]) return null; // hit this line when dbt[2]==='' caused by '>>>/a/'
      var lth = liveTag.mems.init({domain:dbt[0], board:dbt[1], no:parseInt(dbt[2],10)}); // may be first time if the llink is cross-link.
      if (lth.q===undefined) lth.q = (lth.no>1000000)? Object.create(lth) : Object.create({0:undefined, 1:undefined, 2:undefined, __proto__:lth}); // PATCH before removing [0:2] // crosslink, CYCLIC REFERENCE.
//      if (!liveTag.mems[dbt[0]][dbt[1]][dbt[2]] || !liveTag.mems[dbt[0]][dbt[1]][dbt[2]].q) return null; // patch for accessibility check. 'if (!th)' doesn't work. // WHY??? // HIT THIS LINE WHEN dbt[2]===''.
      return [lth.q, parseInt(dbt[3],10), dbt, lth];
    },
////    popups_add: function(posts, posts_old, th){ // working code.
////      if (!posts_old) posts_old = [];
////      var th_q0 = liveTag.mems[th.domain][th.board][th.no];
////      if (th_q0.q===undefined) th_q0.q = {};
////      var thq = th_q0.q;
////      if (posts) for (var i=0;i<posts.length;i++) Object.defineProperty(posts[i],'no',{value:th.parse_funcs.no(posts[i])});
////      if (posts && posts.length!=0) {
//////        for (var i=0;i<posts.length;i++) Object.defineProperty(posts[i],'no',{value:parseInt(posts[i].pn.getElementsByClassName('post_no')[1].textContent,10)});
//////        if (posts_old.length!=0) while (posts_old[0].no<posts[0].no) this.popups_release(posts_old.shift()); // [0] is OP
////        if (posts.length>1) while (posts_old.length>1 && posts_old[1].no<posts[1].no) this.popups_release(posts_old.splice(1,1)[0]);
////      }
////      if (posts) {
////        for (var i=0;i<posts.length;i++) {
////          var flag_old = posts_old.length!=0 && posts_old[posts_old.length-1].no>posts[i].no;
////          var tgts = [];
////          var as = posts[i].pn.getElementsByTagName('a');
////          for (var j=0;j<as.length;j++)
////            if (as[j].textContent.search(/>>[0-9]+$/)!=-1) {
////              as[j].onmouseover = this.popups_post_entry;
////              if (!flag_old) {
////                var th_q = site2[th.domain].popups_href2th_q(as[j].getAttribute('href'),as[j],th);
////                if (th_q) {
////                  tgts[tgts.length] = as[j].getAttribute('href');
////                  this.popups_grep(th_q[0],th_q[1]);
////                }
////              }
////            }
////          if (!flag_old) {
////            posts_old[posts_old.length] = (tgts.length!==0)? {no:posts[i].no, tgts:tgts, thq:thq} : {no:posts[i].no, thq:thq};
////            this.popups_grep(thq,posts[i].no);
////            this.popups_set(thq,posts[i].no,posts[i]);
////            if (i===0) thq[posts[i].no].isOP = 1;
////          }
////        }
////      } else while (posts_old.length>1) this.popups_release(posts_old.shift());
////      if (pref.debug_mode['10']) {
////        var d_str = '';
////        for (var i in thq) d_str += i+':'+((thq[i].reffered)? thq[i].reffered : thq[i])+', ';
////        console.log('popups_add :'+th.key+': '+d_str);
////      }
////      return posts_old;
////    },
////    popups_grep: function(thq, no){
////      if (thq[no]===undefined) thq[no] = 1; // waiting to be filled, but not make requests.
////      else if (typeof(thq[no])==='number') thq[no] = thq[no] + ((thq[no]<0)? -1 : 1);
////      else ++thq[no].reffered;
////    },
////    popups_set: function(thq, no, val){
////      if (typeof(thq[no])==='number') {
////        thq[no] = val;
////        thq[no].reffered = (val_old>=0)? val_old : -val_old;
////      }
////    },
////    popups_release_1: function(th_q){
////      if (th_q) {
////        if (typeof(th_q[0][th_q[1]])==='number') {
////          th_q[0][th_q[1]] -= (th_q[0][th_q[1]]<0)? -1 : 1;
////          if (th_q[0][th_q[1]]===0) delete th_q[0][th_q[1]];
////        } else if (--th_q[0][th_q[1]].reffered===0) delete th_q[0][th_q[1]];
////      }
////    },
////    popups_release: function(post){
////      if (post.tgts)
////        for (var i=0;i<post.tgts.length;i++)
////          this.popups_release_1(site2[site.nickname].popups_href2th_q(post.tgts[i])); // TEMPORAL
////      this.popups_release_1([post.thq, post.no]);
////      if (pref.debug_mode['10']) {
////        var d_str = '';
////        for (var i in thq) d_str += i+':'+((thq[i].reffered)? thq[i].reffered : thq[i])+', ';
////        console.log('popups_release :'+post.no+': '+d_str);
////       }
////    },
    popups_prep: function(th, lth){
      if (!lth) lth = liveTag.mems[th.domain][th.board][th.no];
      if (lth.q===undefined) lth.q = ( th.no>1000000)? Object.create(lth) : Object.create({0:undefined, 1:undefined, 2:undefined, __proto__:lth}); // PATCH before removing [0:2] // usual, lth.q is made in popups_href2th_q if crosslink. CYCLIC REFERENCE.
    },
//    popups_add: function(posts, th, release){
////    popups_add: function(posts, th, deactivate, from_native, release){
//      var lth = liveTag.mems[th.domain][th.board][th.no];
////      if (lth.q===undefined) lth.q = ( th.no>1000000)? Object.create(lth) : Object.create({0:undefined, 1:undefined, 2:undefined, __proto__:lth}); // PATCH before removing [0:2] // usual, lth.q is made in popups_href2th_q if crosslink. CYCLIC REFERENCE.
//      if (posts) if (th.type_data==='html') {for (var i=0;i<posts.length;i++) if (!posts[i].no) Object.defineProperty(posts[i],'no',{value:th.parse_funcs.no(posts[i])});}
////                 else for (var i=0;i<posts.length;i++) if (!posts[i].pn) posts[i].pn = this.post_json2html(posts[i],th.board);
//      var posts_exist = {};
//      if (posts) {
//        for (var i=0;i<posts.length;i++) {
//          var post_no = posts[i].no;
//          posts_exist[post_no] = null;
////          if (lth.q[post_no]===undefined || Array.isArray(lth.q[post_no])) this.popups_add_1(th, posts[i], deactivate, lth.q, true, from_native); // may cause multiple entry because lth.q[post_no] will be deleted even if it is shown when the post is not reffered from other posts of the same thread but there is a cross link and the post of the cross link is changed to not shown. This invokes emurational recursive call of popups_release to delete lth.q[post_no]. But this is OK because popups_add_1 admits multiple entry, you can call it any times.
//        }
//      }
//      if (!pref[cataLog.embed_mode].backlink_all && (release || pref.test_mode['125'])) this.popups_release_all(lth, posts_exist);
//    },
    popups_release_all_not_exist: function(lth, posts){
      if (!lth.q) return;
      var nos = {};
      if (posts) for (var i=0;i<posts.length;i++) nos[posts[i].no]=null;
      var lth_q_ps = Object.keys(lth.q);
      for (var i=0;i<lth_q_ps.length;i++)
        if (nos[lth_q_ps[i]]===undefined && lth_q_ps[i]!=='waiting') this.popups_release(lth, lth_q_ps[i]);
    },
//    popups_release_all: function(lth, posts_exist){
//      if (!lth.q) return;
//      var lth_q_ps = Object.keys(lth.q);
//      for (var i=0;i<lth_q_ps.length;i++)
//        if (posts_exist[lth_q_ps[i]]===undefined && lth_q_ps[i]!=='waiting') this.popups_release(lth, lth_q_ps[i]);
//    },
//    popups_add: function(tgt_th16, th, deactivate, from_native){ // working code.
//      var posts = tgt_th16.posts;
//      if (!tgt_th16.popups) tgt_th16.popups = Object.create(null);
//      var popups = tgt_th16.popups;
//      var lth = liveTag.mems[th.domain][th.board][th.no];
//      if (lth.q===undefined) lth.q = ( th.no>1000000)? Object.create(lth) : Object.create({0:undefined, 1:undefined, 2:undefined, __proto__:lth}); // PATCH before removing [0:2] // usual, lth.q is made in popups_href2th_q if crosslink. CYCLIC REFERENCE.
//      if (posts) if (th.type_data==='html') {for (var i=0;i<posts.length;i++) if (!posts[i].no) Object.defineProperty(posts[i],'no',{value:th.parse_funcs.no(posts[i])});}
////                 else for (var i=0;i<posts.length;i++) if (!posts[i].pn) posts[i].pn = this.post_json2html(posts[i],th.board);
//      var posts_exist = {};
//      if (posts) {
//        for (var i=0;i<posts.length;i++) {
//          var post_no = posts[i].no;
//          posts_exist[post_no] = null;
//          if (popups[post_no]===undefined) {
//            this.popups_add_1(th, posts[i], deactivate, lth.q, true, from_native);
//            popups[post_no] = null;
//          }
//        }
//      }
//      if (!pref.filter.kwd.posts_active) for (var i in popups) {
//        if (posts_exist[i]===undefined) {
////          this.popups_release(popups[i], i, th.key); // BUG???
//          this.popups_release(lth, i);
//          delete popups[i];
//        }
//      }
////      if (posts) site2[th.domain_html].format_pn(posts[0].pn, lth.q[th.no]); // PROBABLY THIS IS REDUNDANT, BUT NOT DEBUGGED.
////      if (pref.debug_mode['10']) this.popups_debug('popups_add: '+th.no+': ', lth.q);
//    },
    popups_add_1_cross: function(th, posts, cross){ // called before wrapped
      for (var j=0;j<posts.length;j++) {
        var hrefs = th.parse_funcs.hrefs(posts[j]);
        if (hrefs) for (var i=0;i<hrefs.length;i++) {
          if (hrefs[i][0]==='#') continue;
          if (!cross && hrefs[i].indexOf(th.board)!==0) continue;
          var th_q = site2[th.domain].popups_href2th_q(hrefs[i]);
          if (th_q) {
            var skey = th_q[2][0]!==th.domain? th.key : th_q[2][1]!==th.board? th.board+th.no : th_q[2][2]!=th.no? th.no : null;
            if (skey!==null) site2[th.domain_html].popups_add_backlink(th_q[0],th_q[1], skey+'#'+posts[j].no, th, false, false);
          }
        }
      }
    },
//        var link_regex = />>[0-9]+$|>>>\/[0-z_]+\/[0-9]+$/;
//        var link_regex = />>(>\/.+\/)*[0-9]+$/;
//    popups_link_regex: />>(>\/.+\/)*[0-9]+(\s\(You\))*$/, // patch for '(You)'
    popups_link_regex: /^>>(>\/[^/]+\/)*[0-9]+/, // patch for '(You)' and '(OP)'
    popups_add_1: function(th, post, deactivate, thq, from_not_popup, add_backlink){ // must be able to accept multiple calls, this is called at every appearance/dissapearance.
//      if (!post.pn) post.pn = site2[th.domain_html].post_json2html(post,th.board);
      var as = Array.prototype.slice.call(site2[th.domain_html].post_pn2ce(post.pn).getElementsByTagName('a')); // static array for adding '<a>Merge</a>' later.
      if (deactivate && pref[cataLog.embed_mode].env.backlink_native) {
        var nof_flinks = as.length;
        var bks = site2[th.domain_html].add_backlinks_bks_query(post.pn);
        if (bks) as = as.concat(Array.prototype.slice.call(bks.getElementsByTagName('a'))); // direct call of concat causes error.
      }
      if (as.length>0) {
//        var op_regexp = new RegExp('^>>'+th.no+'(\\s|\\(|$)');
        var quotes = [];
        for (var j=0;j<as.length;j++) {
  //        if (as[j].textContent.search(/>>[0-9]+$/)!=-1) {
          var as_txt = as[j].textContent;
          if (as_txt.search(this.popups_link_regex)!=-1) {
            if (deactivate) {
              if (pref[cataLog.embed_mode].env.popup_native_kill) {
                var as_new = as[j].cloneNode(true);
                as[j].parentNode.replaceChild(as_new,as[j]);
                as[j] = as_new;
              } else this.popups_posts.blacklist.add(as[j]);
              if (j>=nof_flinks) continue;
            }
//            if (activate) as[j].onmouseover = this.popups_post_entry;
            var th_q = site2[th.domain].popups_href2th_q(as[j].getAttribute('href')); // ,as[j],th); // this may change href, so this must be here. <- FIXED.
  //          var a_class = as[j].parentNode.getAttribute('class');
  //          if (!a_class || a_class.indexOf(site2[th.domain_html].backlink_class)==-1) { // skip backlinks
            if (th_q) {
              if (pref[cataLog.embed_mode].link_show_op && th_q[2][2]==th_q[2][3] && as_txt.indexOf('(OP)')==-1) as[j].textContent += ' (OP)'; // th_q[2][3] is string
//              if (pref[cataLog.embed_mode].link_show_op && as_txt.search(op_regexp)===0 && as_txt.indexOf('(OP)')==-1) as[j].textContent += ' (OP)';
              if (th_q[0]!==thq) {
                if (pref[cataLog.embed_mode].link_show_cross && as_txt.indexOf('\u2192')===-1) as[j].textContent += ' \u2192';
                if (pref[cataLog.embed_mode].merge_btn.add) {
                  var pn_mg = Triage.prototype.doms.emMerge.cloneNode(true);
                  gGEH.pns_all_keys.set(pn_mg,th.key);
                  if (th.no!==post.no) pn_mg.dataset.cmd = 'MERGETC';
                  as[j].parentNode.insertBefore(pn_mg,as[j].nextSibling);
                }
//                if (pref[cataLog.embed_mode].merge_btn) cnst.doms_insertBefore(as[j].parentNode, ' [<a href="javascript:;" class="'+pref.cpfx+'UImerge">Merge</a>]',as[j].nextSibling);
              }
              quotes[quotes.length] = [th_q[0], th_q[1]]; // quotes may be multiple.
//              quotes[quotes.length] = [thq, th_q[1]]; // quotes may be multiple.  // use 'thq' instead of 'th_q[0]' for test_mode['80'] // BUG. MUST USE th_q[0]
              var skey = th_q[2][0]!==th.domain? th.key : th_q[2][1]!==th.board? th.board+th.no : th_q[2][2]!=th.no? th.no : null;
              site2[th.domain_html].popups_add_backlink(th_q[0],th_q[1], skey!==null? skey+'#'+post.no : post.no, th, from_not_popup, add_backlink, from_not_popup);
//              site2[th.domain_html].popups_add_backlink(thq,th_q[1], skey!==null? skey+'#'+post.no : post.no, th, dig);
            }
  //          }
          }
        }
      }
      this.popups_set(thq,post.no,post, quotes && quotes.length!=0 && quotes, from_not_popup);
//      if (op) thq[post.no].isOP = site2[th.domain_html].popups_op_func_set(post.pn);
    },
    popups_add_backlink: function(thq, no, key, th, dig, add_backlink, from_not_popup){
      if (thq[no]===undefined) {
        thq[no] = [key];
        if (dig) this.popups_set_waiting(thq,no);
      } else if (Array.isArray(thq[no])) {
        if (thq[no].indexOf(key)==-1) {
          thq[no][thq[no].length] = key;
          if (dig) this.popups_set_waiting(thq,no); // may not waiting because of 'dig'.
        }
      } else {
        if (!thq[no].backlinks) thq[no].backlinks = [];
        var blks = thq[no].backlinks;
        if (blks.indexOf(key)==-1) {
          blks[blks.length] = key;
          if (add_backlink) { // pref[cataLog.embed_mode].backlink && (!pref[cataLog.embed_mode].env.backlink_native || !from_native)) {
            if (thq[no].pn) site2[th.domain_html].add_backlinks(thq[no].pn,  blks, blks.length-1, th);
            var pn = !from_not_popup && (th.posts.filter(function(v){return v.no===no;})[0]||{}).pn; // test patch for popup at first time, but slow
            if (pn) site2[th.domain_html].add_backlinks(pn, blks, blks.length-1, th);
          }
        }
      }
    },
    add_backlinks: function(pn,backlinks,target, th){ // this refers site2[domain_html]
      var bks_pn = this.add_backlinks_bks_query(pn);
      var bks = bks_pn || this.add_backlinks_bks();
      if (!target) bks.innerHTML = ''; // this hits target===0 also and clean up.
      for (var i=(target || 0);i<backlinks.length;i++) {
        this.add_backlinks_add_1(bks, this.popups_backlink2dbtpth(backlinks[i], th));
        if (target) break;
      }
      if (!bks_pn) this.add_backlinks_bks_append(pn, bks);
    },
    popups_backlink2dbtpth: function(backlink, th){
      var dbtp;
      var dbtpth = (typeof(backlink)==='number')? [th.domain, th.board, th.no, backlink, '>>'+backlink]
          : (dbtp = backlink.split(/[\/#]/),
             dbtp.length==2? [th.domain, th.board, dbtp[0], dbtp[1], '>>'+(pref.proto.bl_ec? dbtp[0]+'#':'')+dbtp[1]]
                           : [dbtp[0] || th.domain, '/'+dbtp[1]+'/', dbtp[2], dbtp[3], (dbtp[0]? '>>>>'+dbtp[0]:'>>>')+'/'+dbtp[1]+'/'+(pref.proto.bl_ec? dbtp[2]+'#':'')+dbtp[3]]);
      dbtpth[5] = site2[dbtpth[0]].link_dbtp2href_abs(dbtpth); // don't use 'this', 'this' refers site2[domain_html], not site2[domain].
      return dbtpth;
    },
    remove_backlink: function(pn,idx){}, // dummy
    popups_remove_backlink: function(thq, no, key){
      if (!thq[no]) return; // 'quotes' may be multiple, then hit this line.
      var ary = (Array.isArray(thq[no]))? thq[no] : thq[no].backlinks;
      var idx = ary.indexOf(key);
      if (idx>=0) {
        ary.splice(idx,1);
        if (thq.no===no && pref.proto.bl_rm) if (thq[no]) this.remove_backlink(thq[no].pn, idx); // remove op only for faster execution. // includes cross link
//        if (ary.length===0) return true; // prevent from infinite loop by reference loop of anchors.
      }
////      else console.log('ERROR in handling popups ' + no+', '+key+', '+thq[no].backlinks); // quotes allows multiple, but backlink doesn't, deletion of a post which has multiple links to a post hits this line.
////      if (ary.length===0 && thq[no].remove_if_no_backlinks) delete thq[no];
////      if (pref.debug_mode['10']) console.log('popups_remove_backlink: '+(idx>=0?'suc':'fail')+', '+no+'<-'+key+':    '+this.popups_debug(thq));
////      if (ary.length===0) return true;
    },
    popups_set: function(thq, no, val, quotes, from_not_popup){
      if (no<=2) if (!Object.hasOwnProperty.call(thq,no)) thq[no] = undefined; // PATCH before renaming, now thq has prototype, [0][1] or [2] returns tags or watch.
      if (thq[no]===undefined) {
        if (val.backlinks) val.backlinks = undefined; // PATCH FOR MEGUCA, meguca has backlinks in native.
        thq[no] = val;
      } else if (Array.isArray(thq[no])) {
        val.backlinks = thq[no];
        thq[no] = val;
      } else { // for multiple entry at editing===true, update thq[no].
        if (thq[no].backlinks) val.backlinks = thq[no].backlinks;
        if (from_not_popup) thq[no] = val; // BUG, keep lth.q for backlinks, but this can track only one instance. Bug when there are multiple thread/page views.
      }
//      thq[no] = val;
      if (quotes) thq[no].quotes = quotes;
      if (val.editing) this.popups_set_waiting(thq,no); // keep updating while editing by 'popups_fetched'
      this.popups_remove_waiting(thq,no);
    },
    popups_release: function(lth, no){ // in-place, on-demand ver.
      var quotes = [];
      var i=0;
      if (pref.debug_mode['12']) var initial_cond = {no:no, lth:lth, len:debug_len(lth)};
      prep_quotes.call(this, lth, no);
      while (--i>=0) {
        var tgt = quotes[i];
        lth = tgt[0];
        no  = tgt[1];
        if (tgt.length==3) {
          var q = tgt[2][0];
          var p = tgt[2][1];
          var skey = q.domain!==lth.domain? lth.key : q.board!==lth.board? lth.board+lth.no : q.no!=lth.no? lth.no : null;
          this.popups_remove_backlink(q, p, skey!==null? skey+'#'+no : parseInt(no,10));
          prep_quotes.call(this, Object.getPrototypeOf(q), p); // emulates recursive call // overwrite for in-place
        } else delete lth.q[no];
//        } else {
//          if (lth.no!=no && lth.q && lth.q[no])
//            if (!lth.q[no].backlinks || lth.q[no].backlinks.length===0) delete lth.q[no];
////            else if (lth.q[no].quote) lth.q[no].quote = null; // nullify for strict accordance, but redundant.
////          if (pref.debug_mode['10']) console.log('popups_release: '+no+':    '+this.popups_debug(lth.q));
//        }
      }
      if (pref.debug_mode['12']) {
        var len = debug_len(initial_cond.lth);
        if (len!==initial_cond.len) console.log('popups_release: '+initial_cond.lth.key+': '+len+' <- '+initial_cond.len+', '+initial_cond.no+
                                                ((pref.debug_mode['10'])? '    '+this.popups_debug(lth.q):''));}
      function prep_quotes(lth, no){
        if (lth.no!=no && lth.q && lth.q[no]) { // lth.q is nullfied when the thread was pruned.
          var ary = (Array.isArray(lth.q[no]))? lth.q[no] : lth.q[no].backlinks;
          if (!ary || ary.length===0) {
            this.popups_remove_waiting(lth.q,no);
            quotes[i++] = [lth, no];
            var qts = lth.q && lth.q[no] && lth.q[no].quotes; // lth may refer archived thread, which never have loaded.
            if (qts) for (var j=qts.length-1;j>=0;j--) quotes[i++] = [lth, no, qts[j]];
          }
        }
      }
      function debug_len(lth){
        var nos = Object.keys(lth.q);
        return nos.length - (nos.indexOf('waiting')==-1? 0:1);
      }
    },
//    popups_release: function(lth, no){ // in-place, on-demand ver. // working code.
//      var quotes = [];
//      var i=0;
//      if (pref.debug_mode['12']) var initial_cond = {no:no, lth:lth, len:Object.keys(lth.q).length};
//      prep_quotes(lth, no);
//      while (--i>=0) {
//        var tgt = quotes[i];
//        lth = tgt[0];
//        no  = tgt[1];
//        if (tgt.length==4) {
//          var q = tgt[2];
//          var p = tgt[3];
//          var skey = q.domain!==lth.domain? lth.key : q.board!==lth.board? lth.board+lth.no : q.no!=lth.no? lth.no : null;
//          if (this.popups_remove_backlink(q, p, skey!==null? skey+'#'+no : parseInt(no,10))) { // only true if removed successfully, this prevents infinite loop by reference loop.
//            this.popups_remove_waiting(q,p);
//            prep_quotes(Object.getPrototypeOf(q), p);// emulates recursive call // overwrite for in-place
//          }
//        } else {
//          if (lth.no!=no && lth.q && lth.q[no])
//            if (!lth.q[no].backlinks || lth.q[no].backlinks.length===0) delete lth.q[no];
////            else if (lth.q[no].quote) lth.q[no].quote = null; // nullify for strict accordance, but redundant.
////          if (pref.debug_mode['10']) console.log('popups_release: '+no+':    '+this.popups_debug(lth.q));
//        }
//      }
//      if (pref.debug_mode['12']) console.log('popups_release: '+initial_cond.lth.key+': '+Object.keys(initial_cond.lth.q).length+' <- '+initial_cond.len+', '+initial_cond.no+
//                                             ((pref.debug_mode['10'])? '    '+this.popups_debug(lth.q):''));
//      function prep_quotes(lth, no){
//        quotes[i++] = [lth, no];
//        var qts = lth.q && lth.q[no] && lth.q[no].quotes; // lth may refer archived thread, which never have loaded.
//        if (qts) for (var j=qts.length-1;j>=0;j--) quotes[i++] = [lth, no, qts[j][0], qts[j][1]];
//      }
//    },
//    popups_release: function(lth, no){ // working code. loop ver.
//      var quotes = format_quotes(lth, no, lth.q[no].quotes);
//      while (quotes.length>0) {
//        var tgt = quotes.shift();
//        lth = tgt[0];
//        no  = tgt[1];
//        if (tgt.length==4) {
//          var q = tgt[2];
//          var p = tgt[3];
//          var skey = q.domain!==lth.domain? lth.key : q.board!==lth.board? lth.board+lth.no : q.no!=lth.no? lth.no : null;
//          if (this.popups_remove_backlink(q, p, skey? skey+'#'+no : parseInt(no,10))) {
//            this.popups_remove_waiting(q,p);
//            var lth_next = Object.getPrototypeOf(q);
//            quotes = format_quotes(lth_next, p, lth_next.q[p].quotes).concat(quotes); // emulates recursive call
//            lth_next.q[p].quotes = null; // prevent from infinite loop by reference loop of anchors.
//          }
//        } else {
//          if (lth.no!=no && (!lth.q[no].backlinks || lth.q[no].backlinks.length===0)) delete lth.q[no];
//          if (pref.debug_mode['10']) console.log('popups_release: '+no+':    '+this.popups_debug(lth.q));
//        }
//      }
//      function format_quotes(lth,no, quotes){
//        return (quotes)? quotes.map(function(v){return [lth, no].concat(v);}).concat([[lth, no]]) : [[lth, no]];
//      }
//    },
////    popups_release_list: [], // DOESN'T WORK, leaves must be removed first, or lost reference and get too complicated procedures.
////    popups_release_recursive: function(){ // avoid recursive calls
////      while (this.popups_release_list.length>0) {
////        var tgt = this.popups_release_list.pop();
////        this.popups_release(tgt[0], tgt[1], tgt[2]);
////      }
////    },
//    popups_release: function(lth, no){ // working code // , thq_no_in){
//      var thq = lth.q;
//      var thq_no = thq[no]; //  || thq_no_in;
//      if (thq_no.quotes)
//        for (var i=0;i<thq_no.quotes.length;i++) {
//          var q = thq_no.quotes[i][0];
//          var p = thq_no.quotes[i][1];
//          var skey = q.domain!==lth.domain? lth.key : q.board!==lth.board? lth.board+lth.no : q.no!=lth.no? lth.no : null;
//          if (this.popups_remove_backlink(q, p, skey? skey+'#'+no : parseInt(no,10))) {
//            this.popups_remove_waiting(q,p);
////            this.popups_release_list.push([Object.getPrototypeOf(q), p, q[p]]); // DOESN'T WORK
//            try { 
//              this.popups_release(Object.getPrototypeOf(q), p); // RECURSIVE CALLS MAY CAUSE STACKOVERFLOW.
//            } catch(e){
//              setTimeout(this.popups_release.bind(this,Object.getPrototypeOf(q), p),0);
//            }
//          }
//        }
//      if (lth.no!=no && (!thq_no.backlinks || thq_no.backlinks.length===0)) delete thq[no];
////      else thq_no.remove_if_no_backlinks = 1;
//      if (pref.debug_mode['10']) console.log('popups_release: '+no+':    '+this.popups_debug(lth.q));
////      if (!thq_no_in && this.popups_release_list.length>0) this.popups_release_recursive();
//    },
    popups_set_waiting: function(thq,no){
      if (!thq.waiting) thq.waiting = [no];
      else {
        var i=0;
        while (i<thq.waiting.length && thq.waiting[i]<no) i++;
        if (thq.waiting[i]!=no) thq.waiting.splice(i,0,no);
      }
    },
    popups_remove_waiting: function(thq,no){
      if (!thq.waiting) return;
      var idx = thq.waiting.indexOf(no);
      if (idx!=-1) {
        thq.waiting.splice(idx,1);
        if (thq.waiting.length===0) delete thq.waiting;
      }
    },
    popups_fetched: function(th, lth, start){ // 'if (lth.q && lth.q.waiting)' is checked by caller.
      if (!start) start = 0;
      var j=th.posts.length-1;
      for (var i=lth.q.waiting.length-1;i>=0;i--) {
        var no = lth.q.waiting[i];
        while (j>start && no<th.posts[j].no) j--;
        if (no===th.posts[j].no) this.popups_set(lth.q, no, th.posts[j]); // this.popups_fetched_1(th, lth, no, th.posts[j]);
        else if (th.type_source==='thread') {
          if (pref.test_mode['64']) this.popups_set(lth.q, no, {time: 0, com:'DELETED'});
          else this.popups_set(lth.q, no, {time: 0, com:'DELETED'}); // referring deleted posts should be here.
        } // else if (j===start) break;
        this.popups_posts.pop_load(th.key+'#'+th.posts[j].no, th.domain);
//        if (this.popup_info && this.popup_info.key===th.key+'#'+th.posts[j].no) this.popups_posts.pop_make({target:this.popup_info.node, __proto__:this.popup_info}, th.domain);
      }
    },
//    popups_fetched_html: function(post, th){ // PATCH, WILL BE REMOVED.
////      if (th.domain!==site.nickname && pref.catalog.mimic_base_site && th.domain_html!==site.nickname) {
////        var post_new = comf.shallow_copy_1(post); // MAY COLLIDE???
//
////        site2[th.domain].wrap_to_parse_posts(th); // TEMPORAL
//        var proto_obj = {domain:th.domain, board:th.board, domain_html:th.domain_html, parse_funcs:th.parse_funcs};
//        if (post.extra_files) for (var j=0;j<post.extra_files.length;j++) for (var k in proto_obj) post.extra_files[j][k] = proto_obj[k];
//        for (var k in proto_obj) post[k] = proto_obj[k];
//
////        if (th.parse_funcs.posts_full) th.parse_funcs.posts_full(th);
//        if (post.parse_funcs.filename) post.filename = post.parse_funcs.filename(post);
////        if (th.parse_funcs.prep_mimic) th.parse_funcs.prep_mimic(th);
//
//        var dummy = post.time; // invoke getter
//        dummy = post.sub;
//        dummy = post.com;
//        dummy = post.name;
//        dummy = post.no;
//        dummy = post.op_img_url;
//        post.type_data = th.type_data;
////      }
//      return post;
//    },
    backlink: function(pn,thq_no, th){
      var backlinks = (thq_no)? ((Array.isArray(thq_no))? thq_no : thq_no.backlinks) : null;
      if (backlinks) this.add_backlinks(pn,backlinks, undefined, th);
    },

    popups_debug: function(thq){
      if (!thq) return '';
      var keys = Object.keys(thq);
      var d_str = keys.length+', '+thq.key+': ';
      for (var j=0;j<keys.length;j++) {
        var i = keys[j];
        d_str += i+':'+((Array.isArray(thq[i]))? thq[i] : (thq[i].backlinks || thq[i].remove_if_no_backlinks || 'P'))+', ';
      }
      return d_str;
    },
    toplevel_anchor_pos:1,
    toplevel_anchor: function(pn, th){
      var as = pn.getElementsByTagName('a');
      for (var i=0;i<as.length;i++) {
        var href = as[i].getAttribute('href');
        if (href && href[0]==='#') as[i].setAttribute('href',this.link_dbtp2href([th.domain, th.board, th.no, href.substr(this.toplevel_anchor_pos)]));
      }
    },

    wrap_to_parse: (function(){
      var th_regexp = /[A-z]/;
//      var parse_objs = {};
      var key_table = { // to reduce memory consumption.
        catalog: 'c',
        page:    'i',
        thread:  't',
        post:    'p',
        shortCatalog: 'b',
        shortThread:  's',
        html:    'h',
        json:    'j',
      };
      function get_getters(html){
//        if (pref.test_mode['6']) return site4.parse_funcs_no_cache; // no cache parse to reduce memory consumption.
//        if (pref.test_mode['6']) return site4.parse_funcs_one_time; // one time parse for faster execution.
        return html? site4.parse_funcs_html : (pref.debug_mode.parse_error)? site4.parse_funcs_on_demand_debug : site4.parse_funcs_on_demand;
//        var obj = (pref.debug_mode.parse_error)? site4.parse_funcs_on_demand_debug : site4.parse_funcs_on_demand;
//        if (pref.test_mode['1'] && parse_funcs){ // test of static getters '&& parse_funcs' is a patch for test. // required to change 'get_getters(parse_funcs){'
//          var obj2 = {};
//          var props = ['no', 'key', 'time_bumped', 'nof_posts', 'nof_files', 'time_created', 'posts', 'sub', 'name', 'com', 'flag', 'flags', 'footer', 'sticky', 'pn', 'time', 'time_posted', 'txt']; // ['op_img_url'] // op_img_url is wrapped at localArhicve mode dynamically.
//          for (var i=0;i<props.length;i++)
//            Object.defineProperty(obj2,props[i],typeof(parse_funcs[props[i]]==='function')? {get:parse_funcs[props[i]], enumerable:true, configurable:true}
//                                                                                          : {value:parse_funcs[props[i]], enumerable:true, configurable:true}); // not writable
//          var time_tu_func = parse_funcs.time_unit===1?    function(){return this.time;}
//                           : parse_funcs.time_unit===1000? function(){return this.time * 1000;}
//                           :                               function(){return this.time * parse_funcs.time_unit;};
//          Object.defineProperty(obj2,'time_tu', {get:time_tu_func, enumerable:true, configurable:true});
//          obj2.__proto__ = obj;
//          return obj2;
//        }
//        return obj;
      }
      function prep_pfunc(domain, board, type){
//        var key = domain+board+type +'/'+pref.test_mode['6']+pref.debug_mode.parse_error;
//        var key = domain+board+type;
//        var parse_obj = parse_objs[key];
        var type_source = type.substr(0,type.indexOf('_'));
        var type_data   = type.substr(type.indexOf('_')+1);
        var key = key_table[type_source] + key_table[type_data] + ((pref.debug_mode.parse_error)? '_debug':''); // site2['rss'].add_rss has the same line.
        var pfunc = liveTag.mems.init({domain:domain, board:board}).p[key];
        if (!pfunc) {
          var pfunc_root = liveTag.mems[domain].p;
          var pfunc = pfunc_root[key];
          if (!pfunc) {
            var proto = !pref.test_mode['182'] && site2[domain].parse_funcs[type+'_template'] || site2[domain].parse_funcs[type_data+'_template'] || get_getters(type_data==='html');
            var domain_html = (pref.catalog.mimic_base_site || site2[domain].mimic_always)? site.nickname : domain;
            pfunc = {domain: domain,
                       parse_funcs: site2[domain].parse_funcs[type],
                       parse_funcs_html: site2[domain_html].parse_funcs[type_source+'_html'],
                       type_parse: type,
                       type_source: type_source,
                       type_data: type_data,
                       type_html: type_source,
                       domain_html: domain_html,
//                       thread: null, // for faster execution.
//                       page_no: null,
//                       __proto__:site4.parse_funcs_on_demand
                       __proto__:proto
                              };
            if (pref.test_mode['182']) {
              var proto_type = site2[domain].parse_funcs[type+'_template'] || proto;
              while (proto_type && proto_type!==proto) {
                for (var i in proto_type) if (proto_type.hasOwnProperty(i) && !pfunc.hasOwnProperty(i)) Object.defineProperty(pfunc,i,Object.getOwnPropertyDescriptor(proto_type, i));
                proto_type = Object.getPrototypeOf(proto_type);
              }
            }
            if (pfunc.parse_funcs.missing_info) pfunc.missing_info = pfunc.parse_funcs.missing_info;
            pfunc_root[key] = pfunc;
////            var template = site2[domain].parse_funcs[type+'_template'];
////            if (template) {
////////              var keys = Object.keys(template);
////////              for (var i=0;i<keys.length;i++) Object.defineProperty(pfunc, keys[i], {value:template[keys[i]], enumerable:true, configurable:true, writable:true});
//////              for (var i in template) Object.defineProperty(pfunc, i, {value:template[i], enumerable:true, configurable:true, writable:true}); // search in prototype, overwrite getters
////              for (var i in template) // search in prototype, overwrite getters
////                if (typeof(template[i])==='function') Object.defineProperty(pfunc, i, {get:template[i], enumerable:true, configurable:true});
////                else Object.defineProperty(pfunc, i, {value:template[i], enumerable:true, configurable:true, writable:true});
////            }
          }
          pfunc = {board: board, __proto__: pfunc};
          liveTag.mems[domain][board].p[key] = pfunc;
        }
        return pfunc;
      }
      return {
        get_getters: get_getters,
        prep_pfunc: prep_pfunc,
        get: function(doc_obj, domain, board, type, options, sb){
          var parse_obj = prep_pfunc(domain, board, type);
          if (options) {
            if (options.thread!==undefined) {
              if (th_regexp.test(options.thread)) options.thread = options.thread.substr(1);
//              options.op = options.thread; // patch for thread_html
            }
            options.__proto__ = parse_obj;
            parse_obj = options;
          }
//          if (options) {  // working code.
//            if (options.thread) parse_obj.thread = (th_regexp.test(options.thread))? options.thread.substr(1): options.thread;
//            if (options.page) parse_obj.page = options.page;
//          }
          return (type.indexOf('_json')==-1)? parse_obj.parse_funcs.ths({pn:doc_obj, __proto__:parse_obj}) : // for dynamic ripping pfunc, see site2['rss'].parse_funcs.page_html.ths
                                              parse_obj.parse_funcs.ths(doc_obj, parse_obj, sb);
//          return (type.indexOf('_json')==-1)? site2[domain].parse_funcs[type].ths({pn:doc_obj, __proto__:parse_obj}) :
//                                              site2[domain].parse_funcs[type].ths(doc_obj, parse_obj);
        },
////        clean: function(boards){
////          var keys = Object.keys(parse_objs);
////          for (var i=0;i<keys.length;i++) {
////            var key_split = keys[i].split('/');
////            if (boards[key_split[0]+key_split[1]]===undefined) parse_objs[keys[i]] = null;
////          }
////        }
        posts: function(th, start, proto_options){
          if (th.type_data===undefined) {
            var arr = [];
            if (th.posts) for (var i=0;i<th.posts.length;i++) if (!th.posts[i].parse_funcs && th.posts[i].pn) arr[arr.length] = th.posts[i]; // BUG??? th.posts[i].pn was th.posts.pn, I saw this and revised it, but not debugged yet.
            if (arr.length!=0) this.posts({type_data:'html', posts:arr, __proto__:th}, 0, comf.shallow_copy_1(proto_options));
            th = {type_data:'json', __proto__:th};
          }
          var proto_obj = prep_pfunc(th.domain, th.board, 'post_'+(th.posts[0].type_data||th.type_data));
          if (proto_options) {
            proto_options.__proto__ = proto_obj;
            proto_obj = proto_options;
          }
          var localArchive = th.localArchive;
          if (localArchive) proto_obj = {localArchive:localArchive, __proto__:proto_obj};
//          var proto_obj = {domain:th.domain, board:th.board, domain_html:th.domain_html, parse_funcs:th.parse_funcs};
          if (th.posts) for (var i=start||0;i<th.posts.length;i++) {
            if (!th.posts[i].parse_funcs || localArchive) {
              if (th.posts[i].extra_files) for (var j=0;j<th.posts[i].extra_files.length;j++)
                if (!th.posts[i].extra_files.parse_funcs) th.posts[i].extra_files[j].__proto__ = proto_obj;
              th.posts[i].__proto__ = proto_obj;
            }
          }
          if (th.extra_files) if (!th.extra_files[0].parse_funcs || localArchive) for (var j=0;j<th.extra_files.length;j++) th.extra_files[j].__proto__ = proto_obj;
          if (th.posts[0].extra_files) for (var j=0;j<th.posts[0].extra_files.length;j++) // patch for 8chan's thread_json, extra files is not parsed at first.
            if (!th.posts[0].extra_files.parse_funcs) th.posts[0].extra_files[j].__proto__ = proto_obj;
////      if (th.posts) for (var i=0;i<th.posts.length;i++) {
////        if (th.posts[i].extra_files) for (var j=0;j<th.posts[i].extra_files.length;j++) th.posts[i].extra_files[j].__proto__ = th.__proto__;
////        th.posts[i].__proto__ = th.__proto__;
////      }
////      if (th.extra_files) for (var j=0;j<th.extra_files.length;j++) th.extra_files[j].__proto__ = th.__proto__;
        },
      }
    })(),
    parse_funcs: { // DEFAULT
      'common': {
////        entry : function(dtpo,req) { // doc, thread, post, object // working code.
////          for (var i=0;i<req.length;i++) {
////            if (req[i]===':ITER') {
////              if (req[i+1]===':ALL') for (var j=0;j<dtpo[req[i+2]].length;j++) this.exe_sub(dtpo,req,i,j);
////              else if (req[i+1]===':FL') {
////                this.exe_sub(dtpo,req,i,0);
////                this.exe_sub(dtpo,req,i,dtpo[req[i+2]].length-1);
////              } else if (req[i+1]===':FLx' || req[i+1]===':GFLx' || req[i+1]===':GALL') {
////                var j = dtpo[req[i+2]].length - pref[cataLog.embed_mode].t2h_num_of_posts;
////                if (j<1 || req[i+1]===':GALL') j=1;
////                if (req[i+1]===':FLx') {
////                  this.exe_sub(dtpo,req,i,0);
////                  while (j<dtpo[req[i+2]].length) this.exe_sub(dtpo,req,i,j++);
////                } else {
////  //                dtpo[req[i+3][0]] = [];
////                  Object.defineProperty(dtpo,req[i+3][0], {value:[], enumerable:true, configurable:true, writable:true});
////                  dtpo[req[i+3][0]].push(dtpo[req[i+2]][0][req[i+3][1]]);
////                  while (j<dtpo[req[i+2]].length) dtpo[req[i+3][0]].push(dtpo[req[i+2]][j++][req[i+3][1]]);
////                  for (var j=dtpo[req[i+3][0]].length-1;j>=0;j--) if (!dtpo[req[i+3][0]][j]) dtpo[req[i+3][0]].splice(j,1);
////                }
////              }
////              i += 3;
////  //          } else dtpo[req[i]] = this[req[i]](dtpo,req);
////            } else Object.defineProperty(dtpo,req[i], {value:this[req[i]](dtpo,req), enumerable:true, configurable:true, writable:true});
////          }
////        },
////        exe_sub : function(dtpo,req,i,j) {
////          dtpo[req[i+2]][j].domain = dtpo.domain;
////          dtpo[req[i+2]][j].board  = dtpo.board;
////          this.entry(dtpo[req[i+2]][j],req[i+3]);
////        },
        posts: function(){return undefined;},
        com: function(){return undefined;},
        flags: function(){return undefined;},
        flag: function(){},
        id: function(){},
        pn_id: function(){},
        pn_name: function(){},
        country: function(){},
        hrefs: function(){},
        trip: function(){}, // hit this line in archive of 4chan with trip search acrive.
        pn_trip: function(){},
        id2: function(){},
//        op_img_url: function() {return undefined;},
//        preventDefault:function(e){e.preventDefault();},
        th_init: null,
//        th_destroy: null,
        tn_as: function(th){return th.pn.getElementsByTagName('a');},
        tn_imgs: function(th){
          var imgs = [];
//          for (var i=0;i<as.length;i++) imgs[i] = as[i].getElementsByTagName('img')[0];
          var as = th.pn && th.pn.getElementsByTagName('a');
          if (as) for (var i=0;i<as.length;i++) {
            var tmp = as[i].getElementsByTagName('img')[0] || as[i].getElementsByTagName('video')[0]; // <video> for distchan
//            var tmp = as[i].getElementsByTagName('img')[0];
            if (tmp) imgs[imgs.length] = tmp;
          }
          return imgs;
        },
        key: function(th){return th.domain + th.board + th.no;},
//        pn: function(th){return site2[th.domain_html].catalog_json2html3(th,th.board, this.op_img_url(th));},
        op_img_url: function(th) {return site2[th.domain].catalog_json2html3_thumbnail(th, th.board);},
        get_op_src: function(th) {return th.op_img_url;},
        ths_array: function(doc,ths_col){
          var ths = [];
          if (ths_col)
            for (var i=0;i<ths_col.length;i++) {
//            for (var i=ths_col.length-1;i>=0;i--) { // WHY???
              var page = (doc.type_html==='catalog')? Math.floor(i/15)+'.'+i%15
                                                    : (doc.type_html==='page')? doc.page + '.' + i : undefined; // page_html
              ths[i] = {
                pn: ths_col[i],
                page: page,
                __proto__: doc.__proto__};
            }
          return ths;
        },
        time: function(th){return undefined;},
        time_posted : function(th){return undefined;},
        time_unit: 1,
        has_posts: true,
//        last_replies: function(th){return th.posts.slice(1);},
////        post_no: function(post){return post.no;},
        txt: (function(){
          var pn = document.createElement('div');
          return function(th){
            pn.innerHTML = th.com;
            return pn[brwsr.innerText];
          };
        })(),
        type_com: 'html',
        footer_prep: function(th, footer){
          var str = footer.innerHTML.replace(/ *R:[0-9 \/]*I:[0-9 \/]*/,'');
          str = str.replace(/P:[0-9 \/]*/,'');
          str = str.replace(/ *\(sticky\) */,'');
          if (str!=='') {
            footer.innerHTML = '<span></span>' + str;
            footer = footer.childNodes[0];
          }
        },
//        adopt_pn: function(th){
////          for (var i=0;i<th.posts.length;i++) Object.defineProperty(th.posts[i], 'pn', {value:document.adoptNode(th.posts[i].pn), enumerable:true, configurable:true, writable:true});
//          Object.defineProperty(th, 'pn', {value:document.adoptNode(th.pn), enumerable:true, configurable:true, writable:true});
//        },
        get_lth_from_node: function(pn){
          while (pn && !pn.name) pn = pn.parentNode;
          return pn && liveTag.mems.getFromName(pn.name) || null;
        },
//        txt2com: function(txt){return txt;},
        nativeUpdate2pn: function(pn_in){return pn_in.lastChild;}, // rip container from updated instance by native updater.
//        nativeUpdate2pn: function(pn_in){return pn_in;},
        nofFiles: function(){return 0;}, // nof files in post
        hrefs: function(post){ // moved from DEFAULT.post_json
          if (!post.com) return;
          var hrefs = post.com.match(/href="([^"]*)"/g);
          if (hrefs) for (var i=hrefs.length-1;i>=0;i--) {
            hrefs[i] = hrefs[i].slice(6,-1); // remove href=""
            if (hrefs[i].indexOf('javascript')===0) hrefs.splice(i,1);
          }
//          if (hrefs) for (var i=0;i<hrefs.length;i++) {
//            var idx = hrefs[i].indexOf('://');
//            hrefs[i] = hrefs[i].slice(idx===-1? 6:idx+1, -1);
//          }
          return hrefs;
        },
        _links: function(post){
          var arr = this.hrefs(post);
          if (arr) for (var i=0;i<arr.length;i++) arr[i] = site2[post.domain].popups_href2dbtp(arr[i]);
          return arr;
        },
      },
      'headline_html': {
        footer: function(th){return th.pn.insertBefore(cnst.dom('<span style="padding:0 3px 0 0"></span>'),th.pn.firstChild);},
        css: '.threadIcons {margin:0px;position:static;width:auto;text-align:left;display:inline}',
      },
      'catalog_html': {
        posts: function(th){return [{pn:th.pn, __proto__:th.__proto__}];}, // test, gives mirror of th, but not getter, [{get 0(){return th;}, length:1}]
//        posts: function(th){return [{sub:th.sub, com:th.com, name:th.name, pn:th.pn}];}, // BUG, should be [{sub:th.sub, com:th.com, name:th.name}], but this causes too many erroes because all data must be parsed from th.pn in catalog_html, pn===undefined needes to write all uses of posts[0].XXX 
        time: function(th){return th.time_created;},
        posts_full: (function(){
          var exlist = {posts:null, pn:null, flag:null, flags:null, footer:null, pn_name:null, ths:null, page:null,
                        tn_as:null, tn_imgs:null, txt:null, pn_id:null, pn_trip:null,
                        parse_funcs:null, parse_funcs_html:null, type_parse:null, type_source:null, type_data:null, type_html:null,
                        domain:null, board:null, domain_html:null, exe_sub:null, exe_sub2:null};
          return function(th){
//            for (var prop in th) if (exlist[prop]===undefined) th.posts[0][prop] = th[prop]; // invoke getters.
            for (var prop in th) if (exlist[prop]===undefined)
              if (th.parse_funcs[prop]) Object.defineProperty(th.posts[0], prop, {value:th[prop], enumerable:true, configurable:true, writable:true}); // invoke getters to cut reference to pn(whole html), and also set th[prop].
          };
        })(),
        has_posts: false,
        type_com: 'txt',
        filename: function(th){
          if (!th.op_img_url) return undefined;
          var fname = th.op_img_url.replace(/.*\//,'');
          var idx = fname.indexOf('.');
          th.tim = fname.substr(0,idx);
//          th.ext = fname.substr(  idx);
          return th.tim;
        },
      },
      'catalog_json': {
        ths: function(obj, parse_obj) {
          var ths = [];
          for (var i=0;i<obj.length;i++)
            if (obj[i].threads) for (var j=0;j<obj[i].threads.length;j++) {
              var post0 = obj[i].threads[j];
              post0.sticky = obj[i].threads[j].sticky===1; // overwrite property of the same name before setting prototype to use polarity.
              post0.__proto__ = parse_obj;
              ths[ths.length] = {
                page: i + '.' + j,
                posts: [post0],
                __proto__:post0 // all parser must need to accept both post.pn and thread.pn, but this is a catalog and post is wrapped already(has parse_funcs), OK.
              };
//              obj[i].threads[j].page = i + '.' + j;
//              obj[i].threads[j].sticky = obj[i].threads[j].sticky===1; // overwrite property of the same name before setting prototype to use polarity.
//              obj[i].threads[j].type_html = 'catalog';
//              obj[i].threads[j].__proto__ = parse_obj;
//              ths[ths.length] = obj[i].threads[j];
            }
          return ths;
        },
////        posts: function(th){return [{sub:th.sub, com:th.com, name:th.name, trip:th.trip, pn:th.pn, time:th.time_created}];}, // time for post_json2html
////        posts: function(th){return [{sub:th.sub, com:th.com, name:th.name, time:th.time}];}, // this is used at scan, so the lighter is the best. 'time' is only for 'check_reply' and 'stats'. And this removes prototype chain to reduce memory consumption instead of using '[{__proto__:th}]'.
//        posts: function(th){return [{time:th.time, DUMMY:true}];}, // 'time' is for 'check_reply' and 'stats'. And this removes prototype chain to reduce memory consumption instead of using '[{__proto__:th}]'.
//        posts_full: function(th){
////          th.posts[0] = {}; // BUG NEVER DO THIS. posts_full is called AFTER SETTING PROTOTYPE in wrap_to_parse.posts in format_html.prepare_html_prep_posts
//            delete th.posts[0].DUMMY;
//////          th.posts[0].no = th.no; // works
//////          th.posts[0].time = th.time;
//          var keys = Object.keys(th);
////          var exlist = ['obj','posts'];
////          for (var i=0;i<keys.length;i++) if (exlist.indexOf(prop)==-1) th.posts[0][keys[i]] = th[keys[i]]; // remove props in prototype chain. // will be this.
//          for (var i=0;i<keys.length;i++) if (keys[i]!=='posts' && keys[i]!=='pn') th.posts[0][keys[i]] = th[keys[i]]; // remove props in prototype chain.
//        },
        has_posts: false,
      },
//      'catalog_html': {
//        ths_array: function(doc,ths_col){ // working code.
//          var ths = [];
//          if (ths_col)
////            if (site.nickname!==doc.domain) site2[doc.domain].absolute_link(doc.pn, doc.board);
//            for (var i=ths_col.length-1;i>=0;i--) {
//              var page = Math.floor(i/15)+'.'+i%15;
//              ths[i] = {
//                pn: ths_col[i],
//                type_html: 'catalog_html',
////                page: Math.floor(i/15)+'.'+i%15, // cause warning of 'Object literal with complex property' in V8.
//                page: page,
//                __proto__: doc.__proto__};
//            }
//          return ths;
//        },
//      },
      'page_html': {
//        ths_array: function(doc,ths_col){ // working code.
//          var ths = [];
//          if (ths_col)
////            if (site.nickname!==doc.domain) site2[doc.domain].absolute_link(doc.pn, doc.board);
//            for (var i=ths_col.length-1;i>=0;i--) {
//              var page = (doc.page!=='?')? doc.page + '.' + i : doc.page;
//              ths[i] = {
//                pn: ths_col[i],
//                type_html: 'page_html',
////                page: (doc.page!=='?')? doc.page + '.' + i : doc.page, // cause warning of 'Object literal with complex property' in V8.
//                page: page,
//                __proto__: doc.__proto__};
//            }
//          return ths;
//        },
        posts_array: function(th,pns){ // working code.
          var pts = [];
          var proto = site2[th.domain].wrap_to_parse.prep_pfunc(th.domain, th.board, 'post_html');
//          var proto = (th.domain==='4chan')? site2[th.domain].wrap_to_parse.prep_pfunc(th.domain, th.board, 'post_html') : th.__proto__; // tests only in 4chan, this reuires so many test...
//          var proto_kwd = 'post_' + th.type_data; // working code, WILL BE THIS.
//          var proto = (site2[th.domain].parse_funcs.hasOwnProperty(proto_kwd))? site2[th.domain].parse_funcs[proto_kwd] : th.__proto__;
//          var proto = th.__proto__; 
if (!pref.test_mode['5']) { // faster, because object creation is light,,,orz,,,
          for (var i=0;i<pns.length;i++) pts[pts.length] = {pn:pns[i], __proto__:proto};
} else {
          pts.pns = pns;
          pts.idx0 = 0;
          pts.idx1 = pns.length-1;
          pts.get0 = this.posts_get0;
          pts.get1 = this.posts_get1;
          pts.proto = proto;
//          var pts = {pn:pns, idx0:0, idx1:pns.length-1, length:pns.length, __proto__:proto};
          Object.defineProperty(pts, pts.idx0, {get: pts.get0, enumerable:true, configurable:true});
          Object.defineProperty(pts, pts.idx1, {get: pts.get1, enumerable:true, configurable:true});
}
          return pts;
        },
        posts_get0: function(){
//console.log('get0: '+this.idx0);
          var val = {pn:this.pns[this.idx0],  __proto__:this.proto}; // this.__proto__ reffers Array, doesn't work.
          Object.defineProperty(this, this.idx0, {value: val, enumerable:true, configurable:true, writable:true});
          if (++this.idx0<this.idx1) Object.defineProperty(this, this.idx0, {get: this.get0, enumerable:true, configurable:true});
          return val;
        },
        posts_get1: function(){
//console.log('get1: '+this.idx1);
//console.log('this.pns: ');
//console.log(this.pns);
          var val = {pn:this.pns[this.idx1],  __proto__:this.proto};
          Object.defineProperty(this, this.idx1, {value: val, enumerable:true, configurable:true, writable:true});
          if (--this.idx1>this.idx0) Object.defineProperty(this, this.idx1, {get: this.get1, enumerable:true, configurable:true});
          return val;
        },
//        posts_array: function(th,posts){ // working code.
//          var pts = [];
//          for (var i=0;i<posts.length;i++) pts[pts.length] = {pn:posts[i], __proto__:th.__proto__};
//          return pts;
//        },
        posts: function(th){return this.posts_array(th, th.pn.getElementsByClassName('post'));},
////////        posts_pn: function(th){return th.pn.getElementsByClassName('post');},
        time_posted: 'thread_html',
        time_bumped: function(th){ // TO BE FIXED.
          return th.posts[th.posts.length-1].time;
        },
        time_created: function(th){return th.posts[0].time;},
        key: function(th){return th.domain + th.board + th.no;},
        insert_footer4: function(ref){
          var footer = document.createElement('div');
          return ref.parentNode.insertBefore(footer,ref);
        },
        flags: function(th){ // for on demand access.
          var flags = [];
          var i = th.posts.length -1;
          var n = pref[cataLog.embed_mode].t2h_num_of_posts -1;
          if (i<n) { // POSTS MUST BE ACCESSED FROM HEAD OR TAIL.
            n=0;
            while (n<=i) {
//console.log('up: '+n+', '+i);
              if (th.posts[n]) flags[n] = th.posts[n].flag; // 8chan doesn't show 5 posts all the time.
              n++;
            }
          } else {
            while (n>=0) {
//console.log('down: '+n+', '+i);
//console.log(th.posts[i]);
              if (th.posts[i]) flags[n--] = th.posts[i--].flag;
              if (n==0) i=0;
            }
          }
          return flags;
        },
////        flags: function(th){ // for on demand access. // worked code, but post was changed to had to be accessed from top or end.
////          var flags = [];
////          var i = th.posts.length - pref.catalog_t2h_num_of_posts;
////          if (i<1) i=1;
////          if (th.posts.length!=0) flags[0] = th.posts[0].flag;
////          while (i<th.posts.length) {
////            if (th.posts[i]) flags[i] = th.posts[i].flag;
////            i++;
////          }
////          return flags;
////        },
        html_org: function(th){return th.pn.innerHTML;},
        sticky: function(){return false;},
////        pop_post_prep: function(th){ // working code.
////          th.children = th.pn.childNodes;
////          th.idx_pop = th.pn.childNodes.length-1;
////        },
        type_com: 'txt',
        replace_omitted_info : function(dst, src){dst.textContent = src.textContent;},
        replace_omitted_info2: 'replace_omitted_info',
        posts_full: 'catalog_html',
        get_max_page: function(doc){
          var pns = doc.getElementsByClassName('pages')[0];
          if (!pns) return;
          pns = pns.childNodes;
          var max = 0;
          for (var i=0;i<pns.length;i++) if (max+1 == pns[i].textContent) max++; // == used intentionally.
          return max;
        },
      },
      'page_json': {
        ths: function(obj, parse_obj){
          var ths = [];
          if (obj && obj.threads) for (var i=0;i<obj.threads.length;i++) { // patch for 8chan
//          for (var i=0;i<obj.threads.length;i++) {
            var tgt = obj.threads[i];
            if (tgt) { // patch for 8chan
              tgt.page = (parse_obj.page)? parse_obj.page + '.' + i : undefined;
              tgt.key = parse_obj.domain + parse_obj.board + tgt.posts[0].no;
              tgt.posts[0].__proto__ = parse_obj;
              tgt.__proto__ = tgt.posts[0];
              ths[i] = tgt;
            }
////            if (tgt) { // patch for 8chan // working code.
////              ths[i] = {obj: tgt, // buffered, because if obj is used, change of obj's contents will affect to this program directly.
////                        posts: tgt.posts, ext: tgt.posts[0].ext, tim:tgt.posts[0].tim,
////                        time: tgt.posts[0].time, // for 'check_reply.check_t1_op'
////                        key: parse_obj.domain + parse_obj.board + tgt.posts[0].no, no: tgt.posts[0].no,
////                        page: (parse_obj.page)? parse_obj.page + '.' + i : undefined,
////                        __proto__:parse_obj};
////            }
////            tgt.posts = null; // cut the reference, if leave this, cutting th.posts.length in 'update_posts_replace_prep' doesn't affect to tgt.posts and tgt.posts keep containing all posts.
          }
          return ths;
        },
        flags: function(th){
          if (!th.country) return undefined;
          var n = th.posts.length - pref[cataLog.embed_mode].t2h_num_of_posts;
          return [th].concat(th.posts.slice((n<=1)? 1 : n)); // patch for 4chan:catalog_html, it isn't formed as catalog_json, posts[0] doesn't contain full information.
//          return (n<=1)? th.posts.slice() : [th.posts[0]].concat(th.posts.slice(n));
        },
      },
      'thread_html'  : {
        ths_array: function(doc, ths_col){
          return [{pn:ths_col,
//                   type_html: 'thread_html',
//                   page: '?',
                   __proto__: doc.__proto__}];
        },
        time_posted : function(th){return th.posts[th.posts.length-1].time*1000;},
        posts_full: 'catalog_html',
        id: function(th){return th.posts[0].id;},
        country: function(th){return th.posts[0].country;},
        trip: function(th){return th.posts[0].trip;},
        flag: function(th){return th.posts[0].flag;},
        name: function(th){return th.posts[0].name;},
        type_com: 'txt',
        time_unit:1000,
      },
      'thread_json'  : {
        ths: function(obj, parse_obj){
          obj.key = parse_obj.domain + parse_obj.board + obj.posts[0].no;
          obj.posts[0].__proto__ = parse_obj;
          obj.__proto__ = obj.posts[0];
          return [obj];
        },
//        ths: function(obj, parse_obj){return site2['DEFAULT'].parse_funcs.page_json.ths({threads:[obj]}, parse_obj);}, // working code.
////        ths: function(obj, parse_obj){
////          return [{obj:obj, posts: obj.posts, ext: obj.posts[0].ext, tim:obj.posts[0].tim,
////                   key: parse_obj.domain + parse_obj.board + parse_obj.thread, no: parse_obj.thread, __proto__:parse_obj}];
////        },

////        pop_post_prep: function(obj){ // working code.
////          obj.idx_pop = obj.posts.length-1;
////        },
////        pop_post: function(obj){
////          if (obj.idx_pop>=0) {
////            obj.post = obj.posts[obj.idx_pop--];
//////            obj.post.parse_funcs = this;
//////            obj.post.__proto__ = obj.__proto__;
////            obj.post.time *= 1000; // DESTRUCTIVE
////            return true;
////          } else return false;
////        },
////        preprocess: function(obj){ // working code, but deleted.
////          if (!obj.preprocessed) {
////            obj.preprocessed = true;
////            for (var i=obj.posts.length-1;i>=0;i--) obj.posts[i].time *= 1000;
////          }
////        },
////        time_created : function(obj){return obj.posts[0].time;}, // preprocessed
////        time_bumped : function(obj){return obj.posts[obj.posts.length-1].time;}, // preprocessed
        time_created : function(obj){return obj.posts[0].time*1000;},
        time_bumped : function(obj){
          if (obj.posts[0].bumplimit) return undefined; // 4chan doesn't have email field but compensated.
          var i = obj.posts.length-1;
          while (i>0 && obj.posts[i].email && obj.posts[i].email==='sage') i--;
          return obj.posts[i].time*1000;
        },
        time_posted : function(obj){return obj.posts[obj.posts.length-1].time*1000;},
        nof_posts: function(obj){return obj.posts[0].replies+1;},
        nof_files: function(obj){return obj.posts[0].images+1;},
        has_nof_files: true,
        sub: function(obj){return (obj.posts[0].sub || '');},
        name: function(obj){return (obj.posts[0].name || '');},
        com: function(obj){return (obj.posts[0].com || '');},
        sticky: function(obj){return obj.posts[0].sticky===1;},
        time_unit: 1000,
        prep_to_archive: function(obj){return {posts:obj};},
        rip_from_archive: function(obj){return obj.posts || obj;}, // patch for dual acceptance, internal and external format.
//        proto: 'page_json'
      },
      'post_html': {
        sub: function(post){
          var sub = post.pn.getElementsByClassName('subject')[0];
          return (sub)? sub.textContent : '';
        },
        img2src: function(img){return img.parentNode.href;},
        img2ext: function(img){return img.parentNode.getAttribute('data-ext');},
        nofFiles: function(post){return post.pn.getElementsByClassName('file').length;},
      },
      'post_json': {
        time_unit: 1000,
        pn: function(post){return site2[post.domain_html].post_json2html(post);}, // blocked by template
        anchor: function(){},
      },
      'base_html': { // for HTML based sites, timeunit===1
        time_created: function(th){return th.posts[0].time;},
        time_posted: function(th){return th.posts[th.posts.length-1].time;},
        time_unit: 1,
        proto: 'page_html',
      },
      'json_template': {
        sub: '',
        name: 'Anonymous',
        com: '',
        sticky: undefined,
//        pn: undefined, // block getter
        no: undefined, // patch
        time: undefined, // patch
        get nofFiles(){return this.filename? 1 : 0;},
      },
    },
//    parse_funcs : function(dtp,req) { // doc, thread, post, use .call() to call with 'this'.
//      for (var i=0;i<req.length;i++) {
//        if (this[req[i]]) dtp[req[i]] = this[req[i]](dtp,req);
//        if (req[i]==='ths') i++;
//      }
//    },
    parse_parts:{
      add_op_img_url: function(posts,board,domain){
         for (var i=0;i<posts.length;i++)
          posts[i].op_img_url = site2[domain].catalog_json2html3_thumbnail(posts[i], board);
      },
    },
////    update_posts_replace: function(th,th_old,pnode) { // working code.
////      if (th_old.posts) {
////        th_old.posts[0].pn.innerHTML = th.posts[0].pn.innerHTML;
////        for (var i=th_old.posts.length-1;i>=1;i--) this.update_posts_remove(th_old,i,pnode);
////      }
////      th_old.posts = [th_old.posts[0]];
////      this.update_posts_add(th,th_old,pnode);
////    },
    update_posts_merge_prep: function(posts, posts_m, num, by_no) { // merge posts by time
      if (posts) {
        num = (num>=0)? num+1 : ((posts_m && posts_m.length)? posts_m.length : 0) + posts.length;
        if (num<posts.length) posts = posts.slice(0,1).concat(posts.slice(posts.length - num + 1)); // cut
        var i=1;
        var j=0;
        var posts_ret = [posts[0]];
        if (by_no) // by no.
          while (i<posts.length && j<posts_m.length) {
            if (posts[i].no === posts_m[j].no) {posts_ret[posts_ret.length] = posts[i++]; j++; continue;}
            posts_ret[posts_ret.length] = (posts[i].no < posts_m[j].no)? posts[i++] : posts_m[j++];
          }
        else
          while (i<posts.length && j<posts_m.length)
            posts_ret[posts_ret.length] = (posts[i].time * posts[i].parse_funcs.time_unit < posts_m[j].time * posts_m[j].parse_funcs.time_unit)? posts[i++] : posts_m[j++];;
        posts_ret = posts_ret.concat((i<posts.length)? posts.slice(i) : posts_m.slice(j));
        return (posts_ret.length<=num)? posts_ret :
                                        posts_ret.slice(0,1).concat(posts_ret.slice(posts_ret.length-num+1,posts_ret.length));
      }
    },
    update_posts_merge_bases: {
      factory: function(){
        return {
          bases: {},
          initialized: false,
          shown: new Map(), // Set(),
          shownSet: new Set(), // null,
//          view: this.view,
          __proto__: this
        };
      },
      bases: {},
      bases_unique: function(){
        var map = new Map();
        for (var name in this.bases) map.set(this.bases[name], name);
        return map;
      },
      reorder_bases: function(clg){
        for (var b in this.bases)
          if (this.bases[b].reorder_req>=4) this.bases[b].reorder_all(clg); else this.bases[b].re_style(clg);
      },
      initialized: false,
      query: function(key, tgt_th, th, isShown, clg){
//      query: function(key, tgt_th, th, tgt, isShown, clg){
//        if (this.bases[key]) return this.bases[key]; // this is checked in caller.
        var tgt = this.base_tgts(key, clg.pref[clg.mode]);
        if (!tgt) return undefined;
        var base_by_tgt = this.bases[tgt];
        var base = base_by_tgt || this.base_factory(key, tgt_th, th, isShown, clg);
        this.bases[key] = base;
        this.merge_th(tgt_th, th, base, key, clg);
        if (base_by_tgt) {
          if (isShown) clg.func_hide_org(key);
        } else if (!this.initialized) this.setup_hook(clg);
        tgt_th[0] = base.pn; // after func_hide_org
//        var base = (tgt)? this.bases[tgt] : null; // working code
//        if (base) {
////        for (var i=0;i<tgts.length;i++) if (this.bases[tgts[i]]) {
////          var base = this.bases[tgts[i]];
//          this.bases[key] = base;
//          this.merge_th(tgt_th, th, base, key, clg);
//          if (isShown) cataLog.catalog_obj2.func_hide_org(key);
////          th.pn.setAttribute('style','display:none'); // TEMPORAL PATCH.
//          tgt_th[0] = base.pn;
//          return base;
//        }
//        this.base_factory(key, tgt_th, th, isShown, clg);
//        this.bases[key] = base;
//        this.merge_th(tgt_th, th, base, key, clg);
//        if (!this.initialized) this.setup_hook();
        return base;
      },
      base_factory: (function(){
        function func_0(v){return v[8][0];}
        function func_1(v){return v[8][1];}
        function func_2(v){return v[8][2];}
        function func_3(v){return v[8][3];}
        function func_4(v){return v[8][4];}
        function func_max(a,c){return a>c? a : c;}
        function func_min(a,c){return a<c? a : c;}
        function func_sum(a,c){return a+c;}
        var base_tmp;
        function base_tmp_map_reduce(func){return base_tmp.lths.map(func).reduce(func_blank_sum);}
        function func_blank_sum(a,c){return a && c? a+c : a? a : c;}
        function lth_ta(lth){return (lth.ta && lth.ta.posts)? lth.ta.posts.length : '';}
        function lth_pd(lth){return lth.pd? lth.pd.length : '';}
        function lth_sp(lth){return lth.sg? lth.sg.size : '';}
        var emu_ta = {posts:{get length(){return base_tmp_map_reduce(lth_ta);}}};
        var emu_pd = {get length(){return base_tmp_map_reduce(lth_pd);}};
        var emu_sp = {get size(){return base_tmp_map_reduce(lth_sp);}};
        var emu_16 = {recent_posts: function(num){
          if (num===undefined || num===null) num = {lth: base_tmp, __proto__:base_tmp.tgt_ths[0][16]}.get_t2h_num_of_posts();
          var posts = base_tmp.posts ||
            (base_tmp.lths.forEach(function(lth){if (lth.ta) site2[lth.domain].wrap_to_parse.posts({posts:lth.ta.posts, __proto__:lth.ta.posts[0]});}), // for not watching threads.
             base_tmp.tgt_ths.map(function(v){return v[16].recent_posts(num);}).reduce(function(a,c){return site2['DEFAULT'].update_posts_merge_prep(a,c, num);}));
          return site2['DEFAULT'].update_posts_replace_prep(null, posts, num);
        }};
        var base_proto = {
          lazy_draw: function(idx){
            if (idx>=this.lazy.length) this.lazy[idx] = true; // all are not initialized.
            else this.lazy.splice(idx,0,true);
            if (this.lazy_idx>idx) this.lazy_idx = idx;
          },
          remove: function(idx){
            this.posts.splice(idx,1);
            if (idx<this.lazy_idx) this.lazy_idx--;
            return (this.lazy)? !this.lazy.splice(idx,1)[0] : true;
          },
          draw_1_pos: function(idx){ // this.lazy_idx may refer "true" node by this.
            var n=idx+1;
            while (n<this.lazy.length && this.lazy[n]) n++; // skip myself also
            this.lazy[idx] = false;
            return n;
          },
          get_class: function(key, force, old) {
            if (!force && !pref.proto.merge_lv) return null;
            var lv = pref_func.merge_obj5a_sc(key, old && pref3.proto.merge_lv_obj2_old || pref3.proto.merge_lv_obj2, null); // '0' can be got.
//            var lv = (key===site.nickname+site.board+site.no)? 0 : 1; // test
            return (lv===null)? null : pref.cpfx+'merged'+lv;
          },

          posts: null, // dummies for catalog or headline
          lazy: null,
          lazy_idx: null,
//        tgt_th: {
//          '8':{
//              get 0(){
//                var latest = this.tgt_ths[0][8][0];
//                for (var i=1;i<this.tgt_ths.length;i++) {
//                  var test = this.tgt_ths[i][8][0];
//                  if (test>latest) latest = test;
//                }
//                return latest;
//              },
              get 0(){return this.tgt_ths.map(func_0).reduce(func_max);},
              get 1(){return this.tgt_ths.map(func_1).reduce(func_min);},
              get 2(){return this.tgt_ths.map(func_2).reduce(func_sum);},
              get 3(){return this.tgt_ths.map(func_3).reduce(func_sum);},
              get 4(){return this.tgt_ths.map(func_4).reduce(func_max);},
              get 8(){return this;}, // remove hierarchy, but tricky
//          },
            get 14(){
              if (this.tgt_ths.length==1) return this.tgt_ths[14];
              var arr = this.tgt_ths.map(function(v){var p = (v[14]||'').split('.'); p[0] = parseInt(p[0],10); return p;}).sort(function(a,b){return a[0]!=b[0]? a[0]-b[0] : a[1]===undefined? -1 : b[1]===undefined? 1 : a[1].length!=b[1].length? a[1].length-b[1].length : a[1]<b[1]? -1 : 1});
              return arr[0].join('.')+'-'+arr[arr.length-1].join('.');
            }, // '14': 'M',
            get 16(){base_tmp = this;return this._16;},
//            get 16(){base_tmp = this;return emu_16;}, // '16':{recent_posts:function(){return null;}},
//        },
//        lth: {
            domain:'', board:'', no:'',
            get tags(){
              var pf = pref.proto.footer.merged_tag;
              if (pf==='no') return [];
              var lths_len = this.lths.length;
              if (pf==='all') {
                var tags_all = {};
                for (var i=0;i<lths_len;i++) {
                  var tags = this.lths[i].tags;
                  for (var j=0;j<tags.length;j++) tags_all[tags[j]] = (tags_all[tags[j]]||0) + 1; // obj might be faster than Map?
                }
                var tags0 = this.lths[0].tags.slice();
                for (var j=tags0.length-1;j>=0;j--) if (tags_all[tags0[j]]!==lths_len) tags0.splice(j,1);
                return tags0;
              } else {
                var tags_all = new Map();
                for (var i=0;i<lths_len;i++) {
                  var tags = this.lths[i].tags;
                  for (var j=0;j<tags.length;j++) tags_all.set(tags[j], (tags_all.get(tags[j])||0)+1);
                }
                var tags_ret = [];
                for (var tag of tags_all.keys()) tags_ret[tags_ret.length] = tag;
                return tags_ret.sort(function(a,b){return tags_all.get(b) - tags_all.get(a);});
              }
            },
            get watched(){return this.lths.some(function(v){return v.watched;});},
            get watched_p(){return this.lths.some(function(v){return v.watched_p;});},
            get nrtm(){return this.lths.map(function(v){return v.watched && v.nrtm || 0;}).reduce(func_sum);},
            get nr(){return this.lths.map(function(v){return v.watched_p && v.nr || 0;}).reduce(func_sum);},
            get ta(){base_tmp = this; return emu_ta;},
            get pd(){base_tmp = this; return emu_pd;},
            get sp(){base_tmp = this; return emu_sp;},
            get archived(){
              var len = this.lths.filter(function(v){return v.archived;}).length;
              return len===0? 0 : (len===this.lths.length)? 1 : 8;
            },
//        },
          get keys(){return this.lths.map(function(v){return v.key;});},
          reorder_all(clg){
            var keys = this.keys;
            if (keys.length<2) return;
            var idxs = clg.idx_insert_bulk(keys, clg, true, true);
//            if (pref.test_mode['168'] || pref.test_mode['169']) { // working code, dropped by releasing test_mode168,169
//              var idxs = [keys[0]];
//              var clg_emu = {idxs:idxs, __proto__:clg};
//              for (var i=1;i<keys.length;i++) clg.idx_insert(keys[i], clg_emu);
//            }
//            if (!pref.test_mode['168']) {
//              var idxs_bulk = clg.idx_insert_bulk(keys, pref.test_mode['169']? {idxs_single:idxs, loose_check:true, __proto__:clg} : clg, true, true); // clg.idxs will be ovrwritten
//              if (!pref.test_mode['169']) idxs = idxs_bulk;
//            }
            for (var i=0;i<idxs.length;i++) if (idxs[i]!=keys[i]) {
              var idx_old = keys.indexOf(idxs[i]);
              keys.splice(i,0,keys.splice(idx_old,1)[0]);
              this.lths.splice(i, 0, this.lths.splice(idx_old,1)[0]);
              this.tgt_ths.splice(i, 0, this.tgt_ths.splice(idx_old,1)[0]);
              var tgt_th = this.tgt_ths[i];
              var domain_html = tgt_th[16].domain_html;
              if (clg.view==='headline') site2[domain_html].headline_reorder(tgt_th[3], this, clg, i);
              else if (clg.view==='catalog') site2[domain_html].catalog_reorder(tgt_th[3], this, clg, i);
            }
            this.re_style(clg);
          },
//          reorder(key, clg){
//            var keys = this.keys;
//            if (keys.length<2) return;
//            var idx_old = keys.indexOf(key);
//            keys.splice(idx_old,1);
//            var idx_new = clg.idx_insert(key, {idxs:keys, __proto__:clg});
//            if (idx_old===idx_new) return;
//            this.lths.splice(idx_new, 0, this.lths.splice(idx_old,1)[0]);
//            this.tgt_ths.splice(idx_new, 0, this.tgt_ths.splice(idx_old,1)[0]);
//            var tgt_th = this.tgt_ths[idx_new];
//            var domain_html = tgt_th[16].domain_html;
//            if (clg.view==='headline') site2[domain_html].headline_reorder(tgt_th[3], this, clg, idx_new);
//          },
          re_style: function(clg){
            var attr_obj = clg.pref.filter.attr_list_obj2;
            var lths = this.lths;
            var force = this.reorder_req&0x02;
            if (force) clg.view_attr_set(lths[0].key, true);
            var i=0;
            while (i<lths.length) if (attr_obj[lths[i].key] && attr_obj[lths[i].key].style) { // SLOW!!!
              if (!force) clg.view_attr_set(lths[0].key, true);
              if (i!==0) clg.view_attr_set(lths[i].key, true);
              break;
            } else i++;
            this.reorder_req = 0;
          },
        };
        return function(key, tgt_th, th, isShown, clg){
          var merge = {
            pn: tgt_th[0], // site2[th.domain].page_json2html3({domain:th.domain, board:th.board, no:0}, '/ALL_MERGED/', null, true),
            isShown: 0, // increased in merge_th_catalog // (isShown)? 1 : 0, // num of participants
//            key:key,
            top_key:key,
            tgt_ths:[],
            lths:[],
            footer: null,
            reorder_req: 0,
            _16: {__proto__:emu_16}, // for icons at headline
            __proto__:base_proto
          };
          if (clg.view==='catalog') {
            merge.cns = null;
          } else if (clg.view==='page' || clg.view==='thread') {
            var posts = tgt_th[16].posts || th.posts;
            clg.merge_bases.class_add_all(posts, base_proto.get_class(key));
            merge.posts = posts.slice(),
            merge.lazy = (pref[clg.view].lazyDraw.merge)? posts.map(function(){return false;}) : null;
            merge.lazy_idx = (pref[clg.view].lazyDraw.merge)? posts.length : null;
          }
          return merge;
        }
      })(),
      class_add_all: function(posts, merge_class){
        if (!merge_class) return;
        for (var i=0;i<posts.length;i++) if (posts[i].pn && posts[i].pn.parentNode) posts[i].pn.parentNode.classList.add(merge_class);
      },
      class_remove_all: function(posts, merge_class){
        if (!merge_class) return;
        for (var i=0;i<posts.length;i++) if (posts[i].pn && posts[i].pn.parentNode) posts[i].pn.parentNode.classList.remove(merge_class);
      },
      remove_th: function(key, tgt_th, clg){
        var merge = this.bases[key];
        if (merge) {
          var idx = merge.lths.indexOf(liveTag.mems.getFromName(key));
          merge.tgt_ths.splice(idx,1);
          merge.lths.splice(idx,1);
          var domain_html = tgt_th[16].domain_html;
          tgt_th[0] = (clg.view==='catalog')? site2[domain_html].catalog_separate(merge, tgt_th[3], tgt_th[16], clg) // , merge.isShown>1 && merge.keys[0]===key) // this.remove_th_catalog({cns:tgt_th[3], __proto__:tgt_th[16]}, merge);
                    : (clg.view==='headline')? site2[domain_html].headline_separate(merge, tgt_th[3], clg, tgt_th)
                    : this.remove_th_page(tgt_th[16], false, merge, clg); // merge.isShown==1 doesn't mean 'remove'
          if (merge.isShown===2) {
            merge.footer[0].parentNode.removeChild(merge.footer[0]);
            merge.footer = null;
          }
//          if (pref.test_mode['140']) {
          clg.remake_html_prep(tgt_th, clg.view!=='headline' && (!pref.test_mode['191'] || clg.view!=='catalog') && key); // overwrite tgt_th[0] to false
//          clg.remake_html_prep(tgt_th, clg.view==='page' || clg.view==='thread'); // overwrite tgt_th[0] to false
////            tgt_th[0] = false;
////            tgt_th[16].th = {pn:null, __proto__:tgt_th[16].lth.th};
////            tgt_th[16].posts = null;
//          } else {
//            if (tgt_th[16].icon_sticky) delete tgt_th[16].icon_sticky;
//            if (tgt_th[16].icon_showAlways) delete tgt_th[16].icon_showAlways;
//            gGEH.pns_all_keys.set(tgt_th[0], key);
          clg.view_attr_set(key, null, true); // doesn't call idx_reorder since tgt_th[20] is NOT changed.
//          }
          tgt_th[3] = null; // for catalog and headline
//          if (merge.isShown>=2) tgt_th[22] = null;
//          var idx = merge.lths.indexOf(liveTag.mems.getFromName(key));
//          merge.tgt_ths.splice(idx,1);
//          merge.lths.splice(idx,1);
          clg.footer.draw_merge(tgt_th,key,null); // update both footers.
          if (--merge.isShown===1) gGEH.pns_all_keys.set(merge.pn, merge.lths[0].key);
          else if (merge.isShown!==0) merge.reorder_req |= (idx===0)? 0x03 : 0x01;
//          else if (merge.isShown!==0 && idx===0) {
//            clg.view_attr_set(key, true, true, merge.pn, true);
//            clg.view_attr_set(merge.lths[0].key, null, true, merge.pn); // calls idx_reorder and changes order of clg.idxs here
//          }
////          if (--merge.isShown!==0 && merge.key===key) {
////            merge.key = merge.lths[0].key;
////            gGEH.pns_all_keys.set(merge.pn, merge.key);
////            clg.view_attr_set(merge.key, null, true); // calls idx_reorder and changes order of clg.idxs here
////          }
          if (merge.top_key===key) merge.top_key = null; // thread is overrated
          delete this.bases[key];
        }
      },
      merge_th: function(tgt_th, th, merge, key, clg){
        merge.tgt_ths[merge.tgt_ths.length] = tgt_th;
        merge.lths[merge.lths.length] = liveTag.mems.getFromName(key); // th.lth can't be used here, because th is null when this is called from func_search_ref_idx
        var domain_html = tgt_th[16].domain_html;
        if (clg.view==='catalog') tgt_th[3] = site2[domain_html].catalog_merge(tgt_th[0], merge, clg, key); // this.merge_th_catalog({pn:tgt_th[0], __proto__:tgt_th[16]}, merge, clg);
        else if (clg.view==='headline') tgt_th[3] = site2[domain_html].headline_merge(tgt_th[0], merge, clg, tgt_th);
        else {
          if (merge.isShown===1) merge.footer = [merge.pn.insertBefore(cnst.dom('<div style="clear:both"></div>'), merge.pn.firstChild)];
          if (merge.isShown!==0) this.merge_th_page({pn:tgt_th[0], posts:tgt_th[16].posts || th.posts, __proto__:tgt_th[16]}, merge, clg);
        }
//        merge.tgt_ths[merge.tgt_ths.length] = tgt_th;
//        merge.lths[merge.lths.length] = liveTag.mems.getFromName(key); // th.lth can't be used here, because th is null when this is called from func_search_ref_idx
        clg.footer.draw_merge(tgt_th,key,merge);
        merge.isShown++; // this.bases[th.key].isShown++;
//        if (merge.isShown>=2) tgt_th[22] = clg.threads[merge.lths[0].key][22];
//        else if (merge.isShown==1 && !tgt_th[22]) tgt_th[22] = {};
        if (merge.isShown===2) {
          gGEH.pns_all_keys.set(merge.pn,merge);
          gGEH.pns_all_keys.set(merge.footer[0], merge);
        }
//        if (merge.isShown>=2) {
//          if (clg.view==='headline') {
//            var attr_obj = clg.pref.filter.attr_list_obj2;
//            var key0 = merge.lths[0].key;
//            var attr_key = (merge.isShown===2 && attr_obj[key0])? key0 : attr_obj[key]? key : key0;
//            if (attr_key) clg.view_attr_set(attr_key, null, true, merge.pn);
//          }
//        }
        if (!pref.test_mode['148'] && merge.isShown>=2) merge.reorder_req |= (merge.isShown===2)? 7 : 5;
        gGEH.pns_all_keys.set(tgt_th[24][0],key);
      },
      remove_th_page: function(th, remove, merge, clg){
//          var dbt = comf.name2dbt(th.key);
//          if (!cataLog.threads[th.key][16].th) cataLog.threads[th.key][16].th = {domain:dbt[0], board:dbt[1], no:dbt[2], posts:th.posts.slice(), __proto__:cataLog.threads[th.key][16]}; // test patch, but these funcs are NOT reentrant.
//          var posts = (!remove)? th.posts.slice() : null;
//          var k = 0;
//          for (var i=th.posts.length-1;i>=0;i--) k = site2[th.domain_html].update_posts_remove_1(th, i, merge.pn, merge.isShown, merge, k);
        var pnode = (!remove)? site2[th.domain_html].page_json2html3_skelton(th) : null;
        site2[th.domain_html].update_posts_separate(th, merge, pnode, clg);
//          if (--merge.isShown==0) {
//            cataLog.catalog_obj2.func_hide_org(th.key);
//            this.shown.delete(merge);
//            cataLog.catalog_obj2.lazy_tgts.delete(merge);
//          }
//          delete this.bases[th.key];
//          if (!remove) {
//            cataLog.threads[th.key][0] = pnode;
//            gGEH.pns_all_keys.set(pnode,th.key);
//          }
////          if (!remove) {
////            var dbt = comf.name2dbt(th.key);
////            var pnode = site2[th.domain_html].page_json2html3_skelton({no:dbt[2]});
////            for (var i=0;i<posts.length;i++) site2[th.domain_html].update_posts_insert(posts,th.posts,i,i,pnode,false); // th.posts is NOT updated.
////            th.posts = posts;
////            site2[th.domain_html].page_json2html3_add_omitted_info({posts:th.posts, __proto__:liveTag.mems.getFromName(th.key)},th.posts,th.posts); // th.nof_xxx are required.
//////            site2[th.domain_html].page_json2html3_add_omitted_info(th,th.posts,th.posts); // th.nof_xxx are required.
////            cataLog.threads[th.key][0] = pnode;
////          }
        return pnode;
      },
      merge_th_page: function(th, merge, clg){
        var k=0;
        for (var i=0;i<th.posts.length;i++) {
//          cataLog.format_html.prepare_html_post(th, merge.posts[i]); // NEED TO CHECK CAPABILITY OF REENTRY.
          k = site2[th.domain_html].update_posts_merge_1(th, merge, i, k);
        }
//        merge.isShown++; // this.bases[th.key].isShown++;
        if (merge.lazy && merge.lazy_idx<merge.posts.length) clg.lazy_tgts.set(merge,th.key); // patch for merge_first. slightly redundant.
      },
//      remove_th_catalog: function(th, merge){
//        return site2[th.domain_html].catalog_json2html3_separate(th, merge);
//      },
//      merge_th_catalog: function(th, merge, clg){
//        return site2[th.domain_html].catalog_json2html3_merge(th.pn, merge, clg);
//      },
      base_arr: function(name){
        return pref_func.merge_obj5a_sc(name, pref3.proto.merge_list_obj2, null);
//        if (pref.test_mode['177']) { // cached
//          var cached = pref3.proto.merge_list_obj2c[name] || null;
//          if (ref!==cached) console.log('ERROR: base_arr:'+name+': '+cached+', ref:'+ref);
//        }
//        return ref;
      },
      base_arr_old: function(name){
        return pref_func.merge_obj5a_sc(name, pref3.proto.merge_list_obj2_old || {}, null);
      },
//      base_arr_raw: function(name, old){
//        return pref_func.merge_obj5a_sc(name, old? pref3.proto.merge_list_obj2_old : pref3.proto.merge_list_obj2, null);
//      },
//      base_arr: function(name, old){
//        return pref_func.merge_obj5a_sc(name, old? pref3.proto.merge_list_obj2_old : pref3.proto.merge_list_obj2, null);
//      },
      base_tgts: function(name, pf){
        if (pf.merge) return Object.keys(this.bases)[0] || name;
        if (!pf.merge_list) return null;
        var mems = this.base_arr(name);
        if (!mems) return null;
        for (var i=0;i<mems.length;i++) if (this.base_arr(mems[i])===mems && this.bases[mems[i]]) return mems[i]; // merge to existing merged thread // SCANNING!!! TOO SLOW!!!, but better than stannning whole this.bases
//        for (var key in this.bases) if (this.base_arr(key)===mems) return key; // merge to existing merged thread // SCANNING!!! TOO SLOW!!!
        return name;
      },
      query_merged_keys: function(name){
        var base = this.bases[name];
        return base? base.keys : [name];
      },
//      query_others: function(name){ // called from triage
//        var base = this.bases[name];
//        if (!base) return null;
//        return base.lths.map(function(v){return v.key;}).filter(function(v){return v!==name;});
////        return this.base_arr(name).filter(function(v){return v!==name && this.bases[v]===base;}, this);
//      },
      func_check_reorder: function(name, i){ // this refers catalog_obj2 
        var mb = this.merge_bases;
        var base = mb.bases[name];
        if (!base) return;
        var top = i;
        for (var j=0;j<base.lths.length;j++) {
          var key = base.lths[j].key;
          var idx = (key===name)? i : this.idxs.indexOf(key);
          if (idx<=top) {
            top = idx;
            base.top_key = key;
          }}
//        if (top===i) mb.shown.delete(base); // to bump in func_show
      },
      func_show: function(name,ref){ // this refers catalog_obj2
        var mb = this.merge_bases;
        var tgt_th = this.threads[name];
        var base = mb.bases[name]; //  || !tgt_th[1] && mb.query(name, tgt_th, null, false, this); // for merge_tails
//        var base = mb.bases[name]; // working code
//        if (!base && !this.threads[name][1]) { // CAUTION: this may cause conflict with all boards where threads[name][1]===1 always.
//          var tgts = mb.base_tgts(name);
//          if (tgts) {
//            var tgt_th = this.threads[name];
//            base = mb.query(name, tgt_th, null, tgts, false, this);
//          }
//        }
//        var base = (pref[cataLog.embed_mode].merge)? mb.query({pn:tgt_th[0], __proto__:tgt_th[16]}, 'ALL', false) : mb.bases[name];
        if (base) {
//          if (base.isShown!=0) cataLog.catalog_obj2.func_hide_org(name);
//          if (base.isShown==0) base.isShown++;
          if (base.top_key===name) this.func_show_org(name,ref); // bump if this thread is the top of merged threads.
//          if (!mb.shown.has(base)) this.func_show_org(name,ref); // bump if this thread is the top of merged threads.
          return true;
        } else return this.func_show_org(name,ref);
      },
      func_hide: function(name){ // this refers catalog_obj2
        var mb = this.merge_bases;
        var base = mb.bases[name];
        if (base) {
          if (base.isShown===1) {
            this.func_hide_org(name); // prior to mb.remove_th to use tgt_th[0]
            mb.shown.delete(base);
            if (this.view==='page' || this.view==='thread') this.lazy_tgts.delete(base);
          }
          mb.remove_th(name, this.threads[name], this);
          return true;
        } else return this.func_hide_org(name);
      },
      func_search_ref_idx: function(key, idx_now, merge_tail){ // this refers catalog_obj2
        var mb = this.merge_bases;
        var tgt_th = this.threads[key];
        var base_0 = mb.bases[key];
        var base = base_0 || !tgt_th[1] && mb.query(key, tgt_th, null, false, this); // for usual case -> all cases
//        if (!pref.test_mode['148'] && base) base.reorder_req = true; // base.reorder(key, this); // causes chattering // moved to insert in idx_xxxx because this invokes reorder always.
        if (!base || base_0 && merge_tail) return idx_now; // merge_tail for short cut, ignores return value // base_0 check is for additional auto merge by pref.*.merge_op.auto
        if (!pref.test_mode['179']) {
          if (!mb.shown.has(base)) {
            mb.shown.set(base, idx_now); // track first idx to remove indexOf // cant' be used in test_mode['132']
            base.top_key = key;
            mb.shownSet.add(mb.base_arr(key));
            return idx_now;
          } else return mb.shown.get(base);
        }
//        if (pref.test_mode['179']) return mb.shown.get(base) || idx_now;
        var idx = this.idxs.indexOf(base.top_key); // idx===-1 means base.top_key===null
        return (idx===-1 || idx>idx_now)? (base.top_key = key, idx_now) : idx; // bump up, and this can handle bumping down also,
      },
      func_search_ref: function(key){ // this refers catalog_obj2
        var mb = this.merge_bases;
        var base = mb.bases[key];
        return base && base.top_key!==key;
      },
      func_check_overrated: function(key){
        var mb = this.merge_bases;
        var base = mb.bases[key];
//        if (base && base.top_key===null && pref.test_mode['179']) console.log('overrated: '+key); // this line is not hit probably.
        return base && base.top_key===null;
      },
      func_track_shown: function(name, update_drawn_y, idx){ // this refers catalog_obj2
        var mb = this.merge_bases;
        var base = mb.bases[name];
        if (!base) return this.func_track_shown_org(name, update_drawn_y);
        var pf_merge = this.pref[this.mode].merge;
        var isTop = !pref.test_mode['179']? name===base.top_key : !mb.shown.has(base);
        if (isTop) {
//        var pn = base.pn;
//        if (!mb.shown.has(pn) || mb.shown.get(pn)===name) { // ===name for reentry.
          this.func_track_shown_org(name, !pf_merge && update_drawn_y);
          if (pref.test_mode['179']) {
            mb.shown.set(base, idx); // track first idx to remove indexOf in func_search_ref_idx // cant' be used in test_mode['132']
            base.top_key = name; // BUG, IT'S TOO LATE, top_key is used in func_search_ref before.
  //          mb.shown.add(base); // set(base, name); // track first name, but this causes BUG at test_mode['132'] // trial patch 'func_check_reorder was given.
            /*if (mb.shownSet)*/ mb.shownSet.add(mb.base_arr(name));
          }
        }
        if (base.lazy && base.lazy_idx<base.posts.length) this.lazy_tgts.set(base,name);
        if (pf_merge && update_drawn_y && (this.view==='page' || this.view==='thread')) {
          var step = (pref.test_mode['88'])? 1 : pref.proto.lazyDraw.merge_step;
          if (++this.post_count%step==0) this.drawn_y = this.threads[name][16].posts[0].pn.offsetTop;
          return isTop? 0x02 : 0x00;
        }
        return isTop? 0x02 : 0x01; // [1]: isTop, [0]: NOT updated this.drawy_y (to supress increasing lazy_next, true(1) must be strictly accurate, but false(0) is allowed if vague.
      },
      shown: new Map(), // base -> idx of first shown member
//      shown: new Set(), // Map(), // base -> name of first shown pair for search_ref
      shownSet: new Set(), // null,
      isShownForTails: function(name){
//        if (!pref.test_mode['149'] && this.bases[name]) return true; // for short cut
////        if (!this.shownSet) {
////          this.shownSet = new Set();
////          for (var base of this.shown.values()) this.shownSet.add(this.base_arr(base.key)); // name));
////        }
        var arr = this.base_arr(name);
        return arr? this.shownSet.has(arr) : false; // for faster execution, .has() consumes 0.3% of execution time.
//        return this.shownSet.has(this.base_arr(name));
      },
      func_track_reset: function(){ // this refers catalog_obj2
        var mb = this.merge_bases;
        mb.shown.clear();
        mb.shownSet.clear(); // = null;
        this.post_count = 0;
        this.lazy_tgts.clear();
        this.func_track_reset_org();
      },
      setup_hook: function(clg){
        clg.func_show_org = clg.func_show;
        clg.func_show     = this.func_show;
        clg.func_hide_org = clg.func_hide;
        clg.func_hide     = this.func_hide;
        clg.func_track_shown_org = clg.func_track_shown;
        clg.func_track_shown     = this.func_track_shown;
        clg.func_track_reset_org = clg.func_track_reset;
        clg.func_track_reset     = this.func_track_reset;
        clg.func_search_ref = this.func_search_ref;
        clg.func_search_ref_idx = this.func_search_ref_idx;
        clg.func_check_overrated = this.func_check_overrated;
        clg.post_count = 0;
        clg.lazy_tgts = new Map();
//        var lazy_tgts = new Map();
//        clg.lazy_tgts = lazy_tgts;
//        this.lazy_tgts = lazy_tgts;
////        cataLog.catalog_obj2.lazy_draw = function(name){ // this refers catalog_obj2
////          var mb = site2['DEFAULT'].update_posts_merge_bases;
////          var merge = mb.bases[name];
////          if (!merge || !merge.lazy || merge.lazy_idx>=merge.posts.length) lazy_tgts.set(merge,name);
////        };
        clg.func_lazy_draw = this.func_lazy_draw;
        clg.func_check_reorder = this.func_check_reorder;
        this.initialized = true;
      },
      func_lazy_draw: function(){ // this refers each clg.
        if (this.view==='page' || this.view==='thread') {
          var ref_height = this.get_ref_height(4);
          for (var merge of this.lazy_tgts.keys()) {
            var i = merge.lazy_idx;
            if (i>=1 && merge.posts[i-1].pn.offsetTop>ref_height) break;
            var j = 0;
            var step = (pref.test_mode['88'])? 1 : pref.proto.lazyDraw.merge_step;
            var th_domain_html = (pref.catalog.mimic_base_site)? site.nickname : this.threads[this.lazy_tgts.get(merge)][16].domain_html;
            while (i<merge.posts.length) {
              if (merge.lazy[i]) {
                if (j<i) j=i+1;
                while (j<merge.lazy.length && merge.lazy[j]) j++;
                site2[th_domain_html].update_posts_insert(merge.posts,merge.posts,i,j,merge.pn);
                merge.lazy[i] = false;
                var merge_class = merge.get_class(merge.posts[i].key_op);
                if (merge_class) merge.posts[i].pn.parentNode.classList.add(merge_class);
              }
              if (!pref.test_mode['111']) if (++this.post_count%step==0 && merge.posts[i].pn.offsetTop>ref_height) break; // slow.
              i++;
            }
            merge.lazy_idx = i;
            if (i>=merge.posts.length) this.lazy_tgts.delete(merge);
          }
        } else if (this.view==='headline' || this.view==='catalog') this.merge_bases.reorder_bases(this);
      },
      release_hook: function(){},
      auto_merge_list: function(name, clg){
////        var tgt_th = clg.threads[name];
//        var base = this.bases[name]; //  || this.query(name, tgt_th, null, false, clg); // called after show_catalog(), calling this.query is redundant.
//        if (base) {
          var arr = this.base_arr(name);
          if (arr) {
            var tgts = arr.slice();
            tgts.splice(tgts.indexOf(name),1);
            return tgts;
//            scan.scan_ui('mergeAuto', {tgts:tgts, options:{refresh:clg}});
//            if (pref[pClg.mode].merge_auto_lv_add) pClg.merge_bases.add_to_lv_list(tgts, pref[pClg.mode].merge_auto_lv, pClg);
          }
////          for (var key in pref3.proto.merge_list_obj2) if (name!==key && this.base_arr(key)===arr) tgts[tgts.length] = key;
////          scan.scan_ui('mergeAuto', {tgts:tgts, options:{refresh:clg}});
//        }
      },
//      add_to_list_fromUI: function(dst, src, clg, no_insert){ // moved to Clg.prototype.merge_cross_links_fromUI
//        if (!clg.threads[src]) scan.scan_ui('mergeUI', {tgts:[src], options:{refresh:clg}});
////        var pn13_doms = pref_func.settings.pn13 && pref_func.settings.pn13.getElementsByTagName('*') || {};
//        this.add_to_lv_list_pf(dst, src, clg, pref.proto.merge_btn_lv);
////        if (pref.proto.merge_btn_lv_add) { // working code
////          var lv = pref.proto.merge_btn_lv_inc && (parseInt(pref3.proto.merge_lv_obj2[dst],10) + pref.proto.merge_btn_lv_hop) || pref.proto.merge_btn_lv;
////          this.add_to_lv_list(src, lv, clg);
////        }
//////        if (pref.proto.merge_btn_lv_add) { // working code
//////          var key_lv = new RegExp('(^|,)\\s*'+name+'\\s*:\\s*\\d+(,|\n|$)','mg');
//////          var str_lv = pref.proto.merge_lv_str.replace(key_lv,',')+','+name+':'+pref.proto.merge_btn_lv+',\n';
//////          pref.proto.merge_lv_str = str_lv.replace(/,,+/g,',').replace(/^,/g,'').replace(/\n,/g,'\n').replace(/\n\n+/g,'\n').replace(/^\n/,''); // see 'triage_exe'
//////          var dom_lv = pn13_doms['proto.merge_lv_str'];
//////          pref_func.apply_prep(dom_lv || cnst.dom('<textarea name="proto.merge_lv_str"></textarea>'), false, true, null, true);
//////          if (!dom_lv) clg.onchange_funcs['*w/.merge_lv_str']();
//////        }
//        var mg_mode = clg.pref[clg.mode].merge_mode==='all'? 'merge' : 'merge_list';
//        if (!clg.pref[clg.mode][mg_mode]) {
//          clg.pref[clg.mode][mg_mode] = true;
//          pref_func.apply_prep(clg.components[mg_mode], false, true, clg.mode!=='float');
//        }
//        if (mg_mode==='merge_list') this.add_to_list_1(dst, src, clg, no_insert);
////        if (clg.pref[clg.mode].merge_mode==='all') {
////          if (!clg.pref[clg.mode].merge) {
////            clg.pref[clg.mode].merge = true;
////            pref_func.apply_prep(clg.components.merge, false, true, clg.mode!=='float');
////          }
////        } else {
////          this.add_to_list_1(key_dst, name, clg, null, true);
//////        var str = pref.proto.merge_list_str.replace(/\s*\+/g,',+'); // working code
//////        var dst_arr = this.base_arr(key_dst);
//////        var src_arr = this.base_arr(name);
//////        if (!dst_arr || !src_arr || dst_arr!==src_arr) {
//////          var key = new RegExp('(^|,)\\s*\\+*\\s*'+key_dst+'\\s*(,|\n|$)','mg');
//////          var str_addend = ',+'+name+',';
//////          var idx = str.search(key);
//////          var str_new = (idx!=-1)? str.replace(key,'$&'+str_addend) : str + key_dst + str_addend;
//////          pref.proto.merge_list_str = str_new.replace(/,+\+/g,'+').replace(/,,+/g,',').replace(/^,/g,'').replace(/\n,/g,'\n').replace(/\n\n+/g,'\n').replace(/^\n/,''); // see 'triage_exe'
//////          var dom = pn13_doms['proto.merge_list_str'];
//////          pref_func.apply_prep(dom || cnst.dom('<textarea name="proto.merge_list_str"></textarea>'), false, true, null, true);
//////          if (!dom) pref_func.settings.onchange_funcs['*.merge_list_str']();
//////        }
////          if (!clg.pref[clg.mode].merge_list) {
////            clg.pref[clg.mode].merge_list = true;
////            pref_func.apply_prep(clg.components.merge_list, false, true, clg.mode!=='float');
//////            pref_func.settings.pnOrDummy(clg.mode+'.merge_list').checked = true;
////////          var dom_cb = pn13_doms[pClg.mode+'.merge_list'];
////////          if (dom_cb) dom_cb.checked = true;
//////            clg.onchange_funcs['*w/.merge_list']();
////          }
////        }
//      },
      add_to_lv_list_pf: function(dst, src, clg, pf){
        if (!pf.lv_add) return;
        var lv = pf.lv_inc && (parseInt(pref3.proto.merge_lv_obj2[dst],10) + 1) || pf.lv_def;
        this.add_to_lv_list(src, lv, clg);
      },
      add_to_lv_list: function(name, lv, clg){
//if (pref.test_mode['193']) {
        var tgts = pref3.proto.merge_lv_obj2._revise(name, lv); // ._revise can accept array of names.
        if (clg.pref[clg.mode].merge_lv && Object.keys(tgts).length>0) clg.onchange_funcs['*w/.merge_lv_str'](null, clg.mode, tgts);
////        if (!Array.isArray(name))            pref.proto.merge_lv_str = pref3.proto.merge_lv_obj2._revise(name,    lv, rm, pref.proto.merge_lv_str);
////        else for (var i=0;i<name.length;i++) pref.proto.merge_lv_str = pref3.proto.merge_lv_obj2._revise(name[i], lv, rm, pref.proto.merge_lv_str);
////        pref_func.settings.pnOrDummy('proto.merge_lv_str').value = pref.proto.merge_lv_str;
//} else { // working code
//        if (Array.isArray(name)) {
//          for (var i=0;i<name.length;i++) this.add_to_lv_list(name[i], lv, i===name.length-1? clg : null);
//          return;
//        }
//        var key = new RegExp('(^|,)\\s*'+name+'\\s*:\\s*\\w+\\s*(,|\n|$)','mg');
//        var str = pref.proto.merge_lv_str.replace(key,'');
//        pref.proto.merge_lv_str = pref_func.fmt4str2_2(str, lv!==null && (name+':'+lv));
//        if (!clg) return;
//        pref_func.apply_prep_1n('proto.merge_lv_str', false, null, null, true); // make obj
////        var dom = pref_func.settings.pn13 && pref_func.settings.pn13.getElementsByTagName('TEXTAREA')['proto.merge_lv_str'];
////        pref_func.apply_prep(dom || cnst.dom('<textarea name="proto.merge_lv_str"></textarea>'), false, true, null, true);
//        clg.onchange_funcs['*w/.merge_lv_str'](null, clg.mode, typeof(name)==='string'? [name] : name);
////        /*if (!dom)*/ clg.onchange_funcs['*w/.merge_lv_str']();
//}
      },
      add_to_list_1: function(dst, name, clg, no_insert, prep_only){
        var sarr = this.base_arr(name);
        var darr = dst && this.base_arr(dst);
        if (!dst && !sarr || dst && sarr && sarr===darr) return;
//if (pref.test_mode['192']) {
        var tgts = pref3.proto.merge_list_obj2._revise(dst, name, no_insert);
//        pref_func.settings.pnOrDummy('proto.merge_list_str').value = pref.proto.merge_list_str = retval.str;
        clg.onchange_funcs['*w/.merge_list_str'](null, clg.mode, tgts, prep_only);
//} else { // working code
//        if (pref.debug_mode['41']) {
//          var old_old = pref3.proto.merge_list_obj2_old;
//          var now = pref3.proto.merge_list_obj2;
//          var old_org = this.debug_copy(old_old||now);
//          var str_old = pref.proto.merge_list_str;
//        }
//        var str = pref.proto.merge_list_str;
////        if (sarr && merge==='unmerge_once') str = this.add_to_list_2(null, name, str); // erase once for GUI
//        str = pref_func.add_to_list_2(dst, name, str, no_insert); // erase, add or insert
//        if ((dst===null || darr || no_insert) && sarr && sarr.length===2) {
//          var rest = sarr[sarr.indexOf(name)? 0 : 1];
//          var idx = rest.indexOf('/');
//          var idxl = rest.lastIndexOf('/');
//          if (idx>0 && idx!==idxl && idxl!==rest.length-1) str = pref_func.add_to_list_2(null, rest, str); // erase // check if fullname
//        }
//        pref.proto.merge_list_str = str;
//        pref_func.apply_prep_1n('proto.merge_list_str', false, null, null, true); // make obj
////        if (pref.test_mode['177']) pref3.proto.merge_list_obj2c_update(name); // name is NOT registered to gClg.AllThreads yet.
////        var dom = pref_func.settings.pn13 && pref_func.settings.pn13.getElementsByTagName('TEXTAREA')['proto.merge_list_str'];
////        pref_func.apply_prep(dom || cnst.dom('<textarea name="proto.merge_list_str"></textarea>'), false, null, null, true); // make obj
//        var tgts = !dst || darr? [name] : (sarr && !no_insert)? [dst] : [dst,name];
////        var tgts = comf.Array_toObj((sarr||[]).concat(this.base_arr(name)||[]), null); // sarr was made from obj2_old at this time.
//        clg.onchange_funcs['*w/.merge_list_str'](null, clg.mode, tgts, prep_only); // , merge);
//        if (pref.debug_mode['41']) { // changes old directly, can't be before drawing.
//          var now_org = pref3.proto.merge_list_obj2;
////          if (sarr && merge==='unmerge_once') var old = now._revise(old_old, null, name);
//          var retval = now._revise(dst, name, no_insert, str_old, old_old);
////          var old = retval.old;
////          for (var i in old) if (old[i])      this.debug_error_check(name,dst,i,old,old_org,'old!==old_org');
////          for (var i in old_org) if (!old[i]) this.debug_error_check(name,dst,i,old_org,old,'old_org!==old');
//          for (var i in now)                  this.debug_error_check(name,dst,i,now,now_org,'now!==now_org', old_org);
//          for (var i in now_org) if (!now[i]) this.debug_error_check(name,dst,i,now_org,now,'now_org!==now', old_org);
//          for (var db in now[':REV'])     for (var i in now[':REV'][db])     if (now[':REV'][db][i]!==now_org[':REV'][db][i]) console.log('ERROR: now3!==now_org3, '+db+i);
//          for (var db in now_org[':REV']) for (var i in now_org[':REV'][db]) if (now_org[':REV'][db][i]!==now[':REV'][db][i]) console.log('ERROR: now_org3!==now3, '+db+i);
//          if (retval.str!==pref.proto.merge_list_str) {
//            for (var i=0;i<retval.str.length;i++) if (retval.str[i]!==pref.proto.merge_list_str[i]) break;
//            console.log('ERROR: str at '+i+'\n'+retval.str.slice(i<10? 0:i-10,i+100)+'\n'+pref.proto.merge_list_str.slice(i<10? 0:i-10,i+100));
//          }
//        }
//}
      },
      debug_copy: function(src){
        var obj = {};
        for (var i in src) obj[i] = Array.isArray(src[i])? src[i].slice() : src[i];
        return obj;
      },
      debug_error_check: function(name,dst,i,src0,src1,msg, old){
        if (i!==':REV' && i!==':F' && i!=='_revise' && i!=='_rm' && i!=='_str') if (!src1[i] || src0[i].join()!==src1[i].join()) console.log('ERROR: ',name,dst,msg+': '+i+': '+src0[i].join()+': '+(Array.isArray(src1[i])? src1[i].join() : src1[i])+': '+(old? old[i] : '----'));
      },
      hide_merge_tgts: function(clg, check_func, tgts){ // , merge){
        if (!tgts) {
          tgts = [];
          for (var name in clg.threads) if (clg.threads[name] && check_func.call(this,name)) tgts[tgts.length] = name; // clg.threads check for multicatalog
        }
        clg.func_hide_all(tgts);
//        pref3.proto.merge_list_obj2_old = null;
        clg.func_track_reset();
        if (!this.initialized) this.setup_hook(clg);
        return tgts; // Object.keys(tgts).length;
      },
      onchange_funcs: comf.Object_defProps(pref_func.settings.onchange_funcs, { // dummy prop; this property itself is not used.
        '*w/.merge_list_str': function(e,src, tgts, prep_only){ // , merge){
//          var clg = this['.'];
/*          if (!clg)*/ gClg.BroadcastEventByMode(src.split('.')[0], this['*w/.merge_list_str_1'], arguments); // broadcast always
//          else this['*w/.merge_list_str_1'](e, src, prep_only);
          pref3.proto.merge_list_obj2_old = null;
        },
        '*w/.merge_list_str_1': function(e,src, tgts, prep_only){ // , merge){
          var clg = this['.'];
          if (!clg.pref[clg.mode].merge_list) return;
          var tgts_draw = clg.merge_bases.hide_merge_tgts(clg, function(name){
            var now = this.base_arr(name);
            var old = this.base_arr_old(name);
            return (now)? (!old || now.join()!==old.join()) : old;}, pref.test_mode['210']? null : tgts);
//            return (now)? (!old || now.join()!==old.join() && (!tgts_hide || this.bases[this.base_tgts(name, clg.pref[clg.mode])]!==this.bases[name])) : old;}, pref.test_mode['210']? null : tgts, pref.test_mode['210']? null : tgts_hide); //  !merge check for rewinding overrated merged threads when its top thread is unmerged. // this.bases will be updated after show_catalog, but this.base_tgts was updated because it is calculated from this.base_arr. // <- BUG. this.base_tgts traverses this.bases which is NOT updated before show_catalog. merge==='no_insert' hits this case because it may include unmerging.
//            return (now)? (!old || now.join()!==old.join() && (!merge || merge==='no_insert' || this.bases[this.base_tgts(name, clg.pref[clg.mode])]!==this.bases[name])) : old;}, tgts, merge); // now.join()!==old.join() may be redundant. // !merge check for rewinding overrated merged threads when its top thread is unmerged. // this.bases will be updated after show_catalog, but this.base_tgts was updated because it is calculated from this.base_arr. // <- BUG. this.base_tgts traverses this.bases which is NOT updated before show_catalog. merge==='no_insert' hits this case because it may include unmerging.
//            return (now)? (!old || now.join()!==old.join() && (!merge || this.bases[this.base_tgts(name, clg.pref[clg.mode])]!==this.bases[name])) : old;}, tgts, merge); // now.join()!==old.join() may be redundant. // !merge check for rewinding overrated merged threads when its top thread is unmerged. // this.bases will be updated after show_catalog, but this.base_tgts was updated because it is calculated from this.base_arr.
          if (!tgts) clg.dirty |= 0x10; // may be redundant with try_all in show_catalog.
          if (!prep_only && tgts_draw.length>0) clg.show_catalog_buf(tgts_draw);
        },
        '*w/.merge_list': function(e, name){
          var clg = this['.'];
          if (!clg) {gClg.BroadcastEventByMode(name.split('.')[0], this['*w/.merge_list'], arguments); return;}
          clg.merge_bases.hide_merge_tgts(clg, function(name){return this.base_arr(name);}); //  || this.base_arr_old(name);});
          clg.dirty |= 0x10;
          clg.show_catalog(0);
        },
        '*w/.merge': function(e, name){
          var clg = this['.'];
          if (!clg) {gClg.BroadcastEventByMode(name.split('.')[0], this['*w/.merge'], arguments); return;}
          clg.merge_bases.hide_merge_tgts(clg, null, clg.threads);
          clg.dirty |= 0x10;
          if (clg.pref[clg.mode].merge) clg.order.ordering_old = clg.order.ordering;
          clg.components.ordering.onfocus();
          clg.order.ordering = (clg.pref[clg.mode].merge)? clg.idx_idxMergeAll : clg.order.ordering_old || 0; // default value for all merge valid at initial
          pref_func.apply_prep(clg.components.ordering,false,true); // call show_catalog();
          clg.components.ordering.onblur();
          clg.components.ordering.disabled = clg.pref[clg.mode].merge;
        },
        '*w/.merge_lv_str': function(e, src, tgts){
//          var clg = this['.'];
/*          if (!clg)*/ gClg.BroadcastEventByMode(src.split('.')[0], this['*w/.merge_lv_1'], [e, src, true, tgts]); // broadcast always
//          if (pref[clg.view].merge_lv) this['*w/.merge_lv_1'](e,src, true, tgt); // changed from clg.pref[clg.mode].merge_lv
          pref3.proto.merge_lv_obj2_old = null; // this must be after broadcasting.
        },
        '*w/.merge_lv': function(e, src){
          var clg = this['.'];
          if (!clg) gClg.BroadcastEventByMode(src.split('.')[0], this['*w/.merge_lv_1'], arguments);
          else this['*w/.merge_lv_1'](e, src);
        },
        '*w/.merge_lv_1': function(e, src, from_str, tgts_in){
          var clg = this['.'];
          if (!clg.pref[clg.mode].merge && !clg.pref[clg.mode].merge_list) return;
          var mb = clg.merge_bases;
          var tgts = tgts_in || clg.threads;
          var pf_mglv = clg.pref[clg.mode].merge_lv;
          for (var name in tgts) {
            if (!clg.threads[name] || !clg.threads[name][1]) continue;
            var base = mb.bases[name];
            if (!base) continue;
            var now = base.get_class(name);
            var old = !from_str? null : tgts_in? tgts_in[name] && pref.cpfx+'merged'+tgts_in[name] : base.get_class(name,true,true); // tgts_in[name] is null if undefined, see obj2proto_merge_lv._revise
            if (!from_str || now!=old) {
              var posts = (clg.view==='page' || clg.view==='thread')? clg.threads[name][16].posts : [{pn:base.pn}];
              if (from_str || !pf_mglv) mb.class_remove_all(posts, from_str? old : now);
              if (from_str ||  pf_mglv) mb.class_add_all(posts,now);
            }
          }
//          pref3.proto.merge_lv_obj2_old = null; // this must be moved to after broadcasting.
        },
      }),
//      onchange_merge: function(e){ // working code
//        var mb = site2['DEFAULT'].update_posts_merge_bases;
//        var et_name1 = (e.target.name || e.target.getAttribute('name')).split('.')[1];
//        if (et_name1==='merge_list_str' && !pref[cataLog.embed_mode].merge_list) return;
//        var check_tgts = (et_name1==='merge')? function(){return true;}
//        : (et_name1==='merge_list')? function(name){return mb.base_arr(name) || mb.base_arr(name,true);}
//                       : function(name){
//                           var now = mb.base_arr(name);
//                           var old = mb.base_arr(name,true);
//                           return (now)? (!old || now.join()!==old.join()) : old;};
//        for (var name in cataLog.threads) {
//          var tgt_th = cataLog.threads[name];
//          if (tgt_th[1] && check_tgts(name))
//            if (cataLog.catalog_obj2.func_hide(name)) tgt_th[1] = false;
//        }
//        pref3.proto.merge_list_obj2_old = null;
//        cataLog.catalog_obj2.func_track_reset();
//        if (!mb.initialized) mb.setup_hook();
//        if (et_name1==='merge') {
//          if (pref[cataLog.embed_mode].merge) pref3.catalog.order.ordering_old = pref.catalog.order.ordering;
//          cataLog.components.ordering.onfocus();
//          pref.catalog.order.ordering = (pref[cataLog.embed_mode].merge)? 7 : pref3.catalog.order.ordering_old; // 7:/creation
//          pref_func.apply_prep(cataLog.components.ordering,false,true); // call show_catalog();
//          cataLog.components.ordering.onblur();
//          cataLog.components.ordering.disabled = pref[cataLog.embed_mode].merge;
//        } else cataLog.show_catalog();
//      },
//      onchange_lv: function(e){
//        if (!pref[cataLog.embed_mode].merge && !pref[cataLog.embed_mode].merge_list) return;
//        var et_name = (e.target.name || e.target.getAttribute('name')).split('.')[1];
//        if (et_name==='merge_lv_str' && !pref[cataLog.embed_mode].merge_lv) return;
//        var mb = site2['DEFAULT'].update_posts_merge_bases;
//        for (var name in cataLog.threads) {
//          if (!cataLog.threads[name][1]) continue;
//          var base = mb.bases[name];
//          if (!base) continue;
//          var now = base.get_class(name);
//          var old = base.get_class(name,true,true);
//          if (et_name==='merge_lv' || now!=old) {
//            var posts = cataLog.threads[name][16].posts;
//            if (et_name!=='merge_lv' || !pref[cataLog.embed_mode].merge_lv) mb.class_remove_all(posts,old);
//            if (et_name!=='merge_lv' ||  pref[cataLog.embed_mode].merge_lv) mb.class_add_all(posts,now);
//          }
//        }
//        pref3.proto.merge_lv_obj2_old = null;
//      },
    },
////////    update_posts_merge_base: null,
    update_posts_replace_prep: function(posts,posts_old,num) { // use newer posts as much as possible // concate posts by no. existence of posts_old was checked in caller.
      if (!posts || posts.length<=1) return (num<0 || posts_old.length<=num+1)? posts_old : (num==0)? posts_old.slice(0,1) : posts_old.slice(0,1).concat(posts_old.slice(-num));
      num = (num>=0)? num+1 : posts_old.length + posts.length -1;
      if (num==posts.length) return posts;
      if (num< posts.length) return posts.slice(0,1).concat(posts.slice(posts.length - num + 1)); // cut
      var first_no = posts[1].no;
      var i=1;
      while (i<posts_old.length && posts_old[i].no<first_no) i++;
      if (i===1) return posts;
      var start = (i>num-posts.length)? i-(num-posts.length) : 1;
      return posts.slice(0,1).concat(posts_old.slice(start,i),posts.slice(1));
    },
    update_posts0_class: function(pn,search_result) {
      if (search_result) pn.classList.remove('CatChan_search_miss');
      else               pn.classList.add('CatChan_search_miss');
    },
    update_posts_replace: function(th,th_old,pnode,merge,show, posts_used, clg, deactivate) {
      if (merge) {
        pnode = merge.pn;
        show  = merge.isShown;
      }
      if (th_old.posts) {
if (!pref.test_mode['140']) {
        site2[th.domain].page_json2html3_add_omitted_info(th,th_old, pref[clg.view].use_expander_always);
} else {
        var info_old = th_old.parse_funcs_html.get_omitted_info(th_old.posts[0]); // (th_old.posts[0].pn)? : null; // for lazy_draw in merge, but this is patched in 'update_posts_merge_1'.
        var info_new = (th.posts[0].pn && (pref.test_mode['139'] || th.posts[0]!==th_old.posts[0]))? th.parse_funcs_html.get_omitted_info(th.posts[0]) : site2[th.domain_html].page_json2html3_add_omitted_info(th,null,th.posts);
        if (info_old) {
//          if (info_new) th_old.parse_funcs_html.replace_omitted_info(info_old, info_new);
//          else info_old.parentNode.removeChild(info_old);
          th_old.parse_funcs_html.replace_omitted_info2(info_old, info_new, th); // REDUNDANT when check new replies at initial in page view.
        } else if (info_new) th_old.parse_funcs_html.set_omitted_info(th_old.posts[0], info_new);
}
////        var nos = {}; // working code.
////        for (var i=1;i<th.posts.length;i++) nos[th.posts[i].no] = null;
////        for (var i=th_old.posts.length-1;i>=1;i--) 
////          if (nos[th_old.posts[i].no]===undefined) {
////            this.update_posts_remove(th_old,i,pnode);
////            th_old.posts.splice(i,1);
////          }
        var k = -1;
        var j = th.posts.length-1;
//        var scroll_back = 0;
//        var now_height = (scroll_lock)? brwsr.document_body.scrollTop : 0; // WILL BE CHANGED TO get_now_height for multi inscance
        var editings = null;
        for (var i=th_old.posts.length-1;i>=1;i--) {
          while (j>1 && th.posts[j].no>th_old.posts[i].no) j--;
          var editing = th_old.posts[i].editing && th_old.posts[i].pn && th_old.posts[i]!==th.posts[j]; // meguca returns old data sometime since they uses cache.
          // Meguca uses caches of 30 seconds, so it returns old data sometime, and last posts aren't contained in old data sometimes.
          // CatChan takes this as a situation of "the posts was removed", so deleted_posts are fluctuate,
          // and th.posts may contain shallow copies of th_old.posts because deleted_posts are merged with live posts.
          // So, "insert-remove" method causes an error when the post is a shallow copy of an old post beacuse th.posts[j].pn.parentNode will be null.
          if (th.posts[j].no!=th_old.posts[i].no || editing) k = this.update_posts_remove_1(th_old, i, pnode, show, merge, k, clg);
          if (editing) if (editings) editings[editings.length] = th.posts[j]; else editings = [th.posts[j]];
//          else if (th_old.posts[i].editing && (th_old.posts[i].pn) && th_old.posts[i]!==th.posts[j]) { // meguca returns old data sometime since they uses cache.
////          } else if (th_old.posts[i].editing && (!merge || show) && (th_old.posts[i].pn)) {
//            var thq = liveTag.mems[th.domain][th.board][th.no].q;
//            cataLog.format_html.prepare_html_post(th, th.posts[j]);
//            this.popups_add_1(th, th.posts[j], false, thq, j===0, true);
//            if (pref[cataLog.embed_mode].backlink) if (thq[th.posts[j].no].backlinks) site2[th.domain_html].add_backlinks(th.posts[j].pn,thq[th.posts[j].no].backlinks, undefined, th);
//            th_old.posts[i].pn.parentNode.insertBefore(th.posts[j].pn,th_old.posts[i].pn);
//            th_old.posts[i].pn.parentNode.removeChild(th_old.posts[i].pn);
//            th_old.posts[i] = th.posts[j];
//            posts_used[posts_used.length] = th.posts[j];
//          }
        }
//        if (scroll_back) window.scrollTo(0,now_height + scroll_back);
      }
      this.update_posts_add(th,th_old,pnode,merge,show, posts_used, editings, deactivate);
    },
    update_posts_add: function(th,th_old,pnode,merge,show, posts_used, editings, deactivate) {
      if (th_old.posts && th.posts) {
        var thq = liveTag.mems[th.domain][th.board][th.no].q;
        var time_unit = (merge)? th.parse_funcs.time_unit : 1; // (merge)? for safety.
//        var first_no = th_old.posts[1].no; // working, but not verified enough.
//        if (th.posts[1].no<first_no) {
//          var end=1;
//          while (end+1<th.posts.length && th.posts[end+1].no<first_no) end++;
//          var i=end;
//          while (i>=1) {
//            this.update_posts_1(th,th_old,pnode,merge, i, 1, time_unit, thq); // inset before
//            th_old.posts.splice(1,0,th.posts[i--]);
//          }
//          if (merge && show) this.update_posts_insert_merge(th.posts.splice(0,end+1),1);
//        }
//        var th_old_posts_length = th_old.posts.length;
//        for (var i=th_old_posts_length;i<th.posts.length;i++) {
//          this.update_posts_1(th,th_old,pnode,merge, i, th_old.posts.length, time_unit, thq); // th_old.posts.length is live count, always add to end. // add after
//          th_old.posts[th_old.posts.length] = th.posts[i];
//        }
//        if (th.posts.length>1 && merge && show) this.update_posts_insert_merge(th.posts,th_old_posts_length);

        var k = -1;
        var j=(merge)? 0 : 1; // for filter output, random insert.
        for (var i=(merge)? 0 : 1;i<th.posts.length;i++) {
          while (j<th_old.posts.length && th.posts[i].no>th_old.posts[j].no) j++;
          if (j>=th_old.posts.length || th.posts[i].no!=th_old.posts[j].no) {
            var editing = editings && editings.indexOf(th.posts[i])!=-1;
            var thq = editing? liveTag.mems[th.domain][th.board][th.no].q : null; // must be here?
            cataLog.format_html.prepare_html_post(th, th.posts[i], null, null, null, deactivate, true);
            if (editing) {
//              this.popups_add_1(th, th.posts[i], false, thq, true);
              if (pref[cataLog.embed_mode].backlink) if (thq[th.posts[i].no].backlinks) site2[th.domain_html].add_backlinks(th.posts[i].pn,thq[th.posts[i].no].backlinks, undefined, th);
            }
            if (!merge) this.update_posts_insert(th.posts,th_old.posts,i,j,pnode); // th_old.posts.length is live count, always add to end.
            else k = this.update_posts_merge_1(th, merge, i, k);
//            this.update_posts_1(th,th_old,pnode,merge, i, j, time_unit, thq); // th_old.posts.length is live count.
            if (j<th_old.posts.length) th_old.posts.splice(j,0,th.posts[i]); // live array
            else th_old.posts[th_old.posts.length] = th.posts[i];
            posts_used[posts_used.length] = th.posts[i];
          }
          if (j<th_old.posts.length) j++;
        }
//        if (th.posts.length>1 && merge && show) this.update_posts_insert_merge(th.posts,0, th_old.posts[0].pn);
      }
    },
    update_posts_remove_1: function(th_old, i, pnode, isShown, merge, k, clg){
      var post = th_old.posts[i];
      if (isShown) if (pref[cataLog.embed_mode].scroll_lock) clg.show_catalog_scroll_lock.modified(post);
      if (!merge) {
        if (post.pn) this.update_posts_remove(th_old,i,pnode); // MAY CAUSE A BUG. post.pn may not have the same lifetime as th.pn
      } else {
        k = (k==-1)? merge.posts.indexOf(post) // for faster search
                   : merge.posts.lastIndexOf(post, k);
        if (k==-1) k = merge.posts.lastIndexOf(post); // 4chan sometimes returns out-of-order posts.
        if (k>=0) if (merge.remove(k)) this.update_posts_remove(th_old,i,pnode);
      }
//      scroll_back += this.update_posts_remove(th_old,i,pnode,now_height); // SLOW.
      th_old.posts.splice(i,1);
      return k;
    },
    update_posts_merge_1: function(th, merge, i, k){
      var ref_time = th.posts[i].time_tu;
      var m_posts = merge.posts;
      if (k==-1) k = m_posts.length-1;
      while (k>0 && m_posts[k].time_tu>=ref_time) k--; // for faster search // 4chan sometimes returns out-of-order posts.
      while (k<m_posts.length && (m_posts[k].time_tu <ref_time || 
                                  m_posts[k].time_tu==ref_time && m_posts[k].no<=th.posts[i].no)) k++;
      if (merge.lazy) merge.lazy_draw(k);
      if (!merge.lazy || (!pref.test_mode['89'] && i==0)) {
        this.update_posts_insert(th.posts,m_posts,i,merge.lazy? merge.draw_1_pos(k)-1:k, merge.pn); // th_old.posts.length is live count, always add to end if !merge.lazy.
        var merge_class = merge.get_class(th.key);
        if (merge_class) th.posts[i].pn.parentNode.classList.add(merge_class);
      }
//      if (i==0) th.posts[0].isOP = true;
//      if (merge.lazy) merge.lazy_draw(k);
//      else this.update_posts_insert(th.posts,m_posts,i,k,merge.pn); // th_old.posts.length is live count, always add to end.
//      if (!pref.test_mode['89'] && merge.lazy && i==0) // required, sometimes OP has omitted_info outside of the OP. 
//        if (pref[cataLog.embed_mode].merge) this.update_posts_insert(th.posts,m_posts,i,merge.draw_1_pos(k)-1,merge.pn);
////        if (pref.test_mode['89']) this.update_posts_insert(th.posts,m_posts,i,merge.draw_1_pos(k)-1,merge.pn,merge); // i!=0 for ommited info, which doesn't expect lazy drawing. -1 of dst[j] is because dst isn't updated yet.
//        else th.posts[0].isOP = true;
      if (k<m_posts.length) m_posts.splice(k,0,th.posts[i]); // live array
      else m_posts[m_posts.length] = th.posts[i];
      return k;
    },
    update_posts_separate: function(th, merge, pn_sep, clg){
      var merge_class = merge.get_class(th.key, true);
      var k=0;
      for (var i=0;i<th.posts.length;i++) {
        var post = th.posts[i];
        k = merge.posts.indexOf(post, k);
        if (k==-1) k = merge.posts.indexOf(post); // 4chan sometimes returns out-of-order posts.
        var exist = (k!=-1)? merge.remove(k) : false;
        if (merge.isShown) if (pref[cataLog.embed_mode].scroll_lock) clg.show_catalog_scroll_lock.modified(post);
        if (merge_class && post.pn.parentNode) post.pn.parentNode.classList.remove(merge_class);
        if (pn_sep) this.update_posts_insert(th.posts,[],i,i,pn_sep);
        else if (exist) this.update_posts_remove(th,i,merge.pn);
      }
    },
//    update_posts_1: function(th,th_old,pnode,merge, tgt, pos, time_unit, thq) { // working code.
//      if (!merge) this.update_posts_insert(th.posts,th_old.posts,tgt,pos,pnode,merge); // th_old.posts.length is live count, always add to end.
////      this.format_pn(th.posts[tgt].pn, th_old.q[th.posts[tgt].no]); // WILL BE THIS.
////      if (time_unit!==1) th.posts[tgt].time *= time_unit; // BUG!!!, THIS DOESN'T ALLOW MULTIPLE ENTRY, CAN'T BE USED FOR POSTS SEARCH.
//    },
////    update_posts_add: function(th,th_old,pnode,merge,show) { // working code.
////      if (th_old.posts && th.posts) {
////        var thq = liveTag.mems[th.domain][th.board][th.no].q;
////        var last_no = th_old.posts[th_old.posts.length-1].no;
////        if (th.posts[th.posts.length-1].no==last_no || th.posts.length<=1) return;
////        //        var i=1;
////        //        while (i<th.posts.length && th.posts[i].no<=last_no) i++;
////        var i=th.posts.length;
////        while (i>1 && th.posts[i-1].no>last_no) i--;
////        if (i>1) th.posts.splice(1,i-1);
////        if (pref[cataLog.embed_mode].t2h_num_of_posts>=0 && th.posts.length > pref[cataLog.embed_mode].t2h_num_of_posts+1) {
////          th.posts.splice(1,th.posts.length - pref[cataLog.embed_mode].t2h_num_of_posts -1);
////          this.page_json2html3_add_omitted_info(th, th_old.posts, th.posts);
////        }
////        var time_unit = (merge)? th.parse_funcs.time_unit : 1; // (merge)? for safety.
////        for (var i=1;i<th.posts.length;i++) {
////          if (!merge) this.update_posts_insert(th.posts,th_old.posts,i,th_old.posts.length,pnode,merge); // th_old.posts.length is live count, always add to end.
////            this.format_pn(th.posts[i].pn, (thq)? thq[th.posts[i].no] : null);
////          //          this.format_pn(th.posts[i].pn, th_old.q[th.posts[i].no]); // WILL BE THIS.
////          if (time_unit!==1) th.posts[i].time *= time_unit;
////          th_old.posts[th_old.posts.length] = th.posts[i];
////        }
////        if (th.posts.length>1 && merge && show) this.update_posts_insert_merge(th.posts,1);
////      }
////    },
////////    update_posts_insert_merge: function(src,start, src0_pn) { // working code, but very slow because of based on HTML.
////////      if (pref[cataLog.embed_mode].scroll_lock) cataLog.show_catalog_scroll_lock.set();
////////      var i,j;
////////      var myself = this.update_posts_prep_merge();
////////      var dst = myself.posts;
////////      var pnode = myself.pn;
////////      src = src.slice();
////////      src[0].pn = src0_pn; // patch for posts search
////////      for (var i=src.length-1;i>=0;i--) // sort by timestamp only, so if there is, that is already sorted.
////////        if (src[i].pn) {
////////          var tgt = src[i].pn.parentNode;
////////          while (tgt) {
////////            if (tgt===pnode) {src.splice(i,1);break;} // if other sorting scheme is needed, change this to 'update_posts_remove'
////////            else tgt = tgt.parentNode;
////////          }
////////        }
////////      if (src.length===0) return;
////////      if (start===0) {
////////        i = start;
////////        j = 0;
////////        var step = dst.length & 0x7ffffff0;
////////        while (i<src.length) {
////////          if (step>1) while (j+step<dst.length && (dst[j+step].time_tu<src[i].time_tu || dst[j+step].time_tu==src[i].time_tu && dst[j+step].no<=src[i].no)) j += step+1;
////////          while (j<dst.length && (dst[j].time_tu<src[i].time_tu || dst[j].time_tu==src[i].time_tu && dst[j].no<=src[i].no)) j++;
////////          if (j<dst.length) site2[site.nickname].update_posts_insert(src, dst, i++, j, pnode);
////////          else { // can't refer nodes after inserted one.
////////            var k = src.length-1;
////////            site2[site.nickname].update_posts_insert(src, dst, k--, j, pnode);
////////            while (k>=i) {
////////              site2[site.nickname].update_posts_insert(src, src, k, k+1, pnode); // can't refer nodes after inserted one.
////////              k--;
////////            }
////////            break;
////////          }
////////        }
////////      } else {
////////        i = src.length-1;
////////        j = dst.length;
////////        while (i>=start) {
////////          while (j>0 && (dst[j-1].time_tu>src[i].time_tu || dst[j-1].time_tu==src[i].time_tu && dst[j-1].no>src[i].no)) j--;
////////          site2[site.nickname].update_posts_insert(src, dst, i--, j, pnode);
////////          while (i>=start && !(j>0 && (dst[j-1].time_tu>src[i].time_tu || dst[j-1].time_tu==src[i].time_tu && dst[j-1].no>src[i].no))) { // dst is NOT updated. It's an object, not a live list.
////////            site2[site.nickname].update_posts_insert(src, src, i, i+1, pnode);
////////            i--;
////////          }
////////        }
////////      }
////////    },
////////    update_posts_prep_merge: function() {
////////      return (cataLog.embed_mode==='thread')? this.wrap_to_parse.get(document, site.nickname, site.board, 'thread_html', {thread:site.no})[0] :
////////                                              this.wrap_to_parse.get(document, site.nickname, site.board, 'page_html', {page:0})[0];
////////    },
////    update_posts_add: function(th,th_old,pnode) { // working code.
////      if (th_old.posts && th.posts) {
////        var thq = liveTag.mems[th.domain][th.board][th.no].q;
////        for (var i=1;i<th.posts.length;i++) {
////          if (th.posts[i].no>th_old.posts[th_old.posts.length-1].no) {
////            this.update_posts_insert(th,th_old,i,pnode);
////            this.format_pn(th.posts[i].pn, (thq)? thq[th.posts[i].no] : null);
//////            this.format_pn(th.posts[i].pn, th_old.q[th.posts[i].no]); // WILL BE THIS.
////            th_old.posts[th_old.posts.length] = th.posts[i];
////          }
////        }
////      }
////    },
////////    update_posts_add: function(th,th_old,pnode) { // working code.
////////      var ref = th_old.posts[0].nextSibling || null;
////////      for (var i=0;i<th.posts.length;i++) {
////////        if (th.posts[i].no>th_old.posts[th_old.posts.length-1].no) {
////////          pnode.appendChild(document.createElement('br'));
////////          pnode.appendChild(this.post_container(th.posts[i].pn || this.post_json2html(th.posts[i],th.posts.board),th.posts[i].no));
////////          th_old.posts[th_old.posts.length] = th.posts[i];
////////        }
////////      }
////////    },
    short_link:function(){return '';},
//    link_txt2html: function(txt){ // not used
//      return txt.replace(/>>(>(>[^/]+)*\/\w+\/)*(\d\d+)/g,function(m,p1,p2,p3){
//        var dbtp = [p2? p2.slice(1): site.nickname, p1? p1.slice(p1.indexOf('/')) : site.board, p3, p3];
//        return this.link_dbtp2html(dbtp);
//      }.bind(this));
//    },
    link_sanitizedTxt2html: function(txt, th){
      return txt.replace(/&gt;&gt;(&gt;(&gt;[^/]+)*\/\w+\/)*(\d\d+)/g,function(m,p1,p2,p3){
        var dbtp = [p2? p2.slice(4): site.nickname, p1? p1.slice(p1.indexOf('/')) : site.board, p3, p3];
        if (!th._links) th._links = [dbtp]; else th._links[th._links.length] = dbtp;
        return this.link_dbtp2html(dbtp, m); //  + (pref.test_mode['147']? Triage.prototype.MERGEE_button_html:'');
      }.bind(this));
    },
    link_dbtp2txt: function(dbtp, board, domain){
      return (dbtp[0]!==(domain||site.nickname)? '>>>>'+dbtp[0]+dbtp[1]+'/': (dbtp[1]!==(board||site.board)? '>>>'+dbtp[1] : '>>'))+ (dbtp[3]||dbtp[2]);
    },
    modify_a2anchor: function(pn){
      if (pn.childElementCount!=0 || pn.textContent[0]==='>') return;
      var domain = this.popups_posts.href2domain(pn.href);
      if (!domain || !site2[domain]) return;
      var tailer = pn.textContent.replace(pn.href,' ()');
      var dbtp = site2[domain].popups_href2dbtp(pn.href);
      pn.textContent = this.link_dbtp2txt(dbtp) + (tailer===' ()'? '' : tailer);
    },
    modify_href2html: function(th, domain, board, href){
      var dbtp = this.popups_href2dbtp(href);
      if (!th._links) th._links = [dbtp]; else th._links[th._links.length] = dbtp;
      return '<a href="'+href+'">'+this.link_dbtp2txt(dbtp, board, domain)+'</a>' + (pref.test_mode['147']? Triage.prototype.MERGEE_button_html:'');
    },
    page_json2html3_replace_expander: function(){}, // temporal
    page_json2html3_add_omitted_info: function(th, th_old, force_expander, from_initial) {
      if (!from_initial) {
        var posts = th.posts;
        var posts_deleted = th.lth.pd;
        var nof_posts_omitted = th.nof_posts - posts.length + (posts_deleted? posts_deleted.length : 0);
        if (nof_posts_omitted) {
          var nof_files = 0;
          for (var i=0;i<posts.length;i++) nof_files += posts[i].nofFiles;
          if (posts_deleted) {
            var nos_shown = {};
            for (var i=0;i<posts.length;i++) if (posts[i].deleted_after) nos_shown[posts[i].no] = null;
            for (var i=0;i<posts_deleted.length;i++) if (nos_shown[posts_deleted[i].no]===undefined) nof_files -= posts_deleted[i].nofFiles; // decrease nof_files to increase nof_files_omitted
          }
          var nof_files_omitted = th.nof_files - nof_files; // (+ files_in_not_shown_deleted_posts)
        }
      }
      if (!th_old.pn_summary) {
        var f_ex = force_expander || th.domain!==site.nickname && posts.length>1; //  || pref[cataLog.embed_mode].use_expander_always;
        if (nof_posts_omitted>0 || f_ex) th_old.pn_summary = this.page_json2html3_prep_omitted_info(th_old.posts? th_old.posts[0] : th.posts[0], th, f_ex);
      }
      if (th_old.pn_summary && !from_initial) th_old.pn_summary.textContent = this.page_json2html3_set_omitted_info(nof_posts_omitted, nof_files_omitted||0);
//      site2[th_old && th_old.domain_html || th.domain_html].page_json2html3_add_omitted_info_html(th, th_old, force_expander, nof_posts_omitted, nof_files_omitted||0); // 'th_old &&' is a patch for working regardless of pref.test_mode['140'], it can be removed when I fix the test_mode to true.
    },
    page_json2html3_cloneOPWithoutFooter: function(pn_src){
      if (!pn_src) return;
      var pn = pn_src.cloneNode(true);
      var footer = pn.getElementsByClassName(pref.cpfx+'footer')[0];
      if (footer) footer.parentNode.removeChild(footer);
      return pn;
    },
    post_json2html_fname_server: function(post){return post.tim+post.ext;},
    post_json2html_fname: function(post){return post.filename+post.ext;},
    post_com2txt_finisher: (function(){
      var pn_com = document.createElement('div');
      return function(txt, pst){
        if (txt.search(/&[#\w\d]+;/)==-1) return txt; //  && !pref.test_mode['58']) return txt;
        if (pref.debug_mode['17']) console.log('not interpreted html: '+txt);
        pn_com.innerHTML = pst && pst.com; // giving com is faster than giving txt.
        return pn_com.textContent; // [brwsr.innerText];
      };
    })(),
    post_com2txt: function(post){
      return (!post.com)? '' : this.post_com2txt_finisher(post.com.replace(/<[^>]*>/g,' ').replace(/&gt;/g,'>').replace(/&lt;/g,'<').replace(/&amp;/g,'&'), post); // most of lainchan, speed: 9.4/1.99, misshit 4%
    },
    patch:{},
    post_pn2ce: function(pn){
      return (pn.lastChild.tagName==='BLOCKQUOTE')? pn.lastChild : pn.getElementsByTagName('blockquote')[0];
    },
//    post_an2pn: function(pn){
//      while (pn && pn.classList && !pn.classList.contains('post')) pn = pn.parentNode; // because document.parentNode.parentNode.classList===undefined
//      return pn;
//    },
    //    post_pn2ce: function(pn){return pn;},
    headline_json2html: function(th){
//      var pn = cnst.dom('<div class="'+pref.cpfx+'headline'+'">' +(th.sub?'<b>'+th.sub+'</b>: ':'') + th.com + '</div>');
//      var max_txt = pref.headline.max_letters;
//      for (var i=0;i<pn.childNodes.length;i++) {
//        var n = pn.childNodes[i];
//        if (n.tagName==='BR') {pn.removeChild(n);i--;}
//        if (!n.childNodes && n.textContent.length > max_txt) n.textContent = n.textContent.slice(0,max_txt)+' ... ';
//      }
//      var txt = site2[th.domain].post_com2txt(th);
      var max = pref.headline.max_letters - (th.sub? th.sub.length:0);
      if (pref.test_mode['196']) {
        var pn = cnst.dom('<div class="'+pref.cpfx+'headline'+'"><b></b>: </div>');
        if (th.sub) pn.firstChild.textContent = th.sub;
        pn.lastChild.textContent = (th.sub?': ':'')+(th.com.length<max? th.com : th.com.slice(0,max)+' ...');
      } else {
        var html = (th.sub? '<b>'+th.sub+'</b>: ':'') + (/*th.parse_funcs.design>0? th.parse_funcs.com_for_headline(th) :*/ th.com.length<max? th.com : th.com.slice(0,max)+' ...');
        var pn = cnst.dom('<div class="'+pref.cpfx+'headline'+'">'+html+'</div>');
      }
//      pn.setAttribute('style','white-space:nowrap;text-overflow:ellipsis;overflow:hidden;width:100%');
      gGEH.pns_all_keys.set(pn,th.key);
//      pn.innerHTML = '<span></span> '+sub_com;
//      Object.defineProperty(th,'footer',{value:th.firstChild, configurable:true, enumerable:true, writable:true});
//      th.footer = th.firstChild;
      return pn;
    },
    headline_merge: function(pn, merge, clg, tgt_th){ // changing merge.tgt_ths[0][0] is patchy, but better than buggy.
      if (merge.isShown===0) return pn;
      if (merge.isShown===1) {
        var pn0 = merge.pn;
        merge.pn = merge.tgt_ths[0][0] = cnst.dom('<div class="'+pref.cpfx+'headline '+pref.cpfx+'merged"></div>');
        if (pn0.parentNode) pn0.parentNode.insertBefore(merge.pn, pn0);
        var truncated = pref.headline.merge_truncated;
        var mems = cnst.dom('<div class="'+pref.cpfx+'mergedMembers"'+(truncated?' style="display:none"':'')+'></div>');
        mems.appendChild(pn0);
        merge.footer = [clg.footer.add_menu(cnst.dom('<span class="'+pref.cpfx+'footer"></span>'))]; // dependant on tagname of footer
        var summary = this.headline_prep_summary(merge, pn0, cnst.dom('<div></div>'), cnst.dom('<a class="'+pref.cpfx+'button" name="'+(truncated?'SHOW_T':'HIDE_T')+'" data-trav="Pn" data-str="'+(truncated?'[\u25b2]">[\u25bc]':'[\u25bc]">[\u25b2]')+'</a>')); // to remove style, use cnst.dom instead of cloneNode // not to add class to through th_pn search
        merge.pn.appendChild(summary);
        merge.pn.appendChild(mems);
        this.headline_re_icon(merge, clg, merge.tgt_ths[0]); // slightly redundant, scanning merge.tgt_th[1].
      }
      this.headline_re_icon(merge, clg, tgt_th);
      return merge.pn.lastChild.appendChild(pn);
    },
    headline_separate: function(merge, pn, clg, tgt_th){
      if (merge.isShown>=3) {
        if (merge.pn.lastChild.firstChild===pn) this.headline_prep_summary(merge, merge.pn.lastChild.childNodes[1]);
        return merge.pn.lastChild.removeChild(pn);
      } else if (merge.isShown==2) {
        var mems = merge.pn.lastChild;
        var idx0 = mems.firstChild===pn? 1 : 0;
        var pn0 = idx0===1? mems.lastChild : mems.firstChild;
        if (merge.pn.parentNode) merge.pn.parentNode.replaceChild(pn0, merge.pn);
        merge.pn = merge.tgt_ths[0][0] = pn0; // order of this function and changing tgt_ths was changed.
//        merge.pn = merge.tgt_ths[idx0][0] = pn0;
      }
      this.headline_re_icon(merge, clg, tgt_th);
      return pn;
    },
//    headline_merge: function(pn, merge, clg){ // works, but can't color properly when isShown===1, open_thread colors tgt_th[0](outer), but embeddedTriage colors tgt_th[3](inner)
//      if (merge.isShown===0) {
//        merge.pn = cnst.dom('<div class="'+pref.cpfx+'headline '+pref.cpfx+'merged"></div>'); // merge.pn will be tgt_th[0], must be this hierarchy.
//        if (pn.parentNode) pn.insertBefore(merge.pn, pn);
//        return merge.pn.appendChild(pn);
//      }
//      if (merge.isShown===1) {
//        var truncated = pref.headline.merge_truncated;
//        var mems = cnst.dom('<div class="'+pref.cpfx+'mergedMembers"'+(truncated?' style="display:none"':'')+'></div>');
//        var mem0 = mems.appendChild(merge.pn.firstChild);
//        merge.footer = [clg.footer.add_menu(cnst.dom('<span class="'+pref.cpfx+'footer"></span>'))]; // dependant on tagname of footer
//        var summary = this.headline_prep_summary(cnst.dom('<div></div>'), mem0, merge.footer[0], cnst.dom('<a class="'+pref.cpfx+'button" name="'+(truncated?'SHOW':'HIDE')+'_PN" data-str="'+(truncated?'[\u25b2]">[\u25bc]':'[\u25bc]">[\u25b2]')+'</a>')); // to remove style, use cnst.dom instead of cloneNode // not to add class to through th_pn search
////        var clone = merge.pn.lastChild.firstChild.cloneNode(true); // BUG, styles are inherited.
////        clone.firstChild.innerHTML = '';
////        clone.insertBefore(cnst.dom('<a class="'+pref.cpfx+'button" name="'+(truncated?'SHOW':'HIDE')+'_PN" data-str="'+(truncated?'[\u25b2]">[\u25bc]':'[\u25bc]">[\u25b2]')+'</a>'),clone.childNodes[1]);
//        merge.pn.appendChild(summary);
//        merge.pn.appendChild(mems);
//      }
////      if (merge.isShown===1) merge.footer = [merge.pn.insertBefore(clg.footer.add_menu(document.createElement('div')), merge.pn.firstChild)];
//      return merge.pn.lastChild.appendChild(pn);
//    },
//    headline_separate: function(merge, pn){
//      if (merge.isShown>=3) {
//        if (merge.pn.lastChild.firstChild===pn) this.headline_prep_summary(merge.pn.firstChild, merge.pn.lastChild.childNodes[1]);
////      if (merge.pn.lastChild.firstChild===pn && merge.pn.lastChild.childNodes.length>=2) {
////        var clone = merge.pn.lastChild.childNodes[1].cloneNode(true);
////        var ppn = merge.pn.firstChild
////        var n;
////        while (n = ppn.childNodes[2]) ppn.removeChild(n); // [0]:footer, [1]:button for expand
////        while (n = clone.childNodes[1]) ppn.appendChild(n); // [0]:footer
////      }
//        return merge.pn.lastChild.removeChild(pn);
//      } else if (merge.isShown==2) {
//        merge.pn.removeChild(merge.pn.firstChild);
//        var mems = merge.pn.removeChild(merge.pn.lastChild);
//        merge.pn.appendChild(mems.firstChild===pn? mems.lastChild : mems.firstChild);
//      }
//      return pn;
//    },
    headline_prep_summary: function(merge, src, dst_in, button_in){
      var class_footer = pref.cpfx+'footer';
      var footer = merge.footer[0];
      var dst = dst_in || merge.pn.firstChild;
//      var footer = footer_in || dst.getElementsByClassName(class_footer)[0];
      var button = button_in || footer.nextSibling;
      var pn;
      if (!dst_in) while ((pn = dst.lastChild) && pn!==button) dst.removeChild(pn); // remove all after button
//      if (!footer_in) while (pn = dst.firstChild) dst.removeChild(pn); // remove all for threadIcon
      var i = 0;
      if (!dst_in) while (pn = src.childNodes[i++]) if (pn.classList && pn.classList.contains(class_footer)) break;
      while (pn = src.childNodes[i++]) dst.appendChild(pn.classList && pn.classList.contains(class_footer)? (dst.appendChild(footer), button) : pn.cloneNode(true));
//      var pn;
//      if (replace) while (pn = dst.childNodes[2]) dst.removeChild(pn); // [0]:footer, [1]:button for expand
//      var class_footer = pref.cpfx+'footer';
//      var i = 0;
//      while (pn = src.childNodes[i++]) if (!pn.classList || !pn.classList.contains(class_footer)) dst.appendChild(pn.cloneNode(true)); // [0]:footer
      return dst;
    },
    headline_reorder: function(pn, merge, clg, idx_new){
      merge.pn.childNodes[1].insertBefore(pn, merge.pn.childNodes[1].childNodes[idx_new] || null);
      if (idx_new==0) this.headline_prep_summary(merge, pn); // set title from top one.
    },
    headline_re_icon: function(merge, clg, tgt_th){
      if (tgt_th[20]) this.headline_re_icon_sticky(merge, clg);
      if (tgt_th[16].showAlways) this.headline_re_icon_showAlways(merge, clg);
    },
    headline_re_icon_sticky: function(merge, clg){
      var sticky = merge.tgt_ths.some(function(v){return v[20];});
      if (!!merge[16].icon_sticky != sticky) merge[16].icon_sticky = site2[site.nickname].set_icon(merge.pn, clg.view, 'sticky', merge[16], sticky);
    },
    headline_re_icon_showAlways: function(merge, clg){
      var showAlways = merge.tgt_ths.some(function(v){return v[16].showAlways;});
      if (!!merge[16].icon_showAlways != showAlways) merge[16].icon_showAlways = site2[site.nickname].set_icon(merge.pn, clg.view, 'showAlways', merge[16], showAlways);
    },
  };
  site2['common'] = { // common functions
    absorb_children: function(pn){
      var container = document.createElement('div');
      while (pn.childNodes.length!=0) container.appendChild(pn.childNodes[0]);
      pn.appendChild(container);
      return container;
    },
    disgorge_children: function(pn){
      while (pn.childNodes.length!=0) pn.parentNode.appendChild(pn.childNodes[0]);
      pn.parentNode.removeChild(pn);
    },
    remove_by_classname : function(pn,classname,end,remove_br){
      if (end===undefined) end = 0;
      var tgts = pn.getElementsByClassName(classname);
      for (var i=tgts.length-1-end;i>=0;i--) {
        if (remove_br && tgts[i].nextSibling && tgts[i].nextSibling.tagName==='BR') tgts[i].parentNode.removeChild(tgts[i].nextSibling);
//if (!pref.test_mode['14']) tgts[i].setAttribute('style','display:none;'+(tgts[i].getAttribute('style') || ''));
//else
        tgts[i].parentNode.removeChild(tgts[i]);
      }
    },
//    remove_by_classname : function(pn,classname,end,remove_br){ // cause document leak in Chrome at 4chan/a/.
//      if (end===undefined) end = 0;
//      var tgts = pn.getElementsByClassName(classname);
//      for (var i=tgts.length-1-end;i>=0;i--) {
//        if (remove_br && tgts[i].nextSibling && tgts[i].nextSibling.outerHTML==='<br>') tgts[i].parentNode.removeChild(tgts[i].nextSibling);
//        tgts[i].parentNode.removeChild(tgts[i]);
//      }
//    },
    remove_by_tagname : function(pn,tagname,end){
      if (end===undefined) end = 0;
      var tgts = pn.getElementsByTagName(tagname);
      for (var i=tgts.length-1-end;i>=0;i--) tgts[i].parentNode.removeChild(tgts[i]);
    },
    remove_by_attribute : function(pns,attr_name,attr_val){ // pns must be array.
      for (var i=pns.length-1;i>=0;i--)
        for (var j=pns[i].childNodes.length-1;j>=0;j--)
          if (pns[i].childNodes[j].getAttribute)
            if (pns[i].childNodes[j].getAttribute(attr_name))
              if (pns[i].childNodes[j].getAttribute(attr_name).search(attr_val)!=-1) pns[i].removeChild(pns[i].childNodes[j]);
    },
    remove_attribute : function(doc,attr_name){
      var pns = doc.getElementsByTagName('*');
      for (var i=pns.length-1;i>=0;i--)
        if (pns[i].getAttribute && pns[i].getAttribute(attr_name)) pns[i].removeAttribute(attr_name);
    },
    add_attribute_by_classname : function(pn,classname,attr_name,attr_val){
      var tgts = pn.getElementsByClassName(classname);
      for (var i=tgts.length-1;i>=0;i--) tgts[i].setAttribute(attr_name,attr_val);
    },
    add_attribute_by_tagname : function(pn,tagname,attr_name,attr_val){
      var tgts = pn.getElementsByTagName(tagname);
      for (var i=tgts.length-1;i>=0;i--) tgts[i].setAttribute(attr_name,attr_val);
    },
    add_attribute_by_attribute : function(pns,attr_name,attr_val,attr_name2,attr_val2){ // pns must be array.
      for (var i=pns.length-1;i>=0;i--)
        for (var j=pns[i].childNodes.length-1;j>=0;j--)
          if (pns[i].childNodes[j].getAttribute)
            if (pns[i].childNodes[j].getAttribute(attr_name))
              if (pns[i].childNodes[j].getAttribute(attr_name).search(attr_val)!=-1) pns[i].setAttribute(attr_name2,attr_val2);
    },
    move_up_and_delete_parent : function(pns){
      for (var i=0;i<pns.length;i++) {
        var parent = pns[i].parentNode;
        var g_parent = parent.parentNode;
        g_parent.insertBefore(pns[i],parent);
        g_parent.removeChild(parent);
      }
    },
    remove_last_hrs_and_brs : function(th){
      while (1) {
        var last_node = th.childNodes[th.childNodes.length-1];
        if (last_node.tagName !== 'HR' && last_node.tagName !== 'BR') break;
        else th.removeChild(last_node);
      }
    },
    remove_brs : function(elem){
      var tgts = elem.getElementsByTagName('br');
      for (var i=tgts.length-1;i>=0;i--) tgts[i].parentNode.removeChild(tgts[i]);
    },
    remove_double_br : function(elem){
      var elems = elem.getElementsByTagName('*');
      for (var i=elems.length-2;i>=0;i--)
//        if (elems[i].outerHTML=='<br>' && elems[i+1].outerHTML=='<br>') elems[i+1].parentNode.removeChild(elems[i+1]); // CAN'T FIND STRINGS WITHOUT TAGS.
        if (elems[i].outerHTML==='<br>' && elems[i].nextSibling && elems[i].nextSibling.tagName==='BR') elems[i].parentNode.removeChild(elems[i].nextSibling);
    },
    remove_double_tags : function(elem,tag){
      var elems = elem.getElementsByTagName('*');
      for (var i=elems.length-2;i>=0;i--) {
        if (elems[i].tagName===tag && elems[i+1].tagName===tag) elems[i+1].parentNode.removeChild(elems[i+1]);
      }
    },
    change_utc_to_local : function(utc_str){
      var date = new Date(utc_str);
//      return date.toString().replace(/\ GMT.*/,'');
      return date.toLocaleString().replace(/\ /,' ('+date.toString().replace(/\ .*/,'')+') ');
    },
////////    thread2headline : function(doc,nickname){
////////      var retval  = site2[nickname].insert_footer(doc,0,'t2h',false,0,0,0);
////////      site2[nickname].remove_posts(doc,pref.catalog_t2h_num_of_posts);
////////      site2.common.remove_double_br(doc);
////////      var retval2 = site2[nickname].insert_footer(doc,0,'t2h',false,0,0,0);
////////      return [retval[0]-retval2[0], retval[1]-retval2[1]];
////////    },
  };
  var site4 = {
////////
//////// TUNING RESULTS to parse entire 4chan by chrome, (avarage of 3 times), 2015/10/5 on XXX.
////////     TYPE1: written in flat with 'exe_sub'. (traditional)
////////     TYPE2: written in array with 'exe_sub'.
////////     TYPE3: written in array.
////////     TYPE4: written in array systematically.
////////     TYPE5: written in flat.
////////   results(ms): (idle), (program), (anonymous_function), usage. 
////////     TYPE1: 179164.7, 24936.9, 12598.6, 17.3%
////////     TYPE2: 165382.5, 35503.6, 14546.3, 23.2%
////////     TYPE3: 169261.9, 23509.1, 13727.0, 18.0%
////////     TYPE4: 159156.4, 37429.6, 14104.5, 24.5%
////////     TYPE5: 159990.8, 32805.5, 14998.7, 23.0%
////////
//////// TUNING RESULTS use TYPE1 to parse entire 4chan 2015/12/19 on chrome 47.0.2526.106m. Just one trial.
////////     rev_547: 12min05s
////////     rev_714: 11min30s, why does 4chan become so slow? lainchan is not so slow.
////
////    parse_funcs_getters: {}, // works.
////    parse_funcs_getters: Object.create(null), // THIS DOESN'T WORK.
////    parse_funcs_getters: (function(){ // works if '{}'
//////      var obj = Object.create(null); // DOESN'T WORK, probably introduce problem to make prototype chain using '__proto__'.
////      var obj = {}; // works.
////      var props=['no','ths','key','time_bumped','nof_posts','nof_files','time_created','posts','sub','name','com','flag','flags',
////                 'footer','sticky','format','pn','pn_name','time','time_posted','tn_as','tn_imgs','op_img_url','post_no'];
////      for (var i=0;i<props.length;i++) Object.defineProperty(obj,props[i],{get:(function(prop){return function(){return this.exe_sub(prop);}})(props[i]), enumerable:true});
////      return obj;
////    })(),
////
    parse_funcs_getters: { // working code. // TYPE1
      get no() {return this.exe_sub('no');},
//      get ths() {return this.exe_sub('ths');}, // removed to get faster.
      get key() {return this.exe_sub('key');},
      get time_bumped() {return this.exe_sub('time_bumped');},
      get nof_posts() {return this.exe_sub('nof_posts');},
      get nof_files() {return this.exe_sub('nof_files');},
      get time_created() {return this.exe_sub('time_created');},
      get posts() {return this.exe_sub('posts');},
      get sub() {return this.exe_sub('sub');},
      get name() {return this.exe_sub('name');},
      get com() {return this.exe_sub('com');},
      get flag() {return this.exe_sub('flag');},
      get flags() {return this.exe_sub('flags');},
      get footer() {return this.exe_sub('footer');},
      get sticky() {return this.exe_sub('sticky');},
//      get pn() {return this.exe_sub('pn');},
//      get pn_name() {return this.exe_sub2('pn_name');},
      get time() {return this.exe_sub('time');},
      get time_posted() {return this.exe_sub('time_posted');},
//      get html_org() {return this.exe_sub('html_org');},
      get tn_as() {return this.exe_sub2('tn_as');},  // CAUTION. ADDED AFTER TUNING.
      get tn_imgs() {return this.exe_sub2('tn_imgs');},  // CAUTION. ADDED AFTER TUNING.
      get op_img_url() {return this.exe_sub('op_img_url');},
////      get post_no() {return this.exe_sub('post_no');},
//      get last_replies() {return this.exe_sub('last_replies');},   // CAUTION. ADDED AFTER TUNING.
      get txt() {return this.exe_sub('txt');},   // CAUTION. ADDED AFTER TUNING.

      get time_tu() {return this.time * this.parse_funcs.time_unit;},   // CAUTION. ADDED AFTER TUNING. THIS IS NOT A CALLER.
      get time_tu1000() {return this.time;},  // assume time_unit===1000  // CAUTION. ADDED AFTER TUNING. THIS IS NOT A CALLER.
      get lth() {return liveTag.mems[this.domain][this.board][this.no];},   // CAUTION. ADDED AFTER TUNING. THIS IS NOT A CALLER.
      get type_mimic(){return this.type_source;},
      get key_op() {return this.domain + this.board + (this.resto || this.op || this.no);}, // this.op for meguca, it always refer to op, while resto===0 if it is op itself.
    },
////    parse_funcs_on_demand : { // TYPE5
////      get no() {return Object.defineProperty(this,'no',{value:this.parse_funcs['no'](this), enumerable:true, configurable:true, writable:true})['no'];},
////      get ths() {return Object.defineProperty(this,'ths',{value:this.parse_funcs['ths'](this), enumerable:true, configurable:true, writable:true})['ths'];},
////      get key() {return Object.defineProperty(this,'key',{value:this.parse_funcs['key'](this), enumerable:true, configurable:true, writable:true})['key'];},
////      get time_bumped() {return Object.defineProperty(this,'time_bumped',{value:this.parse_funcs['time_bumped'](this), enumerable:true, configurable:true, writable:true})['time_bumped'];},
////      get nof_posts() {return Object.defineProperty(this,'nof_posts',{value:this.parse_funcs['nof_posts'](this), enumerable:true, configurable:true, writable:true})['nof_posts'];},
////      get nof_files() {return Object.defineProperty(this,'nof_files',{value:this.parse_funcs['nof_files'](this), enumerable:true, configurable:true, writable:true})['nof_files'];},
////      get time_created() {return Object.defineProperty(this,'time_created',{value:this.parse_funcs['time_created'](this), enumerable:true, configurable:true, writable:true})['time_created'];},
////      get posts() {return Object.defineProperty(this,'posts',{value:this.parse_funcs['posts'](this), enumerable:true, configurable:true, writable:true})['posts'];},
////      get sub() {return Object.defineProperty(this,'sub',{value:this.parse_funcs['sub'](this), enumerable:true, configurable:true, writable:true})['sub'];},
////      get name() {return Object.defineProperty(this,'name',{value:this.parse_funcs['name'](this), enumerable:true, configurable:true, writable:true})['name'];},
////      get com() {return Object.defineProperty(this,'com',{value:this.parse_funcs['com'](this), enumerable:true, configurable:true, writable:true})['com'];},
////      get flag() {return Object.defineProperty(this,'flag',{value:this.parse_funcs['flag'](this), enumerable:true, configurable:true, writable:true})['flag'];},
////      get flags() {return Object.defineProperty(this,'flags',{value:this.parse_funcs['flags'](this), enumerable:true, configurable:true, writable:true})['flags'];},
////      get footer() {return Object.defineProperty(this,'footer',{value:this.parse_funcs['footer'](this), enumerable:true, configurable:true, writable:true})['footer'];},
////      get sticky() {return Object.defineProperty(this,'sticky',{value:this.parse_funcs['sticky'](this), enumerable:true, configurable:true, writable:true})['sticky'];},
////      get pn() {return Object.defineProperty(this,'pn',{value:this.parse_funcs['pn'](this), enumerable:true, configurable:true, writable:true})['pn'];},
////      get pn_name() {return Object.defineProperty(this,'pn_name',{value:this.parse_funcs['pn_name'](this), enumerable:true, configurable:true, writable:true})['pn_name'];},
////      get time() {return Object.defineProperty(this,'time',{value:this.parse_funcs['time'](this), enumerable:true, configurable:true, writable:true})['time'];},
////      get time_posted() {return Object.defineProperty(this,'time_posted',{value:this.parse_funcs['time_posted'](this), enumerable:true, configurable:true, writable:true})['time_posted'];},
//////      get html_org() {return Object.defineProperty(this,'html_org',{value:this.parse_funcs['html_org'](this), enumerable:true, configurable:true, writable:true})['html_org'];},
////      get tn_as() {return Object.defineProperty(this,'tn_as',{value:this.parse_funcs['tn_as'](this), enumerable:true, configurable:true, writable:true})['tn_as'];},
////      get tn_imgs() {return Object.defineProperty(this,'tn_imgs',{value:this.parse_funcs['tn_imgs'](this), enumerable:true, configurable:true, writable:true})['tn_imgs'];},
////      get op_img_url() {return Object.defineProperty(this,'op_img_url',{value:this.parse_funcs['op_img_url'](this), enumerable:true, configurable:true, writable:true})['op_img_url'];},
////      get post_no() {return Object.defineProperty(this,'post_no',{value:this.parse_funcs['post_no'](this), enumerable:true, configurable:true, writable:true})['post_no'];},
////    },
  };
////  var props=['no','ths','key','time_bumped','nof_posts','nof_files','time_created','posts','sub','name','com','flag','flags', // works.
////             'footer','sticky','format','pn','pn_name','time','time_posted','tn_as','tn_imgs','op_img_url','post_no'];
////  for (var i=0;i<props.length;i++)
//////    Object.defineProperty(site4.parse_funcs_getters,props[i],{get:(function(prop){return function(){return this.exe_sub(prop);}})(props[i]), enumerable:true}); // TYPE2
////    Object.defineProperty(site4.parse_funcs_on_demand,props[i],{get:(function(prop){ // WORKS // TYPE3
////      return function(){
////        return Object.defineProperty(this,prop,{value:this.parse_funcs[prop](this), enumerable:true, configurable:true, writable:true})[prop];
////      }})(props[i]), enumerable:true});
  site4.parse_funcs_on_demand = { // working code. // TYPE1,2
    exe_sub : function(prop){return Object.defineProperty(this,prop,{value:this.parse_funcs[prop](this), enumerable:true, configurable:true, writable:true})[prop];},
    exe_sub2 : function(prop){return this.parse_funcs_html[prop](this);}, // no cache
    __proto__: site4.parse_funcs_getters
  };
  site4.parse_funcs_on_demand_debug = {
    exe_sub : function(prop){
      try {
        return Object.defineProperty(this,prop,{value:this.parse_funcs[prop](this), enumerable:true, configurable:true, writable:true})[prop];
      } catch(e) {
        console.log('parse_error: '+this.key+', '+this.type_parse+', '+prop);
        console.trace();
        console.log(this);
      }
    },
    exe_sub2 : function(prop){
      try {
        return this.parse_funcs_html[prop](this); // no cache
      } catch(e) {
        console.log('parse_error: '+this.key+', '+this.type_parse+', '+prop);
        console.trace();
        console.log(this);
      }
    },
    __proto__: site4.parse_funcs_getters
  };
  site4.parse_funcs_no_cache = {
    exe_sub : function(prop){return this.parse_funcs[prop](this);},
    exe_sub2 : function(prop){return this.parse_funcs_html[prop](this);},
    __proto__: site4.parse_funcs_getters
  };
  site4.parse_funcs_one_time = {
    get posts() {return Object.defineProperty(this,'posts',{value:this.parse_funcs['posts'](this), enumerable:true, configurable:true, writable:true})['posts'];},
    exe_sub : function(prop){return this.parse_funcs[prop](this);},
    exe_sub2 : function(prop){return this.parse_funcs_html[prop](this);},
    __proto__: site4.parse_funcs_getters
  };
  site4.parse_funcs_html = { // for test_mode['121'] initially
    get id(){return this.exe_sub('id');},
    get pn_id(){return this.exe_sub('pn_id');},
//        get country(){return this.exe_sub('country');},
//        get trip(){return this.exe_sub('trip');},
//        get pn_trip(){return this.exe_sub('pn_trip');},
    get pn_name(){return this.exe_sub('pn_name');},
//        get tFlag(){return this.exe_sub('tFlag');},
    get nofFiles(){return this.exe_sub('nofFiles');},
    __proto__: (pref.debug_mode.parse_error)? site4.parse_funcs_on_demand_debug : site4.parse_funcs_on_demand
  };

////  (function (){ // WORKS, BUT TOO SLOW // TYPE4
////    var props=['no','ths','key','time_bumped','nof_posts','nof_files','time_created','posts','sub','name','com','flag','flags', // works.
////               'footer','sticky','format','pn','pn_name','time','time_posted','tn_as','tn_imgs','op_img_url','post_no'];
////    for (var i=0;i<props.length;i++) {
////      var func = (function(prop){return function(){
////                   return Object.defineProperty(this,prop,{value:this.parse_funcs[prop](this), enumerable:true, configurable:true, writable:true})[prop];
////                 }})(props[i]);
////      var func_no_cache = (function(prop){return function(){
////                   return this.parse_funcs[prop](this);
////                 }})(props[i]);
////      var func_debug = (function(func,prop){return function(){
////                         try {
//////                           return func();  // THIS DOESN'T WORK, WHY???
////                           return Object.defineProperty(this,prop,{value:this.parse_funcs[prop](this), enumerable:true, configurable:true, writable:true})[prop]; // works.
////                         } catch(e) {
////                           console.log('parse_error: '+this.key+', '+this.type_parse);
////                         }
////                       }})(func,props[i]);
////      Object.defineProperty(site4.parse_funcs_on_demand,props[i],{get:func, enumerable:true});
////      Object.defineProperty(site4.parse_funcs_on_demand_debug,props[i],{get:func_debug, enumerable:true});  // NOT DEBUGGED YET
////      Object.defineProperty(site4.parse_funcs_no_cache,props[i],{get:func_no_cache, enumerable:true});  // NOT DEBUGGED YET
////      if (props[i]!=='posts') Object.defineProperty(site4.parse_funcs_one_time,props[i],{get:func, enumerable:true});  // NOT DEBUGGED YET
////      else Object.defineProperty(site4.parse_funcs_one_time,props[i],{get:func_no_cache, enumerable:true});
////    }
////  })();

if (pref.features.domains['8chan']) {
  site2['8chan'] = {
    nickname : '8chan',
    domain_url: '8kun.top', // '8ch.net',
    domain_url_image: 'media.8kun.top',
//    home : site.protocol + '//8chan.co/faq.html', // stop twitter and IRC access.
//    home : site.protocol + '//8ch.net/faq.html', // stop twitter and IRC access.
//    home : site.protocol + '//8ch.net/favicon.ico', // can't work in FF.
    home: site.protocol + '//8kun.top' + ((brwsr.ff)? '/faq.html' : '/favicon.ico'),
//    protocol : 'https:',
    protocol : site.protocol,
    features : {uip_tracker: true},
    utilize_boards_json: true,
    check_func : function(){
      var href = window.location.href;
      if (href.indexOf(this.domain_url)!==-1) { // 8chan
//        site2['8chan'].domain_url = (href.search(/8ch.net/)!=-1)? '8ch.net' : '8chan.co';
        this.home = site.protocol + '//' + this.domain_url + this.home.substr(this.home.lastIndexOf('/')); // FF doesn't work on favicon.
        site.whereami = (document.title.indexOf('404 - Page Not Found')!=-1)? '404'
                      : (href.search(/8kun\.top\/?$/)!=-1)? 'frame'
                      : (href.search(/catalog\.html/)!=-1)? 'catalog'
                      : (href.search(/res\/[0-9\+]*\.html/)!=-1)? 'thread'
                      : (href.search(/8kun\.top\/index/)!=-1)? 'boards'
                      : (href.search(/\/$|(index|[0-9]+)\.html|\/#all$/)!=-1)? 'page'
                      : (href.search(/boards.html/)!=-1)? 'boards'
                      : 'other';
        site.config(site2['8chan'].domain_url,'8chan');
        site.header_height = function(){
          var header = document.getElementsByClassName('boardlist')[0];
          return (header)? header.offsetHeight : 0;
        }
        site.postform = document.getElementsByTagName('form')[0];
//        site.postform_comment = document.getElementById('body');
        if (site.postform) this.postform_prep();
        site.max_page = site2['8chan'].max_page(site.board);
        site.catalog = href.search(/catalog\.html/)!=-1;
        site.embed_to = (site.whereami==='thread' || site.whereami==='page')? {
          top: function(){return document.getElementsByName('postcontrols')[0];},
          bottom: function(){return document.getElementsByClassName('boardlist bottom')[0];}
        } : (site.whereami==='catalog')? {
          top: function(){return document.getElementsByClassName('thumbnail')[0].nextSibling;},
          bottom: function(){return document.getElementsByTagName('footer')[0];}
        } : {};
        if (site.components.boardlist) site.components.boardlist.style.zIndex = 0;
        if (site.whereami==='boards') {
          var ppn = document.getElementById('search-form').parentNode;
          var button = ppn.parentNode.insertBefore(document.createElement('button'),ppn);
//          var button = ppn.firstChild.appendChild(document.createElement('button')); // doesn't work, form is submitted.
          button.textContent = 'TagSearch';
          var pn_redirect = document.createElement('div');
          pn_redirect.innerHTML = pref_func.format_html_str('<BTN"tagSearch.genResult,Generate Result"><IC"tagSearch.auto">Auto, &lt;= <ITB4"tagSearch.th_b">boards or <ITB4"tagSearch.th_t">tags, Now: <span></span>');
          var pn_summary = pn_redirect.lastChild;
          pref_func.apply_prep(pn_redirect, false);
          button.onclick = function(e){
            pref.catalog.board.board_tags = true;
            pref_func.settings.onchange_funcs['tag.generate_caller']('tag.scan');
            var pn = liveTag.popup_filter.embed(pn_redirect);
            if (pn) {
              button.parentNode.insertBefore(pn, button.nextSibling);
              button.textContent = 'Hide original search';
              button.onclick = function(e){
                ppn.style.display = 'none';
                e.target.style.display = 'none';
              }
            }
          };
          var pn_result = document.getElementsByClassName('board-list-tbody')[0];
          var func_genResult = function(e,from_auto){
            var tags = liveTag.tags_array_old.filter(function(obj){return liveTag.tags[obj.key].pn;});
            if (tags.length==0) {pn_summary.textContent = 'none'; return;} // pn_summary check is required because liveTag.update_pn is synchronous in popup_filter.embed.
            var bds_check = {};
            var bds = [];
            for (var i=0;i<tags.length;i++) {
              var bds_t = Object.keys(liveTag.tags[tags[i].key].mems_boards());
              for (var j=0;j<bds_t.length;j++) {
                var bd = bds_t[j].replace(/^8chan/,'');
                if (bds_check[bd]===undefined) {
                  bds[bds.length] = bd;
                  bds_check[bd] = null;
                }
              }
            }
//            var bds = tags.map(function(v){return Object.keys(liveTag.tags[v.key].mems_boards()).map(function(v){return v.replace(/^8chan/,'');}).filter(function(bd){return bds_check[bd]===undefined && (bds_check[bd]=null || true);});}).reduce(function(a,c){return a.concat(c);});
            pn_summary.textContent = bds.length+' board'+(bds.length>=2?'s':'')+', '+tags.length+' tag'+(tags.length>=2?'s':'');
            if (from_auto && (!site3['8chan'].boards || bds.length>pref.tagSearch.th_b && tags.length>pref.tagSearch.th_t)) return;
            var obj = site3['8chan'].bds;
            if (!obj) {
              obj = {};
              site3['8chan'].boards.forEach(function(v){obj['/'+v.uri+'/']=v;});
              site3['8chan'].bds = obj;
            }
            pn_result.innerHTML = bds.map(function make_row(bd){return bd[0]!=='/'? '' :
              '<tr><td class="board-uri"><p class="board-cell"><a href="'+bd+'">'+bd+'</a>'+(obj[bd].sfw==1?'<i class="fa fa-briefcase board-sfw" title="SFW"></i>':'')+'</p></td>'+
              '<td class="board-title"><p class="board-cell" title="Created ">'+obj[bd].title+'</p></td>'+
              '<td class="board-pph"><p class="board-cell board-pph-desc" title="'+obj[bd].pph+' made in the last hour, '+obj[bd].pph_average+' on average">'+obj[bd].pph+'</p></td>'+
              '<td class="board-unique"><p class="board-cell">'+obj[bd].active+'</p></td>'+
              '<td class="board-tags"><p class="board-cell">'+ obj[bd].tags.map(make_tag1).join('') + 
//                '<a class="tag-link" href="/boards.php?tags=/pol/">/pol/</a>'+
//                '<a class="tag-link" href="/boards.php?tags=politics">politics</a>'+
//                '<a class="tag-link" href="/boards.php?tags=news">news</a>'+
//                '<a class="tag-link" href="/boards.php?tags=free-speech">free-speech</a>'+
//                '<a class="tag-link" href="/boards.php?tags=current-events">current-events</a>'+
              '</p></td>'+
              '<td class="board-max"><p class="board-cell">'+obj[bd].max+'</p></td></tr>';}).join('');
            function make_tag1(tag_in){
              var tag = tag_in.slice(1);
              return '<a class="tag-link" href="/boards.php?tags='+tag+'">'+tag+'</a>';
            }
          };
//          pn_redirect.firstChild.onclick = func_genResult;
          setTimeout(function(){
            liveTag.popup_filter.onchange_funcs['tagSearch.genResult'] = func_genResult;
            liveTag.sub_tagSearch = function(){if (pref.tagSearch.auto) func_genResult(null, true);};},0);
//          button.onclick = pref_func.settings.onchange_funcs['tag.scan'];
        }
        return true;
      } else {
        if (!brwsr.ff) {
          this.protocol = 'https:';
          this.home = this.protocol + '//' + this.domain_url + this.home.substr(this.home.lastIndexOf('/'));
        }
        return false;
      }
    },
    postprocess_board: function(val){
      for (var i=0;i<val.length;i++) {
        if (val[i].max>0) { // val[i].max is string, but this is ok.
          var bd = liveTag.mems.init({domain:this.nickname, board:'/'+val[i].uri+'/'});
          bd.o = i;
          if (val[i].pages && !bd.pgs) bd.pgs = val[i].pages;
////          site3[this.nickname].boards.push(bd); // BUG. increase length forever.
          Object.defineProperty(bd,'max',{value:parseInt(val[i].max,10), writable:true});
          if (bd.read_max===undefined) Object.defineProperty(bd,'read_max',{value:0, writable:true});
          if (val[i].tags && val[i].tags.length!=0) liveTag.postprocess_board_add_btag(val[i].tags,bd);
        }
      }
    },
////    enumerate_boards_to_scan:function(){
////      var obj = [];
////      var end = (site3[site.nickname].boards.length > pref.scan.max)? pref.scan.max : site3[site.nickname].boards.length;
////      for (var i=0;i<end;i++) 
////        if (site3[site.nickname].boards[i].max) obj[obj.length] = '/'+site3[site.nickname].boards[i].uri+'/';
////      return obj;
////    },
////    make_site3_bds:function(){ // working code.
////      var tgts = site3[site.nickname].boards;
////      for (var i=0;i<tgts.length;i++) site3[this.nickname].bds['/'+tgts[i].uri+'/'] = tgts[i].max;
////    },
    catalog_frame_prep: function(pn12){
      document.getElementsByTagName('header')[0].style.display='none';
      document.getElementsByClassName('footer')[0].style.display = 'none';
      var frame_menu = document.getElementsByClassName('menuCol')[0];
      frame_menu.firstChild.style.display='none';
      var frame_main = document.getElementsByClassName('bodyCol')[0];
      frame_main.firstChild.style.display = 'none';
      frame_menu.insertBefore(pn12,frame_menu.firstChild);
      var ifrm = this.catalog_native_frame_prep_frame(frame_main,frame_main.firstChild);
      this.catalog_embed_prep(pn12);
    },
    prep_own_posts_event : function(e){
      if (e && e.key==='own_posts') site2['8chan'].prep_own_posts();
      if (window.name==='8chan') send_message('parent',['OWN_POSTS', window.name, site3[window.name].own_posts]);
    },
    prep_own_posts : function(){
      var own_posts = {};
      var obj = JSON.parse(localStorage.getItem('own_posts'));
      for (var i in obj) {
        var board = '/'+i+'/';
        own_posts[board] = {};
        for (var j=0;j<obj[i].length;j++) own_posts[board][obj[i][j]] = null;
      }
//      for (var i in obj)
//        for (var j=0;j<obj[i].length;j++)
//          own_posts['/'+i+'/'+obj[i][j]] = null;
//console.log(own_posts);
      site3['8chan'].own_posts = own_posts;
    },
////    format_thread_always : function(th){
////////      site2.common.remove_last_hrs_and_brs(th); // working code.
////      if (th.nextSibling && th.nextSibling.tagName==='HR') th.appendChild(th.nextSibling);
////      else th.appendChild(document.createElement('hr'));
////    },
//    preprocess_doc2: {
//      thread_html: function(doc){
//        var head = doc.getElementsByTagName('head')[0];
//        while (head.firstChild) head.removeChild(head.firstChild);
//        var body = doc.getElementsByTagName('body')[0];
//        for (var i=body.childNodes.length-1;i>=0;i--) if (body.childNodes[i].name!=='postcontrols') body.removeChild(body.childNodes[i]);
//      }
//    },

    parse_funcs : { // 8chan
      'catalog_html' : {
//        ths: function(doc,req){
//          var req2 = req[req.indexOf('ths')+1];
//          var mixs = doc.pn.getElementsByClassName('mix');
//          var ths = [];
//          for (var i=0;i<mixs.length;i++) if (mixs[i].tagName==='DIV') {
//            ths.push({pn:mixs[i], board:doc.board, domain:doc.domain});
//            site2['DEFAULT'].parse_funcs.call(this,ths[ths.length-1],req2);
//          }
//          return ths;
//        },
        before_test : ['ths',':ITER',':ALL','ths',['key','time_bumped','nof_posts','nof_files']],
        after_test  : ['time_created','sub','name','com','footer','sticky','format'],
        full_hier   : ['ths',':ITER',':ALL','ths',['key','time_bumped','nof_posts','nof_files','time_created','sub','name','com','footer','sticky','format']],
//        full_th     : ['key','time_bumped','nof_posts','nof_files','sub','name','com','footer','sticky'],
        ths: function(doc) {
          var mixs = doc.pn.getElementsByClassName('mix');
          var ths = [];
if (pref.test_mode['0']) {
          for (var i=0;i<mixs.length;i++) if (mixs[i].tagName==='DIV') ths[ths.length] = {pn:mixs[i], page:Math.floor(i/15)+'.'+i%15};
} else { 
          for (var i=0;i<mixs.length;i++) if (mixs[i].tagName==='DIV') ths[ths.length] = mixs[i];
          ths = this.ths_array(doc,ths);
}
//          for (var i=0;i<ths.length;i++) ths[i].pn.getElementsByTagName('a')[0].addEventListener('click',this.preventDefault,false); // TEST, works.
          return ths;
        },
        th_init: function(th){
          var pn_child = th.pn.getElementsByTagName('div')[0];
//          pn_child.className = pn_child.className.replace(/grid\-size\-[a-z]*/,'grid-size-'+site2[th.domain].catalog_native_size); // BUG WHEN MIMICED.
          pn_child.className = pn_child.className.replace(/grid\-size\-[a-z]*/,'grid-size-'+site2['8chan'].catalog_native_size);
//          th.pn.getElementsByTagName('a')[0].removeAttribute('href');
//          th.pn.getElementsByTagName('a')[0].addEventListener('click',th.parse_funcs.preventDefault,false); // TEST
        },
//        th_destroy: function(pn, parse_funcs){
////          pn.getElementsByTagName('a')[0].removeEventListener('click',parse_funcs.preventDefault,false); // TEST
//        },
////        ths: function(doc) {
////          var mixs = doc.pn.getElementsByClassName('mix');
////          var ths = [];
////if (pref.test_mode['0']) {
////          for (var i=0;i<mixs.length;i++) if (mixs[i].tagName==='DIV') ths[ths.length] = {pn:mixs[i], page:Math.floor(i/15)+'.'+i%15};
////} else { 
//////          for (var i=0;i<mixs.length;i++) if (mixs[i].tagName==='DIV')
//////            ths[ths.length] = {pn:mixs[i], page:Math.floor(i/15)+'.'+i%15, type_html: 'catalog_html', __proto__:doc.__proto__};
////          for (var i=0;i<mixs.length;i++) if (mixs[i].tagName==='DIV') ths[ths.length] = mixs[i];
////          ths = this.ths_array(doc,ths);
////}
////          for (var i=0;i<ths.length;i++) {
////            var pn_child = ths[i].pn.getElementsByTagName('div')[0];
////            pn_child.className = pn_child.className.replace(/grid\-size\-[a-z]*/,'grid-size-'+site2['8chan'].catalog_native_size);
////          }
////          for (var i=0;i<ths.length;i++) ths[i].pn.getElementsByTagName('a')[0].removeAttribute('href');
//////          for (var i=0;i<ths.length;i++) ths[i].pn.getElementsByTagName('a')[0].addEventListener('click',this.preventDefault,false); // TEST
////          return ths;
//////          for (var i=mixs.length-1;i>=0;i--) if (mixs[i].tagName==='DIV') mixs[i]={pn:mixs[i]}; // CAN'T WRITE TO COLLECTION.
//////                                             else Array.prototype.splice.call(mixs,i,1);
//////          return mixs; // return collection
////        },
//        no : function(th){return th.pn.getElementsByTagName('img')[0].id.replace(/img\-/,'');},
        no : function(th){return parseInt(th.pn.getAttribute('data-id'),10);},
        sticky: function(th){return th.pn.getAttribute('data-sticky')==='true';},
        posts: 'DEFAULT.catalog_html',
        proto: 'vichan.catalog_html'
      },
      'catalog_json' : {
////        op_img_url: function(obj) { // working code.
////          return (obj.ext==='.jpg' || obj.ext==='.png' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.webm' || obj.ext==='.mp4')?
////                   'https://' + site2['8chan'].domain_url + obj.board + 'thumb/' + obj.tim + '.jpg' :
////                 (obj.embed)? 'https:' + obj.embed.replace(/.*src="/,'').replace(/".*/,'') :
////                 (obj.ext==undefined)? 'https://' + site2['8chan'].domain_url + '/static/no-file.png' :
//////                 (obj.ext==undefined)? 'https://' + site2['8chan'].domain_url + '/static/assets' + obj.board + 'no-file.png' :
////                 '';
//////          return ((obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png')? 'https://' + site2['8chan'].domain_url + obj.board + 'thumb/' + obj.tim + obj.ext : '');
////        },
        get_op_src: 'thread_json',
        proto: 'vichan.catalog_json'
      },
      'page_html'    : {
        ths: function(doc) {
//          var pc = doc.pn.getElementsByName('postcontrols')[0].childNodes;
//          var ths = [];
//          if (pc) for (var i=0;i<pc.length;i++) if (pc[i].id && pc[i].id.substr(0,6)=='thread') ths[ths.length] = pc[i];
//          ths = this.ths_array(doc,ths);
          var ths = this.ths_array(doc,doc.pn.getElementsByClassName('thread')); // 2015.04.26 changed to the same as 4chan.
          if (site.whereami!=='page' || !pref.catalog.embed_page) for (var i=0;i<ths.length;i++) ths[i].pn.removeAttribute('class');
//          for (var i=0;i<ths.length;i++) if (ths[i].pn.nextSibling && ths[i].pn.nextSibling.tagName==='HR') ths[i].pn.appendChild(ths[i].pn.nextSibling);
////          for (var i=0;i<ths.length;i++) {
////            var as = ths[i].pn.getElementsByTagName('a');
////            as[0].removeAttribute('href'); // changed 2015.04.26
////            as[1].removeAttribute('href'); // changed 2015.04.26
////          }
          return ths;
        },
//        no : function(th){return parseInt(th.pn.getElementsByClassName('post op')[0].id.substring(3),10);},
//          pn: 'page_json', // for mimic
        filename: 'thread_html',
        proto: 'vichan.page_html'
      },
      'page_json'  : {
        has_posts: true,
//pn: function(th){return site2[th.domain_html].catalog_json2html3(th,th.board, this.op_img_url(th));}, // TEMPORAL PATCH
        proto: 'vichan.page_json'
      },
      'thread_html'  : {
////        qsel_th2posts: ':scope>.post.reply', // working code.
////        pop_post_prep: function(th){
////          th.posts_col = th.pn.querySelectorAll(this.qsel_th2posts);
////          th.idx_pop = th.posts_col.length-1;
////        },
////        pop_post: function(th){
////          while (th.idx_pop>=0) {
////            var pn = th.posts_col[th.idx_pop--];
////            if (pn.className && pn.className.indexOf('post')!=-1 && pn.className.indexOf('reply')!=-1) {
////              th.post = {pn:pn, parse_funcs:this, __proto__:th.__proto__};
////              return true;
////            }
////          }
////          return false;
////        },
        filename: function (th){ // post can be accepted.
          var filenames = [];
          var files = ((th.pn.classList.contains('op'))? th.pn.parentNode : th.pn).getElementsByClassName('files')[0];
          if (files) {
            var spans = files.getElementsByClassName('postfilename');
            for (var i=0;i<spans.length;i++) filenames[i] = spans[i].textContent;
//            files = files.getElementsByClassName('file');
//            for (var i=0;i<files.length;i++) {
//              var as = files[i].getElementsByTagName('a');
//              for (var j=0;j<as.length;j++) if (as[j].getAttribute('title')==='Save as original filename') filenames[filenames.length] = as[j].textContent;
//            }
          }
          if (filenames.length>1) {
            th.extra_files = [];
            for (var i=1;i<filenames.length;i++) th.extra_files[i-1] = {filename:filenames[i], __proto__:th.__proto__};
          }
          return filenames[0];
        },
        get_op_src: 'thread_json',
        proto: 'vichan.thread_html'
      },
      'thread_json'  : {
        get_op_src: function(th){return th.op_img_url.replace('thumb','src').replace('.jpg',th.ext);},
        proto: 'vichan.thread_json'
      },
    },

////    popups_op_func_set: function(pn){return pn.parentNode.querySelector('.files');},
//    popups_op_func_set: function(pn){return pn.querySelector('.files');}, // MUST BE CONSISTENT OF page_json2html
//    popups_op_func_use: function(pn,thq,no){
//      pn.setAttribute('class',pn.getAttribute('class')+' reply');
//      pn.insertBefore(thq[no].isOP.cloneNode(true),pn.firstChild);
//    },
    update_posts_remove: function(th_old,i,pnode){
      pnode.removeChild(th_old.posts[i].pn.nextSibling);
      pnode.removeChild(th_old.posts[i].pn);
    },
    update_posts_insert: function(src,dst,i,j,pnode){
      var last;
      var ref = (j<dst.length)? dst[j].pn : (last = dst[dst.length-1].pn.nextSibling, last  && last.nextSibling);
//      var ref = (j==1)? (dst[0].pn.nextSibling || null) :
//                (j<dst.length)? (dst[j].pn.nextSibling && dst[j].pn.nextSibling.nextSibling || null) : null;
      if (!src[i].pn) src[i].pn = this.post_json2html(src[i]);
      pnode.insertBefore(src[i].pn, ref);
      pnode.insertBefore(document.createElement('br'),ref);
    },

    catalog_json2html3_thumbnail: function(obj, board) {
      return (obj.ext==='.jpg' || obj.ext==='.png' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.webm' || obj.ext==='.mp4')?
        this.protocol + '//' + ((obj.tim.length!=64)? this.domain_url + board + 'thumb/' + obj.tim + (obj.ext==='.gif'? '.gif' : '.jpg')
                                                    : this.domain_url_image + '/file_store/thumb/' + obj.tim + (obj.ext==='.gif'? '.gif' : obj.ext==='.png'? '.png' : obj.ext==='.jpeg'? '.jpeg' : '.jpg'))
      : (obj.embed)? (obj.embed.slice(0,2)=='//'?'https:':'') + obj.embed.replace(/.*src="/,'').replace(/".*/,'')
      : (obj.ext==='.swf')? '/static/file.png' :
//             (obj.ext===undefined)? site2['8chan'].protocol + site2['8chan'].domain_url + '/static/no-file.png' :
////             (obj.ext===undefined)? 'https://' + site2['8chan'].domain_url + '/static/assets' + board + 'no-file.png' :
             '';
//      return ((obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png')? 'https://' + site2['8chan'].domain_url + board + 'thumb/' + obj.tim + obj.ext : '');
    },
    short_link:function(){return '';},
    favicon: {
      __proto__: site2['DEFAULT'].favicon,
      none: '/favicon.ico',
      reply: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAQElEQVR42mNgGEbg/5n/GJgYORQFDQ0IjK4BmxxWzehsQnJYBdFtxCWH1QBcrkKWw2sAWS6gKAwojgWqpIORDQBVkjfW5KYpFQAAAABJRU5ErkJggg==',
      reply_to_me: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAWUlEQVR42mNgAIH/Z/4zNDRgYpA4UQBqADIYZAaAaHRMtAEwjB42cEPQFaCzCclhFcQWK9jk0A1A8QJW2xgYiDeAJBdQFAbYAhLdRryxgByVuOKaYDoY2QAAcHCIXLRHYMUAAAAASUVORK5CYII='
    },
    proto : 'vichan'
  };
  site2['8chan_live'] = {
    parse_funcs : {
      'thread_html'  : {
        posts: function(th){return this.posts_array(th, th.pn.querySelectorAll(':scope>.post.reply'));},
        proto: '8chan.thread_html'
      }
    },
    proto : '8chan'
  };
}
if (pref.features.domains['8chan'] || pref.features.domains['lain'] || pref.features.domains['lainjp']) {
  site2['vichan'] = {
    components: {
      boardlist: '.boardlist',
      postform_comment: ['textarea[name="body"]',0],
      postform_comment2: ['textarea[name="body"]',1],
      postform_submit: ['input[name="post"]',0],
      postform_submit2: ['input[name="post"]',1],
    },
////    postform_prep: function(){
////      site.components.postform_submit2 = null;
////      site.components.postform_submit2_observer = new MutationObserver(this.postform_submit2_find);
////      site.components.postform_submit2_observer.observe(document.getElementsByTagName('body')[0], {childList: true});
////    },
////    postform_submit2_find: function(){
////      var postform_qr = document.querySelectorAll('input[name=post]')[1]; // quick reply
////      if (!site.components.postform_submit2 && postform_qr) {
////        site.components.postform_submit2 = postform_qr;
//////        site.components.postform_submit2_observer.disconnect();
//////        delete site.components.postform_submit2_observer;
////        if (common_obj.thread_reader) common_obj.thread_reader.add_event_to_submit(site.components.postform_submit2);
////      } else if (site.components.postform_submit2 && !postform_qr) {
////        if (common_obj.thread_reader) common_obj.thread_reader.remove_event_from_submit(site.components.postform_submit2);
////        site.components.postform_submit2 = null;
////      }
////      site.components.query_set('postform_comment2');
////      recovery.setup2();
////    },
    general_event_handler:(function(){
      var obj = {
        thread: {
          get_mark: function(pn, clientY){
            while (pn && pn!== cataLog.parent) {
              if (pn.tagName==='BR' || !pn.classList) {pn = pn.previousSibling || pn.parentNode; continue;}
              if (pn.classList.contains('post')) return pn;
              if (pn.classList.contains('postcontainer')) return pn.getElementsByClassName('post')[0];
              if (pn.parentNode && pn.parentNode.id && pn.parentNode.id.search(/^thread/)!=-1) {
                pn = pn.previousSibling;
                if (pn) continue;
                else break;
              }
              if (pn.id && pn.id.search(/^thread/)!=-1) break;
              pn = pn.parentNode;
            }
            return this.get_mark_from_height(clientY);
          },
          get_mark_from_height: function(now_height){
            return this.__proto__.get_mark_from_height(now_height, document.getElementsByClassName('post'));
          },
          image_hover_check_mode: function(img){
            return img.parentNode.parentNode.classList.contains('file')? 'page' : 'catalog';
          },
          getID: function(pn){return pn.classList.contains('poster_id') && pn.textContent;},
          __proto__: site2['DEFAULT'].general_event_handler.common,
        },
      };
      obj.page = {
        add_mouseover: pref.test_mode['68'], // true,
        mouseover: function(e){
          var et = e.target;
          var et_tagName = et.tagName;
          if (et_tagName==='A')
            if (pref[cataLog.embed_mode].popup_truncated && et.parentNode.className==='toolong') site2['DEFAULT'].popups_posts.over(e);
        },
        __proto__:obj.thread
      },
      obj.catalog = Object.create(obj.thread);
//      obj.catalog = { // working code
//        mouseover: function(e){
//          var et = e.target;
//          var et_tagName = et.tagName;
//          if (et_tagName==='IMG')
//            if (pref[cataLog.embed_mode].image_hover && et.parentNode.tagName==='A') cataLog.image_hover_add.call(et, e);
//        },
//        __proto__:obj.thread
//      };
      return obj;
    })(),
    catalog_board_list_obj_dynamic : function(){ // pick up selected boards from 8kun.top/index.html
      if (site.whereami!=='boards') return;
      var pns = document.getElementsByClassName('board-list-tbody')[0];
      var bds = [];
      for (var i=0;i<pns.childNodes.length;i++)
        if (pns.childNodes[i].style.display!=='none') bds[bds.length] = this.nickname + pns.childNodes[i].getElementsByClassName('board-uri')[0].getElementsByTagName('a')[0].getAttribute('href');
      return pref_func.str2obj_replace0(bds);
    },
//    catalog_background : '#eef2ff',
//    catalog_bordercolor : '#d6daf0',
    get_next_image: function(img,top){
      var imgs = Array.prototype.slice.call(cataLog.parent.getElementsByClassName('post-image')).filter(function(v){return v.src.substr(-5,5)!=='.webm';});
//      var imgs = cataLog.parent.getElementsByClassName('post-image');
//      var imgs = cataLog.parent.querySelectorAll('img[id^="thumbnail"]');
      return site2['DEFAULT'].get_next_image(img,top,imgs);
    },
//    get_time_of_posts : function(doc){ // working code.
//      var posts = doc.getElementsByClassName('post');
//      return [Date.parse(posts[posts.length-1].getElementsByTagName('time')[0].getAttribute('datetime')),
//              Date.parse(posts[0             ].getElementsByTagName('time')[0].getAttribute('datetime'))];
//    },
////////    get_thread_link : function(pn,bn,del,name){
////////      var as = pn.getElementsByClassName('post op')[0].getElementsByTagName('a');
////////      var hrefs = [];
////////      var href;
////////      for (var i=as.length-1;i>=0;i--) if (as[i].innerHTML==='[Reply]' || as[i].innerHTML==='[Last 50 Posts]') {
////////        var href = as[i].getAttribute('href');
////////        if (hrefs.length==0 || as[i].innerHTML==='[Reply]') hrefs.unshift(href);
////////        else hrefs.push(href);
////////        if (del) as[i].parentNode.removeChild(as[i]);
////////        else {
//////////          as[i].setAttribute('target',(pref.catalog_open_in_new_tab)? '_blank' : '_self');
//////////          as[i].setAttribute('onclick','open_new_thread('+as[i].getAttribute('href')+','+name+')');
////////          as[i].addEventListener('click', function(){open_new_thread(as[i].getAttribute('href'),name);}, false);
//////////          as[i].removeAttribute('href');
////////        }
////////      }
////////      return (hrefs.length!=0)? hrefs : null;
////////    },
    modify_thread_link : function(pn){
      var as = pn.getElementsByClassName('post op');
      if (as.length==0) return [];
      var retval = [];
      as = as[0].getElementsByTagName('a');
      for (var i=as.length-1;i>=0;i--) 
        if (as[i].innerHTML=='[Reply]' || as[i].innerHTML=='[Last 50 Posts]') {
          var href = as[i].getAttribute('href');
          if (href) {
            retval.push([as[i],href]);
//            as[i].addEventListener('click', make_open_new_thread_callback(href,name), false);
//            as[i].addEventListener('click', function(){open_new_thread(href,name);}, false);
            as[i].removeAttribute('href');
          }
        }
      return retval;
    },
//    add_thread_link : function(doc,url){
//      var pn = document.createElement('a');
//      pn.href = url.replace(new RegExp('/https*:\/\/'+site2['8chan'].domain_url+'/'),'');
//      pn.innerHTML = '[Reply]';
//      var th = doc.getElementsByClassName('post op')[0];
//      if (th) th.insertBefore(pn,th.firstChild);
//    },
    catalog_threads_in_page : function(doc){ // patch
      var doc_obj = {domain:site.nickname, pn:doc};
      var ths = this.parse_funcs['page_html'].ths(doc_obj);
      for (var i=0;i<ths.length;i++) ths[i] = ths[i].pn;
      return ths;
    },
//    catalog_threads_in_page : function(doc){return this.parse_funcs['page_html'].ths(doc);},
//    catalog_threads_in_page : function(doc){
//      var pc = doc.getElementsByName('postcontrols');
//      th = [];
//      if (pc.length!=0)
//        for (var i=0;i<pc[0].childNodes.length;i++)
//          if (pc[0].childNodes[i].id && pc[0].childNodes[i].id.substr(0,6)=='thread') th.push(pc[0].childNodes[i]);
//      return th;
//    },
    remove_posts : function(th,end){
      site2.common.remove_by_classname(th,'post reply',end,true);
//      site2.common.remove_double_br(th);
    },
    remove_files_info : function(th){
      site2.common.remove_by_classname(th,'fileinfo');
      site2.common.move_up_and_delete_parent(th.getElementsByClassName('post-image'));
    },
//    site.catalog_files_info = function(doc){return doc.getElementsByClassName('fileinfo');};
//    site.catalog_delete_checkboxs = function(doc){return doc.getElementsByClassName('delete');};
//    remove_checkboxes : function(doc){
//      var cbxs = doc.getElementsByClassName('delete');
//      for (var i=cbxs.length-1;i>=0;i--) cbxs[i].outerHTML = '';
//      return doc;
//    },
    postform_rules : null,
    thread_keyword : 'res',
    max_page : function(){return 15;},
    make_url4 : function(dbt){
      var url_prefix = this.protocol + '//' + this.domain_url + dbt[1];
//      if (dbt[3]==='page_json') dbt[3] = 'page_html'; // TEMPORAL PATCH
      if      (dbt[3]==='page_html')    return [url_prefix + ((dbt[2]!=0)? (parseInt(dbt[2],10)+1) :'index')+'.html', 'html'];
      else if (dbt[3]==='page_json')    return [url_prefix +                parseInt(dbt[2],10)             +'.json', 'json'];
      else if (dbt[3]==='catalog_json') return [url_prefix + 'catalog.json', 'json'];  // Doesn't contain information about webm thumbnail.
      else if (dbt[3]==='catalog_html') return [url_prefix + 'catalog.html', 'html'];
      else if (dbt[3]==='thread_html')  return [url_prefix + 'res/' + dbt[2] + '.html', 'html'];
      else if (dbt[3]==='thread_json')  return [url_prefix + 'res/' + dbt[2] + '.json', 'json'];
    },
////////    make_url : function(board,no,key){ // working code.
////////      var url_prefix = site2['8chan'].protocol + '//' + site2['8chan'].domain_url + board;
////////      if (key==='p') return [url_prefix + ((no!=0)? (no+1) :'index')+'.html', 'html'];
////////      else if (key==='j') return [url_prefix + 'catalog.json', 'json'];  // Doesn't contain information about webm thumbnail.
////////      else return [url_prefix + 'catalog.html', 'html'];
////////    },
//////////    make_url3: function(board,th){return site2['8chan'].protocol + '//' + site2['8chan'].domain_url + board + 'res/' + th + '.html';},
////////    make_url3: function(board,th){return site2['8chan'].protocol + '//' + site2['8chan'].domain_url + board + 'res/' + ((th[0]!=='t')? (th + '.html') : (th.substr(1) + '.json'));},
//////////    make_url3: function(board,th){
//////////      return [site2['8chan'].protocol + '//' + site2['8chan'].domain_url + board + 'res/' + ((th[0]!=='t')? (th + '.html') : (th.substr(1) + '.json')), (th[0]!=='t')? 'html' : 'json'];
//////////    },
    url_boards_json : function(){return [site2['8chan'].protocol + '//' + site2['8chan'].domain_url + '/boards.json','json'];},
//    enumerate_boards_to_scan:function(){
//      var obj = [];
//      var end = (site3[site.nickname].boards.length > pref.scan.max)? pref.scan.max : site3[site.nickname].boards.length;
//      for (var i=0;i<end;i++) 
//        if (site3[site.nickname].boards[i].max) obj[obj.length] = '/'+site3[site.nickname].boards[i].uri+'/';
//      return obj;
//    },
    get_ops : function(doc){
      var op_containers = doc.getElementsByClassName('post op');
      var ops = [];
//      for (var i=0;i<op_containers.length;i++) ops.push(op_containers[i].getElementsByTagName('input')[0].id.substring(7));
      for (var i=0;i<op_containers.length;i++) ops.push(parseInt(op_containers[i].id.substring(3),10));
      return ops;
    },
    get_posts : function(doc) {
      var posts = [];
//      var deletes = doc.getElementsByClassName('delete');
//      for (var i=0;i<deletes.length;i++) posts.push(deletes[i].id.substr(7));
      var nos = doc.getElementsByClassName('post_no');
      for (var i=0;i<nos.length;i++) if (nos[i].id) posts.push(parseInt(nos[i].id.substr(8),10));
      return posts;
    },
////////    insert_footer : function(th,page_no,bn,exe,date,nof_posts,nof_files){
////////      var key = (!brwsr.ff)? 'innerText' : 'innerHTML';
////////      nof_posts += th.getElementsByClassName('post').length;
////////      nof_files += th.getElementsByClassName('fileinfo').length;
////////      var om_info = th.getElementsByClassName('omitted');
////////      if (om_info[0]) {
////////        var str = om_info[0][key].replace(/\n/g,'');
////////        nof_posts += parseInt(str.replace(/\ post.*/,''),10);
////////        nof_files += parseInt('0'+str.replace(/\ image.*/,'').replace(/[^\ ]*\ /g,''),10);
////////      }
////////      if (exe) {
////////        var pn = document.createElement('div');
////////        pn.setAttribute('name','catalog_footer');
////////        if (pref.catalog_footer_br) pn.setAttribute('style','clear:both');
////////        pn.innerHTML = '<span>' + bn + '  ' + nof_posts + '/' + nof_files + '/' + page_no + '  </span>';
//////////        pn.innerHTML = '<span><span>' + bn + '  </span><span></span><span>' + nof_posts + '/' + nof_files + '/' + page_no + '  </span></span>';
////////        var flags = th.getElementsByClassName('flag');
////////        for (var i=0;i<flags.length;i++) {
////////          pn.appendChild(flags[i].cloneNode(false));
//////////          pn.appendChild(document.createTextNode(' '));
////////        }
////////        th.insertBefore(pn,th.getElementsByClassName('post op')[0]);
////////      }
////////      return [nof_posts,nof_files];
////////    },
////////    insert_footer2 : function(th,type,nums,nums2){
////////      var str_add = ((pref.catalog_footer_show_nof_rep_to_me)? nums[1]+'/' : '' ) +(nums2[2]-nums[2]);
////////      if (type==='page') {
////////        var footer = th.getElementsByTagName('div')['catalog_footer'];
//////////        footer.childNodes[0].childNodes[1].innerHTML = nums[1] + '/' + (nums2[2]-nums[2]) + '/';
//////////        footer.childNodes[0].innerHTML = footer.childNodes[0].innerHTML.replace(/  /,'  '+ nums[1] + '/' + (nums2[2]-nums[2]) + '/');
////////        var str = footer.childNodes[0].innerHTML;
////////        var fields = str.replace(/[^ ]*  /,'').split('/');
////////        if (fields.length>3) fields.splice(0,fields.length-3);
////////        footer.childNodes[0].innerHTML = str.replace(/  .*/,'  ') + ((nums[0]>=0)? str_add + '/' :'') + fields.join('/');
////////      } else {
////////        var footer = th.getElementsByTagName('strong')[0];
////////        footer.childNodes[0].innerHTML = (nums[0]>=0)? 'U: '+ str_add + ' / ' : '';
////////      } 
////////    },
////////    check_reply_to_me : function(name,dbt,nums,value,date,pool){
////////      var obj ;
////////      var time_check = (nums[6]==nums[0])? nums[3] : nums[0];
////////      if (dbt[2][0]==='t') {
////////        obj = ('response' in value)? value.response.posts : JSON.parse(value.responseText).posts;
////////        var images = 0;
////////        for (var i=0;i<obj.length;i++) {
////////          if ('filename' in obj[i]) images++;
////////          if (obj[i].extra_files) for (var j=0;j<obj[i].extra_files.length;j++) if ('filename' in obj[i].extra_files[j]) images++;
////////          obj[i].time *= 1000;
//////////obj[i].time += - pref.localtime_offset*3600000; // BUG PATCH.
////////          if (obj[i].ext==='.jpeg' || obj[i].ext==='.gif') obj[i].ext = '.jpg';
////////          if (obj[i].filename) obj[i].icon =  'https://' + site2['8chan'].domain_url + dbt[1] + 'thumb/' + obj[i].tim + obj[i].ext;
////////        }
////////        obj[0].images = images;
//////////        if (obj[obj.length-1].ext==='.jpeg' || obj[obj.length-1].ext==='.gif') obj[obj.length-1].ext = '.jpg';
//////////        if (obj[obj.length-1].filename) obj[obj.length-1].icon =  'https://' + site2['8chan'].domain_url + dbt[1] + 'thumb/' + obj[obj.length-1].tim + obj[obj.length-1].ext;
////////        pool.sticky = obj[0].sticky;
////////      } else obj = site2['8chan'].get_posts2(value,pool,time_check);
//////////      date = [obj[obj.length-1].time, date[1], obj.length, obj[0].images];
//////////      date[0] = obj[obj.length-1].time; // CAUSE BUG IN PAGE, THIS PREVENT REVISING POST'S INFO FOR POPUPS. AND MUST BE FIXED TIMEZONE MISUNDERSTOOD.
//////////if (date[4] != obj[obj.length-1].time) console.log('find: '+name+', '+date[4]+' -> '+obj[obj.length-1].time+', '+(obj[obj.length-1].time-date[4]));
////////      date[4] = obj[obj.length-1].time;
//////////                            IF UNCOMMENT, CAUSE BLINKS BECAUSE USING DIFFERENT METHOD TO EVALUATE,
//////////                            IF COMMENT, DELAYS UPDATE AND CAUSE BUG IN PAGE MODE.
//////////      date[2] = obj.length; // CAUSE BUG IN PAGE, THIS CAUSE INCONSISTENCY BETWEEN FOOTER AND POPUP COMMENTS.
//////////      date[3] = obj[0].images; // CAUSE BUG IN PAGE, THIS CAUSE INCONSISTENCY BETWEEN FOOTER AND POPUP COMMENTS.
////////
//////////      var i = 0;
//////////      while (i<obj.length && (!obj[i] || obj[i].time<=time_check)) i++;
////////      var i = obj.length;
////////      while (i-1>=0 && obj[i-1] && obj[i-1].time>time_check) i--;
//////////console.log(name+', '+obj.length+', '+(obj.length-i));
//////////      nums[2] = i;
////////      var rep_to_me = (nums[6]==nums[0])? nums[1] : 0;
////////      var rep       = (nums[6]==nums[0])? nums[7] : 0;
////////      nums[4] = [];
////////      if (nums[3]<nums[0]) nums[3]=nums[0];
////////      if (i<obj.length ) {
////////        while (i<obj.length) {
////////          if (pref.catalog_footer_ignore_my_own_posts && (dbt[1]+obj[i].no in site3['8chan'].own_posts)) {i++;continue;}
////////          rep++;
//////////          if (obj[i].time>nums[3]) nums[4].push({icon:obj[obj.length-1].icon, body:obj[obj.length-1].com, time:obj[obj.length-1].time, to_me:false});
////////          if (obj[i].time>nums[3]) nums[4].push({icon:obj[i].icon, body:obj[i].com, time:obj[i].time, to_me:false});
////////          var tgts = [];
////////          if (obj[i].com) {
////////            var anchors = obj[i].com.match(/&gt;&gt;[0-9]+/g);
////////            if (anchors) for (var j=0;j<anchors.length;j++) tgts.push(dbt[1]+anchors[j].substr(8));
////////            anchors = obj[i].com.match(/&gt;&gt;&gt;\/[0-9A-z_\+]+\/[0-9]+/g);
////////            if (anchors) for (var j=0;j<anchors.length;j++) tgts.push(anchors[j].substr(12));
//////////console.log(tgts);
////////            for (var j=0;j<tgts.length;j++) {
////////              if (site3['8chan'].own_posts[tgts[j]]===null) {
////////                rep_to_me++;
////////                if (obj[i].time>nums[3]) nums[4][nums[4].length-1].to_me = true;
//////////console.log(dbt[2]+', >>'+tgts[j]);
////////                break;
////////          }}}
//////////          var anchors = (obj[i].com)? obj[i].com.match(/&gt;&gt;[0-9]+/g) : null; // working code.
//////////          if (anchors) {
//////////            for (var j=0;j<anchors.length;j++) {
//////////              var tgt = anchors[j].substr(8);
//////////              if (site3['8chan'].own_posts[dbt[1]+tgt]===null) {
//////////                rep_to_me++;
//////////                if (obj[i].time>nums[3]) nums[4][nums[4].length-1].to_me = true;
////////////console.log(dbt[2]+', >>'+tgt);
//////////                break;
//////////          }}}
//////////          anchors = (obj[i].com)? obj[i].com.match(/&gt;&gt;&gt;\/[0-9A-z_\+]+\/[0-9]+/g) : null;
//////////          if (anchors) {
//////////            for (var j=0;j<anchors.length;j++) {
//////////              var tgt = anchors[j].substr(12);
//////////              if (site3['8chan'].own_posts[tgt]===null) {
//////////                rep_to_me++;
//////////                if (obj[i].time>nums[3]) nums[4][nums[4].length-1].to_me = true;
//////////                break;
//////////          }}} 
////////          i++;
////////        }
////////      }
////////      nums[2] = obj.length - rep;
////////      nums[1] = rep_to_me;
////////      nums[3] = obj[obj.length-1].time;
////////      nums[6] = nums[0];
////////      nums[7] = rep;
////////    },
////////    get_posts2 : function(doc,pool,time_check) {
////////      var obj = [];
////////      var posts = doc.getElementsByClassName('post');
//////////      for (var i=0;i<posts.length;i++) {
////////      var i=posts.length-1;
////////      while (i>=0) {
////////        var image = posts[i].getElementsByClassName('post-image');
////////        image = (image[0])? image[0].getAttribute('src') : undefined;
////////        var time = Date.parse(posts[i].getElementsByTagName('time')[0].getAttribute('datetime'));
////////        obj[i] = {
////////          time: time,
////////          com:  posts[i].getElementsByClassName('body')[0].innerHTML,
////////          no:   parseInt(posts[i].getElementsByClassName('post_no')[0].id.substr(8),10),
////////          icon: image
////////        }
////////        if (time<=time_check && i!=0) i=0;
////////        else i--;
////////      }
//////////      var image = posts[posts.length-1].getElementsByClassName('post-image');
//////////      obj[obj.length-1].icon = (image[0])? image[0].src : undefined;
////////      var files = doc.getElementsByClassName('thread')[0].getElementsByClassName('file');
//////////      for (var i=files.length-1;i>=0;i--) if (files[i].tagName!=='DIV') files[i].remove(); // slow?
////////      var nof_files = 0;
////////      for (var i=files.length-1;i>=0;i--) if (files[i].tagName==='DIV') nof_files++;
////////      var op_images = doc.getElementsByClassName('thread')[0].getElementsByClassName('files');
////////      op_images = (!op_images[0])? 0 : op_images[0].getElementsByClassName('file').length;
//////////      obj[0].images = files.length - op_images;
////////      obj[0].images = nof_files - op_images;
////////      pool.sticky = doc.getElementsByClassName('fa-thumb-tack').length!=0
////////      return obj;
////////    },
    get_post_offsetTop : function(doc,num) {
      return doc.getElementsByClassName('post')[num].offsetTop;
    },
    get_icon : function(pn,type,kind){
//      return (type==='catalog')? this.add_icon(pn,type,kind,tgt_th16) :
      return (kind==='sticky')? pn.getElementsByClassName('fa-thumb-tack')[0] : null;
    },
    add_icon : function(pn,type,kind){
//      if (pn) {
//        var del_tgt = tgt_th16['icon_'+kind];
//        if (!del_tgt) {
//          var count = 0;
//          if (tgt_th16['icon_sticky']) count++;
//          if (tgt_th16['icon_showAlways']) count++;
          var icon = (kind==='sticky')? this.make_tack() : this.make_showAlways();
          var ref;
          if (type==='catalog' || type==='headline') {
            ref = pn.getElementsByClassName('threadIcons')[0] ||
              (ref = type==='catalog'? pn.firstElementChild : pn.getElementsByClassName(pref.cpfx+'footer')[0].parentNode, ref.insertBefore(cnst.dom('<div class="threadIcons"'+(type==='catalog'?'style="position:absolute"':'')+'></div>'), ref.firstChild));
//            if (!ref) {
//              ref = document.createElement('div');
//              ref.setAttribute('class','threadIcons');
//              ref.setAttribute('style','position:absolute');
//              pn.firstElementChild.insertBefore(ref,pn.firstElementChild.firstChild);
//            }
            ref.appendChild(icon);
//            icon.setAttribute('style','position:absolute;left:' + (16*count) + 'px');
//            ref = pn.getElementsByClassName('thread')[0];
//            ref.insertBefore(icon,ref.firstChild);
          } else {
            var op = pn.getElementsByClassName('op')[0];
            ref = op.getElementsByClassName('mentioned')[0] || op.getElementsByClassName('post_no')[1].nextSibling;
//            ref = pn.getElementsByClassName('mentioned')[0] || Array.prototype.filter.call(pn.getElementsByTagName('a'),function(dom){return dom.textContent==='[Reply]';})[0];
            ref.parentNode.insertBefore(icon,ref);
          }
          return icon;
//        } else del_tgt.parentNode.removeChild(del_tgt);
//      }
//      return null;
    },
//    add_icon : function(th,type,kind,tgt_th16){
//      if (type==='catalog') {
//        var parent = th.getElementsByClassName('thread')[0];
//        if (parent) {
//          var del_tgt = tgt_th16['icon_'+kind];
//          if (!del_tgt) {
//            var count = 0;
//            if (tgt_th16['icon_sticky']) count++;
//            if (tgt_th16['icon_showAlways']) count++;
//            var icon = document.createElement('i');
//            icon.setAttribute('class',(kind==='sticky')? 'fa fa-thumb-tack' : 'fa');
//            icon.setAttribute('style','position:absolute;left:' + (16*count) + 'px');
//            if (kind==='showAlways') icon.textContent = '\u2605';
//            parent.insertBefore(icon,parent.firstChild);
//            return icon;
//          } else del_tgt.parentNode.removeChild(del_tgt);
//        }
//      }
//      return null;
//    },
    favicon: {
      __proto__: site2['DEFAULT'].favicon,
      none: '/favicon.ico',
      reply: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAQElEQVR42mNgGEbg/5n/GJgYORQFDQ0IjK4BmxxWzehsQnJYBdFtxCWH1QBcrkKWw2sAWS6gKAwojgWqpIORDQBVkjfW5KYpFQAAAABJRU5ErkJggg==',
      reply_to_me: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAWUlEQVR42mNgAIH/Z/4zNDRgYpA4UQBqADIYZAaAaHRMtAEwjB42cEPQFaCzCclhFcQWK9jk0A1A8QJW2xgYiDeAJBdQFAbYAhLdRryxgByVuOKaYDoY2QAAcHCIXLRHYMUAAAAASUVORK5CYII='
    },
//    get_op_image_url: function(th,type){
//      var img = th.getElementsByTagName('img')[0];
//      return (img)? img.src : undefined; // patch.
//    },
    time_revised_check: function(nof_ths){return nof_ths>300;},
//    format_thread_layout : function(th){
//      site2.common.add_attribute_by_tagname(th,'img','style','margin:0px');
//      site2.common.add_attribute_by_tagname(th,'iframe','style','margin:0px');
//      site2.common.add_attribute_by_classname(th,'post','style','padding:0px');
//      site2.common.add_attribute_by_classname(th,'intro','style','margin:0px');
//      site2.common.add_attribute_by_classname(th,'body','style','margin:12px 5px');
//    },
//    format_thread_contents : function(th){
//      site2.common.remove_by_classname(th,'delete');
////      site2.common.remove_by_tagname(th,'hr');
////      site2.common.remove_by_tagname(th,'br');
//      site2.common.remove_by_classname(th,'omitted');
//    },
    localtime: function(pn){
      var times = pn.getElementsByTagName('time');
      for (var i=0;i<times.length;i++) times[i].textContent = site2.common.change_utc_to_local(times[i].getAttribute('datetime'));
    },
//    format_remove_tn_area_size: function(th){
//      var files = th.getElementsByClassName('file');
//      for (var i=0;i<files.length;i++){
//        var file_style = files[i].getAttribute('style');
//        if (file_style) {
//          file_style = ((file_style)? file_style + ';' : '') + 'float:left;';
//          if (file_style.indexOf('width')!=-1) file_style = file_style.replace(/width:[^;]*(;|$)/,'');
//          if (file_style.indexOf('height')!=-1) file_style = file_style.replace(/height:[^;]*(;|$)/,'');
//          files[i].setAttribute('style',file_style);
//        }
//      }
//    },
//    get_time_of_post_in_utc : function(post){ // working code
//      return Date.parse(post.getElementsByTagName('time')[0].getAttribute('datetime'));
//    },
//    mark_newer_posts: function(th,date,unmark, short_cut) {
////      return site2.common.mark_newer_posts('8chan',th.getElementsByClassName('post'),date,'border:2px solid red','border: none','class','post');
//      return site2['DEFAULT'].mark_newer_posts('8chan',th.getElementsByClassName('post'),date,null,unmark, short_cut);
//    },
////    unmark_post_from_event: function() {
////      this.setAttribute('style','border: none');
////      this.removeEventListener('mouseover', site2['8chan'].unmark_post_from_event, false);
////    },
////    mark_newer_posts : function(th,date){
////      var marked_first_post = null;
//////      var offset_top = 0;
////      var times = th.getElementsByTagName('time');
////      for (var i=times.length-1;i>=0;i--) {
////        var mark = date<Date.parse(times[i].getAttribute('datetime'))-pref.localtime_offset*3600000;
////        var reply = times[i].parentNode;
////        while (reply.className.search(/post/)==-1) reply = reply.parentNode;
////        if (mark) {
////          reply.setAttribute('style','border: 2px solid red');
////          marked_first_post = reply;
//////          offset_top = reply.offsetTop;
////        } else reply.setAttribute('style','border: none');
////      }
//////      return offset_top;
////      return marked_first_post;
////    },
    get_owners_recommendation: function(){
      var blotter = document.getElementsByClassName('blotter')[0];
      if (blotter) {
        or_str = document.getElementsByClassName('blotter')[0][brwsr.innerText];
        var kwd = 'Recommendation: ';
        var idx = or_str.indexOf(kwd);
        if (idx!=-1) return or_str.substr(idx+kwd.length);
      }
      return '';
    },
////////    thread2headline : function(doc){
////////      return site2.common.thread2headline(doc,'8chan');
////////    },
//    get_json_url_thread: function(board,thread){
//      return site2['8chan'].protocol + '//8chan.co' + board +'res/' + thread + '.json';
//    },
    get_json_url_catalog: function(board){
      return site2['8chan'].protocol + '//' + site2['8chan'].domain_url + board +'catalog.json';
    },
    thread2search_obj: function(th){
      var coms  = aggregate_info(th.getElementsByClassName('body'));
      var subs  = aggregate_info(th.getElementsByClassName('subject'));
      var names = aggregate_info(th.getElementsByClassName('name'));

      var files = th.getElementsByClassName('files');
      var filenames = ['',''];
      if (files.length!=0) {
        for (var i=0;i<files.length;i++) {
          var files_in_post = files[i].getElementsByClassName('unimportant');
          filenames[i] = '';
          for (var j=0;j<files_in_post.length;j++) {
            if (files_in_post[j].getElementsByTagName('a').length!=0) filenames[i] = filenames[i] + files_in_post[j].getElementsByTagName('a')[0][brwsr.innerText] + '\n';
            if (files_in_post[j].getElementsByTagName('span').length!=0) filenames[i] = filenames[i] + files_in_post[j].getElementsByTagName('span')[0][brwsr.innerText] + '\n';
          }
        }
        for (var i=2;i<filenames.length;i++) filenames[1] = filenames[1] + filenames[i] + '\n';
        if (files[0].parentNode!=th) {
          filenames[1] = filenames[0] + '\n' + filenames[1];
          filenames[0] = '';
        }
      }
      return [coms[0], subs[0], names[0], filenames[0], coms[1], subs[1], names[1], filenames[1]];

      function aggregate_info(pns){
        if (pns.length!=0) {
          var parent = pns[0].parentNode;
          while (parent.classList.contains('post')) parent = parent.parentNode;
          var op_idx = (!parent.classList.contains('op'))? 0 : -1;
          var op = (op_idx>=0)? pns[0][brwsr.innerText] : '';
          var posts = '';
          for (var i=op_idx+1;i<pns.length;i++) posts = posts + pns[i][brwsr.innerText] + '\n';
          return [op, posts];
        } return ['',''];
      }
    },
    parse_json_thread: function(txt,from_http){
      var obj = {posts: []};
      var uids = {};
      var nof_uids = 0;
      var posts = document.getElementsByClassName('post');
      for (var i=0;i<posts.length;i++) {
        obj.posts[i] = {};
        obj.posts[i].no = posts[i].id.replace(/op_/,'').replace(/reply_/,'');
        var id = posts[i].getElementsByClassName('poster_id')[0].textContent;
        obj.posts[i].id = id;
        if (uids[id]===undefined) {
          nof_uids++;
          uids[id] = 1;
        }
        obj.posts[i].unique_ips = nof_uids;
      }
      return obj;
    },
    uip_check: function(callback){
      callback(0,200,'');
    },
    uip_tgt_post : function(no){
      var pn = document.getElementById('reply_'+no);
      if (pn) return pn;
      else return document.getElementById('op_'+no);
    },
    uip_post_num : function(tgt_post){
      return tgt_post.getElementsByClassName('intro');
    },
//    catalog_native_prep0: function(threads,callback,pn_filter,pn_tb,func_sel){
////      var node_ref = document.getElementsByClassName('catalog_search')[0].nextSibling;  // FF doesn't work.
//      var node_ref = document.getElementsByClassName('threads')[0];
//      node_ref.parentNode.insertBefore(pn_tb,node_ref);
//      node_ref.parentNode.insertBefore(pn_filter,node_ref);
//
//      site2['8chan'].catalog_from_native(Date.now(),document,threads,null);
//
//      document.getElementById('sort_by').addEventListener('change',func_event,false);
//      return function(){document.getElementById('sort_by').removeEventListener('change',func_event,false);};
//      function func_event(){
//        pref.catalog.indexing = document.getElementById('sort_by').selectedIndex;
//        func_sel();
//      }
//
////      var mixs = document.getElementsByClassName('mix');
////      var date_load = Date.now();
////      for (var i=0;i<mixs.length;i++) site2['8chan'].catalog_from_native_1(threads,mixs[i],date_load);
////      for (var i=0;i<mixs.length;i++) {
////        var url  = mixs[i].getElementsByTagName('a')[0].href;
////        mixs[i].getElementsByTagName('a')[0].removeAttribute('href');
////        var name = url.replace(/.*8chan\.co/,'8chan').replace(/res\//,'').replace(/\.html/,'');
////        var date = [0,0,0,0];
////        var date_load = 0;
////        var page_no = 0;
////        threads[name] = [mixs[i], false, null,
////                         [mixs[i].innerHTML, '8chan'],
////                         mixs[i][brwsr.innerText], null, null, url, date, true,
////                         null,
////                         null, null, date_load, page_no, 0];
////      }
////      http_req.get('catalog',site.nickname+site.board,site.protocol+'//8chan.co'+site.board+'catalog.json',site2['8chan'].catalog_from_json,false,false,[threads,callback,null,site.board]);
//    },
    catalog_native_prep: function(pn_filter,pn_tb,pn_hi){
//      var node_ref = document.getElementsByClassName('catalog_search')[0].nextSibling;  // FF doesn't work.
      var node_ref = (site.whereami==='catalog')? document.getElementsByClassName('threads')[0]
                                                : document.getElementsByName('postcontrols')[0];
      site2['DEFAULT'].catalog_native_prep_sel(pn_filter,pn_tb,site.whereami==='catalog'? document.getElementById('sort_by') : null);
      if (site.whereami==='catalog') {
        pn_tb.style.display='inline';
        pn_tb.childNodes[3].style.display='inline';
//        document.getElementById('image_size').addEventListener('change', site2['8chan'].catalog_native_size_changed, false);
//        var pn_tb_new = document.createElement('span');
//        while (pn_tb.firstChild) pn_tb_new.appendChild(pn_tb.firstChild);
//        pn_tb = pn_tb_new;
//        pn_tb.appendChild(pn_tb.removeChild(pn_tb.childNodes[3]).firstChild);
      } else if (site.whereami==='page') {
        var pctrls = document.getElementsByName('postcontrols')[0];
        if (!pref.catalog_expand_with_hr) {for (var i=pctrls.childNodes.length-1;i>=0;i--) if (pctrls.childNodes[i].tagName==='HR') pctrls.removeChild(pctrls.childNodes[i]);}
        else for (var i=pctrls.childNodes.length-1;i>=0;i--) if (pctrls.childNodes[i].tagName==='HR') pctrls.childNodes[i].setAttribute('class',pref.cpfx+'hs');
        pctrls.parentNode.insertBefore(document.createElement('hr'),pctrls.nextSibling);
      }
//      node_ref.parentNode.insertBefore(pn_hi,node_ref);
      node_ref.parentNode.insertBefore(pn_tb,node_ref);
//      node_ref.parentNode.insertBefore(pn_filter,node_ref);
//      pn_tb.childNodes[0].setAttribute('style',pn_tb.childNodes[0].getAttribute('style')+';display:none');
//      pn_tb.childNodes[1].setAttribute('style',pn_tb.childNodes[1].getAttribute('style')+';display:none');
////////      return site2['8chan'].catalog_from_native(date,document,site.board,site.whereami+'_html');
    },
    catalog_native_size: (document.getElementById('image_size'))? document.getElementById('image_size').value : 'small',
    catalog_native_size_changed: function(){site2['8chan'].catalog_native_size = this.value;},
    catalog_get_native_area: function(){
      if (site.whereami==='catalog') return document.getElementById('Grid');
      return document.getElementsByName('postcontrols')[0];
////      else { // working code.
////        var pc = document.getElementsByName('postcontrols')[0];
////        return pc.insertBefore(document.createElement('div'),pc.firstChild);
////      }
    },
////////    catalog_from_native : function(date,doc,board,type) { // working code.
////////      var ths;
////////if (pref.test_mode['0']) {
////////      ths = {domain:'8chan', board:board, pn:doc};
////////      this.parse_funcs['catalog_html'].entry(ths,this.parse_funcs['catalog_html']['full_hier']);
////////      return ths.ths;
////////} else {
////////////      var parse_obj = {domain:'8chan', board:board, parse_funcs:site2['8chan'].parse_funcs[type], __proto__:site4.parse_funcs_on_demand};
////////////      ths = {pn:doc, __proto__:parse_obj};
////////////      return ths.ths;
////////      return site2[this.nickname].wrap_to_parse.get(doc, this.nickname, board, type);
////////}
////////    },
//    catalog_from_native : function(date,doc,board) {
//      var ths;
//if (pref.test_mode['0']) {
//      ths = {domain:'8chan', board:board, pn:doc};
//      this.parse_funcs['catalog_html'].entry(ths,this.parse_funcs['catalog_html']['full_hier']);
//} else {
////      ths = {domain:'8chan', board:board, pn:doc, parse_funcs:this.parse_funcs['catalog_html'], __proto__:site4.parse_funcs_on_demand};
//      var parse_obj = {domain:'8chan', board:board, parse_funcs:site2['8chan'].parse_funcs['catalog_html'], __proto__:site4.parse_funcs_on_demand};
//      ths = {pn:doc, __proto__:parse_obj};
//}
//      return ths.ths;
//    },
//    catalog_from_native : function(date,doc,board) {
//      var mixs = doc.getElementsByClassName('mix');
//      var ths = [];
////      for (var i=0;i<mixs.length;i++) {
////        ths.push(site2['8chan'].catalog_from_native_1(mixs[i]));
////        if (mixs[i].parentNode) mixs[i].parentNode.removeChild(mixs[i]);
////      }
////var time_in = Date.now();
//      for (var i=0;i<mixs.length;i++) if (mixs[i].tagName==='DIV') ths.push(site2['8chan'].catalog_from_native_1(mixs[i],board));
////console.log('aaa: '+(Date.now()-time_in));
////      for (var i=0;i<ths.length;i++) if (ths[i].pn.parentNode) ths[i].pn.parentNode.removeChild(ths[i].pn);
//      return ths;
//    },
//    catalog_from_native_1 : function(th,board) {
//      th.getElementsByTagName('a')[0].removeAttribute('href');
//      var sub = th.getElementsByClassName('subject');
//      sub = (sub[0])? sub[0].textContent : '';
//      var opn = th.getElementsByTagName('img')[0].getAttribute('data-name');
//      var op = th[brwsr['innerText']].replace(th.getElementsByTagName('strong')[0][brwsr['innerText']],'');
//      if (sub!=='') op = op.replace(sub,'');
////      if (pref.catalog_footer_show_board_name) th.getElementsByTagName('strong')[0].innerHTML = '<span></span>' + th.getElementsByTagName('strong')[0].innerHTML + '&emsp;' + board;
//      return {
//        pn: th,
//        exist: false,
//        no: th.getElementsByTagName('img')[0].id.replace(/img\-/,''),
//        search_obj: [ op, sub, opn, '', '', '', '', ''],
//        page_no: '?',
//        time_bumped: (parseInt(th.getAttribute('data-bump'),10)-pref.localtime_offset*3600)*1000,
//        time_created : (parseInt(th.getAttribute('data-time'),10)-pref.localtime_offset*3600)*1000,
//        nof_posts: parseInt(th.getAttribute('data-reply'),10) +1,
//        nof_files: parseInt(th.getElementsByClassName('replies')[0].getElementsByTagName('strong')[0].textContent.replace(/.*I: */,''),10),
////        init_func: site2['8chan'].catalog_from_native_init_elem_func,
//        update_func: site2['8chan'].catalog_from_native_update_elem_func,
//        board: board,
//        footer: th.getElementsByTagName('strong')[0], // temporal
//        domain: '8chan', // temporal
//        sticky: th.getAttribute('data-sticky')==='true', // temporal
//      }
//    },
//    catalog_from_native_init_elem_func : function(th) {
//      th.setAttribute('class','mix');
//      th.setAttribute('style','display: inline-block;');
//    },
//    catalog_from_native_update_elem_func : function(th,src) {
//      th.setAttribute('data-reply',src.nof_posts);
//      th.setAttribute('data-bump',src.time_bumped);
//      th.setAttribute('data-time',src.time_created);
//    },
//    catalog_from_native_1 : function(th,board) { // working code
//      var th2 = {domain:'8chan', board:board, pn:th};
//      this.parse_funcs['catalog_html'].entry(th2,this.parse_funcs['catalog_html']['full_th']);
//      return th2;
//    },
    parse_funcs : { // vichan
      'catalog_html' : {
//        ths: function(doc) {
//          var ths = this.ths_array(doc,doc.pn.getElementsByClassName('mix'));
//          for (var i=0;i<ths.length;i++) ths[i].pn.getElementsByTagName('a')[0].removeAttribute('href');
//          return ths;
//        },
        ths: function(doc) {return this.ths_array(doc,doc.pn.getElementsByClassName('mix'));},
        no : function(th){return parseInt(th.pn.getElementsByTagName('img')[0].id.substr(4),10);},
        time_bumped: function(th){return parseInt(th.pn.getAttribute('data-bump'),10)*1000;},
        time: function(th){return parseInt(th.pn.getAttribute('data-time'),10);},
        time_created: function(th){return th.time*1000;},
        nof_posts: function(th){return parseInt(th.pn.getAttribute('data-reply'),10) +1;},
//        nof_files: function(th){return parseInt(th.pn.getElementsByClassName('replies')[0].getElementsByTagName('strong')[0].textContent.replace(/.*I: */,''),10);},
        nof_files: function(th){return parseInt(th.pn.getElementsByTagName('strong')[0].textContent.replace(/.*I: */,''),10) +1;}, // assumes OP has 1 pic.
        key: function(th){
//          if (!th.hasOwnProperty('no')) th['no'] = this['no'](th);
if (pref.test_mode['0']) {
          if (!th.hasOwnProperty('no')) Object.defineProperty(th,'no',{value:this['no'](th), enumerable:true, configurable:true, writable:true});
}
          return th.domain + th.board + th.no;
        },
        sub: 'DEFAULT.post_html',
        name: function(th){return th.pn.getElementsByTagName('img')[0].getAttribute('data-name');},
        com: function(th){
          return th.pn.getElementsByClassName('replies')[0].innerHTML.replace(/\s*<strong>[^<]*<\/strong>\s*(<br>)*/,'').replace(/\s*$/,'');
        },
        type_com:'html',
//        com: function(th){ // working code.
//if (pref.test_mode['0']) {
//          if (!th.hasOwnProperty('sub')) th.sub = this.sub(th);
//}
//          return th.pn[brwsr.innerText].replace(th.pn.getElementsByTagName('strong')[0][brwsr.innerText],'').replace(th.sub,'');
//        },
        footer: function(th){return th.pn.getElementsByTagName('strong')[0];},
        sticky: function(th){return null;}, // patch
        op_img_url: function(th){
          var img = th.pn.getElementsByTagName('img')[0];
          return (img)? img.getAttribute('src') : undefined; // patch.
        },
//        filename: function(th){return th.op_img_url;},
//        missing_info: {time_posted: null, op_img_src_url: null},
////        missing_info_fetch: function(th){scan.list_nup.add_board(th.domain+th.board,0,null,null,true);scan.scan('b',this.nickname)}, // BUG. catalog doesn't contain time_posted.
//        missing_info_fetch: function(th){scan.list_nup.add(th.key,0);scan.scan('t',this.nickname)}, // BUG. catalog doesn't contain time_posted.
//        dynamic_image_hover: true,
        missing_info: 2,
        img2src: function(img){
          var lth = this.get_lth_from_node(img);
          if (!lth) return null;
          var src = img.src;
          if (lth.th && lth.th.ext) src = src.replace(/thumb/,'src').replace(/.[^\.]*$/,lth.th.ext);
          else if (lth.th.missing_info) scan.scan_ui('image_hover', {tgts: [lth.domain+lth.board+'j0'], options:{callback:function(){cataLog.image_hover_reentry(img);}, priority:6}});
          return src;
        },
        get_op_src: 'thread_json',
        time_unit:1000,
      },
      'catalog_json' : {
        before_test : ['ths',':ITER',':ALL','ths',['key','time_bumped','nof_posts','nof_files']],
        after_test  : ['time_created','pn','footer'],
        full_hier   : ['ths',':ITER',':ALL','ths',['key','time_bumped','nof_posts','nof_files','pn','footer']],
        ths: 'DEFAULT.catalog_json',
        time_bumped: function(th){return th.last_modified*1000;},
        time_created : function(th){return th.time*1000;},
        nof_posts: function(th){return th.replies+1;},
        nof_files: function(th){return th.images+th.omitted_images;},
        key: function(th){return th.domain + th.board + th.no;},
//        sub: function(th){return ('sub' in th)? th.sub : '';},
//        name: function(th){return ('name' in th)? th.name : '';},
//        com: function(th){return ('com' in th)? th.com : '';},
//        sticky: function(th){return (th.sticky===1);}, // prevent memory leak.
        sub: function(th){return (th.hasOwnProperty('sub'))? th.sub : '';},
        name: function(th){return (th.hasOwnProperty('name'))? th.name : '';},
        com: function(th){return (th.hasOwnProperty('com'))? th.com : '';},
//        sticky: function(th){return (th.hasOwnProperty('sticky'))? th.sticky==='1' : false;},
        op_img_url: 'DEFAULT.common',
////        op_img_url: function(obj) { // working code.
////          return site2[obj.domain].protocol + '//' + site2[obj.domain].domain_url + obj.board + 'thumb/' + obj.tim + ((obj.ext==='.jpg')? '.png' : obj.ext);
//////          return this.protocol+ '//' + site2[obj.domain].domain_url + obj.board + 'thumb/' + obj.tim + ((obj.ext==='.jpg')? '.png' : obj.ext);
////        },
        missing_info: 1,
//        missing_info: null,
//        posts: 'DEFAULT.catalog_json', // DOESN'T WORK
        get_op_src: 'thread_json',
        time_unit: 1000,
        type_com: 'html',
        posts: 'DEFAULT.catalog_json',
        posts_full: null, // 'DEFAULT.catalog_json',
        get_max_page: function(doc){return doc.length;},
        proto: 'catalog_html',
      },
      'page_json' : {
        has_posts: true,
        nof_posts: 'DEFAULT.thread_json', // VICHAN HAS A INCONSISTENCY, thread_json doesn't provide 'replies'.
        nof_files: function(th){return th.posts[0].images+th.posts[0].omitted_images;},
        ths: 'DEFAULT.page_json',
        proto:'thread_json'
      },
      'page_html' : {
        before_test : ['ths',':ITER',':ALL','ths',['key','time_bumped','nof_posts','nof_files']],
//        after_test  : [':ITER',':ALL','posts',['sub','name','com'],'flags'],
        after_test  : ['time_created',':ITER',':ALL','posts',['sub','name','com','flag'],':ITER',':GALL','posts',['flags','flag']],
        before_test_post : ['posts',':ITER',':FL','posts',['time'],'html_org','footer'],
//        ths: function(doc) {
//          var ths = doc.pn.querySelectorAll('div[id^=thread_]');
//          if (ths.length===0) ths = doc.pn.querySelectorAll('div[de-thread]'); // for dollchan archive // doesn't work because of other modification.
//          return this.ths_array(doc,ths);
//        },
        ths: function(doc) {return this.ths_array(doc,doc.pn.querySelectorAll('div[id^=thread_]'));},
        th_init: function(th){
          if (site.whereami!=='page' || !pref.catalog.embed_page) th.pn.removeAttribute('class');
//          var as = th.pn.querySelectot_inheritrAll('.files a');
//          for (var j=0;j<as.length;j++) as[j].removeAttribute('href');
////          for (var j=0;j<as.length;j++) as[j].addEventListener('click',th.parse_funcs.preventDefault,false); // not debugged.
        },
//        th_destroy: function(pn, parse_funcs){
////          var as = pn.querySelectorAll('.files a');
////          for (var j=0;j<as.length;j++) as[j].removeEventListener('click',parse_funcs.preventDefault,false); // not debugged.
//        },
//        ths: function(doc) {
//          var ths = this.ths_array(doc,doc.pn.querySelectorAll('div[id^=thread_]'));
//          if (site.whereami!=='page' || !pref.catalog.embed_page) for (var i=0;i<ths.length;i++) ths[i].pn.removeAttribute('class');
//          for (var i=0;i<ths.length;i++) {
//            var as = ths[i].pn.querySelectorAll('.files a');
//            for (var j=0;j<as.length;j++) as[j].removeAttribute('href');
//          }
//          return ths;
//        },
        no: 'post_html',
//        no : function(th){return parseInt(th.pn.id.substring(th.pn.id.indexOf('_')+1),10);},
////        post_no: 'page_html.no',
        time: function(post){
          var pn_time = post.pn.getElementsByTagName('time')[0]; // old threads doesn't have this.
          return (pn_time)? Date.parse(pn_time.getAttribute('datetime'))/1000 : undefined;
        },
        time_unit:1000, // must be the same as json
////////        time_posted: function(th){
////////if (pref.test_mode['0']) {
////////          if (!th.hasOwnProperty('posts')) this.entry(th,this.before_test_post);
////////}
////////          return th.posts[th.posts.length-1].time;
////////        },
        time_bumped: function(th){ // TO BE FIXED.
////if (pref.test_mode['0']) {
////          if (!th.hasOwnProperty('posts')) this.entry(th,this.before_test_post);
////}
////          return th.posts[th.posts.length-1].time;
          var i=th.posts.length;
          while (--i>=0) {
            var mail = th.posts[i].pn.getElementsByClassName('email')[0];
            if (!mail || mail.href!=='mailto:sage') return th.posts[i].time_tu;
          }
          return undefined;
        },
        time_created : function(th){
if (pref.test_mode['0']) {
          if (!th.hasOwnProperty('posts')) this.entry(th,this.before_test_post);
}
          return th.posts[0].time;
        },
        nof_posts: function(th){
          var nof_posts = th.pn.getElementsByClassName('post').length;
          var nof_files = th.pn.getElementsByClassName('fileinfo').length;
          var om_info   = th.pn.getElementsByClassName('omitted')[0];
          if (om_info) {
            var str = om_info.childNodes[0].textContent.replace(/\n/g,'');
            nof_posts += parseInt(str.replace(/\ post.*/,''),10) || 0;
            nof_files += parseInt('0'+str.replace(/\ image.*/,'').replace(/[^\ ]*\ /g,''),10) || 0;
          }
//          th.nof_files = nof_files;
          Object.defineProperty(th,'nof_files',{value:nof_files, enumerable:true, configurable:true, writable:true});
          return nof_posts;
        },
        nof_files: function(th){
          if (!th.hasOwnProperty('nof_posts')) this['nof_posts'](th);
          return th.nof_files;
        },
        key: function(th){
//          if (!th.hasOwnProperty(('no')) th.no = this.no(th);
if (pref.test_mode['0']) {
          if (!th.hasOwnProperty('no')) Object.defineProperty(th,'no',{value:this['no'](th), enumerable:true, configurable:true, writable:true});
}
          return th.domain + th.board + th.no;
        },
        sub: 'DEFAULT.post_html',
        com:  function(post){
          var com = post.pn.getElementsByClassName('body')[0];
//          return (com)? com[brwsr.innerText] : '';
          return (com)? com.innerHTML : '';
        },
        type_com:'html',
        footer: function(th){return this.insert_footer4(th.pn.getElementsByClassName('post op')[0]);},
//        footer: function(th){
//          var footer = document.createElement('div');
//          footer = th.pn.insertBefore(footer,th.pn.getElementsByClassName('post op')[0]);
//          return footer;
//        },
        sticky: function(th){return (th.pn.getElementsByClassName('fa-thumb-tack').length!=0);},
        flag: 'post_html',
////        flag: function(post){return post.pn.getElementsByClassName('flag')[0];},
//        flag: function(post){
//          var flags = post.pn.getElementsByClassName('flag');
//          return (flags.length!=0)? document.importNode(flags[0],false) : null;
//        },
//        flags: function(th){
//          var flags = th.pn.getElementsByClassName('flag');
//          var pn_flags = [];
//          for (var i=0;i<flags.length;i++) pn_flags.push(flags[i].cloneNode(false));
//          return pn_flags;
//        },
//        flags: function(th){ // for on demand access.
//          var flags = [];
//          var i = th.posts.length - pref.catalog_t2h_num_of_posts;
//          if (i<1) i=1;
//          if (th.posts.length!=0) flags[0] = th.posts[0].flag;
//          while (i<th.posts.length) {
//            if (th.posts[i]) flags[i] = th.posts[i].flag;
//            i++;
//          }
//          return flags;
//        },
        op_img_url: function(th){
          var img = th.pn.getElementsByTagName('img');
          if (img.length>=2) {
            th.op_img_url2 = [];
            for (var i=1;i<img.length;i++) {
              var url2 = img[i].getAttribute('src');
              if (url2) th.op_img_url2[th.op_img_url2.length] = url2;
            }
          }
          return (img && img[0])? img[0].getAttribute('src') : undefined; // patch.
        },
        tn_as: function(th){return th.tn_imgs;},
//        tn_imgs: function(th){
//          var imgs = [];
//          var files = th.pn.getElementsByClassName('file');
//          for (var i=0;i<files.length;i++) imgs[i] = files[i].getElementsByTagName('img')[0];
//          return imgs;
//        },
        tn_imgs: function (th){ // post can be accepted.
          var imgs = [];
          var files = ((th.pn.classList.contains('op'))? th.pn.parentNode : th.pn).getElementsByClassName('files')[0];
          if (files) {
            files = files.getElementsByClassName('file');
            for (var i=0;i<files.length;i++) {
              var tmp = files[i].getElementsByTagName('img')[0] || files[i].getElementsByTagName('video')[0];
//              var tmp = files[i].getElementsByTagName('img')[0];
              if (tmp) imgs[imgs.length] = tmp;
            }
          }
          return imgs;
        },
        get_thread_links : function(th){
          var links = [];
          var as = th.pn.getElementsByClassName('post op')[0].getElementsByTagName('a');
          for (var i=0;i<as.length;i++) {
            if (as[i].textContent==='[Reply]') links.unshift(as[i]);
            if (as[i].textContent==='[Last 50 Posts]') links.push(as[i]);
            if (as[i].classList.contains(pref.cpfx+'link')) links.push(as[i]);
          }
          return links;
        },
        get_omitted_info : function(post){return post.pn.getElementsByClassName('omitted')[0];},
        set_omitted_info : function(post, info){post.pn.lastChild.appendChild(info);},
//        replace_omitted_info : function(dst, src){dst.childNodes[0].textContent = src.childNodes[0].textContent;},
        replace_omitted_info2 : function(dst, src){dst.childNodes[0].textContent = (src)? src.childNodes[0].textContent : '';},
        filename: 'thread_html',
        get_op_src: 'thread_json',
        id: 'DEFAULT.thread_html',
        country: 'DEFAULT.thread_html',
        trip: 'DEFAULT.thread_html',
        flag: 'DEFAULT.thread_html',
        name: 'DEFAULT.thread_html',
        pn_name: 'post_html',
      },
      'thread_html' : {
        after_test  : ['time_created',':ITER',':FLx','posts',['sub','name','com','flag'],':ITER',':GFLx','posts',['flags','flag']],
//        'finisher' : function(th){site2['8chan'].remove_posts(th.pn,pref.catalog_t2h_num_of_posts);},
////        pop_post: function(th){ // working code.
////          while (th.idx_pop>=0) {
////            var pn = th.children[th.idx_pop--];
////            if (pn.className && pn.className.indexOf('post')!=-1 && pn.className.indexOf('reply')!=-1) {
////              th.post = {pn:pn, parse_funcs:this, __proto__:th.__proto__};
////              return true;
////            }
////          }
////          return false;
////        },
        get_op_src: 'thread_json',
        filename: function(th){
          var files = ((th.pn.classList.contains('op'))? th.pn.parentNode : th.pn).getElementsByClassName('files')[0];
          if (files) {
            files = files.querySelectorAll(':scope>.file');
            for (var i=0;i<files.length;i++) {
              var fname = files[i].getElementsByTagName('a')[0];
              if (!fname) continue; // file may be deleted.
              fname = fname.textContent;
              if (i===1) th.extra_files = [];
              if (i>=1) th.extra_files[i-1] = {domain:th.domain, board:th.board};
              var tgt = (i===0)? th : th.extra_files[i-1];
              tgt.filename = (th.domain==='lain')? files[i].getElementsByTagName('a')[1].getAttribute('download') // lainchan 2017.04.25- 
                                                 : files[i].getElementsByClassName('postfilename')[0].textContent.replace(/\..*/,'');
              tgt.ext = fname.substr(  fname.indexOf('.'));
              tgt.tim = fname.substr(0,fname.indexOf('.'));
              var info = (th.domain!=='lainjp')? files[i].getElementsByClassName('details')[0].textContent.split(',')
                                               : files[i].getElementsByClassName('unimportant')[0].textContent.split(',');
              tgt.w = parseInt(info[1].substr(0,info[1].indexOf('x')  ),10);
              tgt.h = parseInt(info[1].substr(  info[1].indexOf('x')+1),10);
              var fsize = info[0].split(/\s/);
              fsize[1] = (fsize[1]==='KB')? 1024 : (fsize[1]==='MB')? 1024*1024 : 1;
              tgt.fsize = parseFloat(fsize[0].replace(/\(/,'')) * fsize[1];
              var tn = files[i].querySelector('img.post-image');
              if (tn) {
                tgt.tn_w = parseInt(tn.style.width,10);
                tgt.tn_h = parseInt(tn.style.height,10);
              }
            }
          }
          return (files)? th.filename : undefined;
        },
        proto: 'page_html',
      },
      'thread_json' : {
        nof_posts: function(obj){return obj.posts.length;},
        nof_files: function(obj){
          var count=0;
          for (var i=1;i<obj.posts.length;i++) {
            if (obj.posts[i].filename) count++;
            if (obj.posts[i].extra_files) count += obj.posts[i].extra_files.length;
          }
          return count;
        },
        add_op_img_url: site2['DEFAULT'].parse_parts.add_op_img_url,
//        get_op_src: function(th){return th.op_img_url.replace('thumb','src').replace('.png',th.ext);},
        get_op_src: function(th, img){
          if (!th.ext && th.missing_info) scan.scan_ui('image_hover', {tgts: [th.domain+th.board+'j0'], options:{callback:function(){cataLog.image_hover_reentry(img);}, priority:6}});
          return (th.ext)? th.op_img_url.replace('thumb','src').replace('.png',th.ext) : img.src;
        },
        pn_name: 'post_html',
        proto: 'DEFAULT.thread_json',
      },
      'post_html': {
        com: 'page_html',
        no: function(th){
          var id = th.pn.getAttribute('id');
          return id && parseInt(id.substring(id.indexOf('_')+1),10);},
        name: function(post){return (post.pn_name)? post.pn_name.textContent : '';},
        pn_name: function(post){return post.pn.getElementsByClassName('name')[0];},
        time_pn: function(post_pn){return Date.parse(post_pn.getElementsByTagName('time')[0].getAttribute('datetime'));},
//        txt2com: function(txt){
//          txt = txt.replace(/(^>[^>].*$)/mg,'<span class="quote">$1</span>');
//          txt = txt.replace(/\*\*([^(\*\*)]*)((\*\*)|$)/g,'<span class="spoiler">$1</span>');
//          return txt.replace(/\n/g,'<br>');},
        txt2com_spoiler_replace_txt: '<span class="spoiler">$1</span>',
        flag: function(post){return post.pn.getElementsByClassName('flag')[0];},
        pn_id: function(post){return post.pn.getElementsByClassName('poster_id')[0];},
        id: function(post){return (post.pn_id)? post.pn_id.textContent : undefined;},
        time: 'page_html',
        time_unit:1000,
        proto: 'DEFAULT.post_html',
      },
      'post_json': {
        proto: 'DEFAULT.post_json'
      },
      'json_template': {
        get nofFiles(){return !this.filename? 0 : (!this.extra_files)? 1 : this.extra_files.length+1;},
      },
    },
    update_posts_remove: function(th_old,i,pnode,merge){
      var tgt = th_old.posts[i].pn;
      if (!merge || tgt.parentNode!==pnode) tgt = tgt.parentNode;
      else this.update_posts_insert_pack(tgt);
      pnode.removeChild(tgt.nextSibling);
      pnode.removeChild(tgt);
    },
    update_posts_insert: function(src,dst,i,j,pnode){
////      var ref = (j==1)? (dst[0].pn.getElementsByClassName('op')[0].nextSibling || null) :
//      var ref = (i==1)? (dst[0].pn.nextSibling || null) :
//                        (dst[dst.length-1].pn.parentNode.nextSibling || null);
//      var ref = (j==0)? (dst[0].pn || null) :
//                (j==1)? (dst[0].pn.nextSibling || null) :
//                (j<dst.length)? (dst[j].pn.parentNode.nextSibling || null) :
//                (dst[dst.length-1].pn.parentNode.nextSibling &&
//                 dst[dst.length-1].pn.parentNode.nextSibling.nextSibling || null);
      var ref = (j<dst.length)? dst[j].pn : null;
//      var ref = dst[(j<dst.length)? j : dst.length-1];
//      ref = (ref)? ref.pn : null;
      var ref_pnode = ref && ref.parentNode;
      if (ref_pnode && ref_pnode!==pnode) ref = ref_pnode; // remove container
//      if (j>=1 && (!merge || ref.parentNode!==pnode)) ref = ref.parentNode; // remove container
      if (j>=dst.length) ref = pnode.getElementsByClassName('clear')[0]; // for universal merge with lazy draw, dst[dst.length-1].pn may remain in old pnode.
//      if (j>=dst.length) ref = ref && ref.nextSibling && ref.nextSibling.nextSibling || null;
      if (!src[i].pn) src[i].pn = this.post_json2html(src[i]);
//      pnode.insertBefore(this.post_container(src[i].pn,src[i].no), ref); // working code.
//      pnode.insertBefore(document.createElement('br'),ref);
//      if (ref && ref.parentNode!==pnode) ref = ref.parentNode; // patch for thread merging.
      if (i===0) this.update_posts_insert_pack(src[i].pn);
      if (j===0 && dst[0]) this.update_posts_insert_pack(dst[0].pn);
//      if (j===0 && ref) this.update_posts_insert_pack(dst[0].pn); // BUG???
      var tgt = (j===0 && i===0)? src[i].pn : this.post_container(src[i].pn,src[i].no); // patch for thread merging.
      pnode.insertBefore(tgt, ref);
      pnode.insertBefore(document.createElement('br'),ref);
      if (i===0 && j===0) this.update_posts_insert_unpack(src[i].pn);
//      if (j===0) {
//        ref = dst[0].pn.nextSibling;
//        pnode.insertBefore(this.post_container(dst[0].pn,dst[0].no), ref);
//      }
    },
    update_posts0_class: function(pn,search_result) {
      site2['DEFAULT'].update_posts0_class(pn,search_result);
      this.update_posts_insert_pack_iter(pn, (search_result)? function(prev){prev.classList.remove('CatChan_search_miss');} : 
                                                              function(prev){prev.classList.add('CatChan_search_miss');});
    },
    update_posts_insert_pack: function(pn){
      this.update_posts_insert_pack_iter(pn, function(prev){pn.insertBefore(prev, pn.firstChild);});
    },
    update_posts_insert_pack_iter: function(pn, func){
      var prev = pn.previousSibling;
      while (prev) {
        var prev_prev = prev.previousSibling; // prev is livelist.
        if (prev.classList && (prev.classList.contains(pref.cpfx+'footer') || prev.classList.contains('files'))) func(prev);
        if (prev.classList &&  prev.classList.contains('op')) break;
        prev = prev_prev;
      }
    },
    update_posts_insert_unpack: function(pn){
      while (1) {
        var fchild = pn.firstChild;
        if (fchild && fchild.classList && (fchild.classList.contains(pref.cpfx+'footer') || fchild.classList.contains('files'))) pn.parentNode.insertBefore(fchild, pn);
        else break;
      }
    },
    add_backlinks_bks: (function(){
      var bks = document.createElement('span');
      bks.setAttribute('class','mentioned unimportant');
      return function(){
        return bks.cloneNode(false);
      };
    })(),
    add_backlinks_add_1: (function(){
      var blk = document.createElement('a');
      blk.setAttribute('class','mentioned'); // mentioned-XXXX in lainchan.
      return function(bks, dbtpth){
        var pn = blk.cloneNode(true);
        pn.setAttribute('href',dbtpth[5]);
        pn.textContent = dbtpth[4];
        pn.onclick = this.backlink_onclick;
//        pn.onmouseover = this.popups_post_entry;
        bks.appendChild(pn);
      };
    })(),
    add_backlinks_bks_query: function(pn){
      return pn.getElementsByClassName('mentioned')[0];
    },
////    add_backlinks: function(pn,backlinks,target,th){
////      var bks_pn = pn.getElementsByClassName('mentioned')[0];
////      var bks = bks_pn || this.add_backlinks_bks();
////      if (!target) bks.innerHTML = '';
////      for (var i=(target || 0);i<backlinks.length;i++) {
////        var dbtp = this.popups_backlink2dbtpth(backlinks[i], th);
////        var domain = dbtp[0];
//////        var board = dbtp[1];
//////        var post_no = dbtp[3];
////        var href = site2[domain].link_dbtp2href(dbtp);
////        if (domain!==site.nickname) href = site2[domain].absolute_link_1(href);
//////        var blk = null; // IS THIS REQUITED???
//////        if (target!==undefined)
//////          for (var j=0;j<bks.childNodes.length;j++)
//////            if (bks.childNodes[j].textContent==='>>'+post_no) {
//////              blk = bks.childNodes[j];
//////              break;
//////            }
//////        if (!blk)
////        this.add_backlinks_add_1(bks, dbtp, href);
////        if (target) break;
////      }
////      if (!bks_pn) {
////        var ref = pn.getElementsByClassName('post_no')[1];
////        var pnode = ref.parentNode;
////        do {ref = ref.nextSibling;} while (ref && ref.tagName==='I');
////        pnode.insertBefore(bks,ref);
////      }
////    },
    add_backlinks_bks_append: function(pn, bks){
      var ref = pn.getElementsByClassName('post_no')[1];
      var pnode = ref.parentNode;
      do {ref = ref.nextSibling;} while (ref && ref.tagName==='I');
      pnode.insertBefore(bks,ref);
    },
    popups_href2dbtp: function(href){ //, src, th){
//      if (href[0]==='#') {
//        href = th.board+'res/'+th.no+'.html'+href;
//        src.setAttribute('href',href);
//      }
      var hrefs = href.split('/');
      var p = hrefs[hrefs.length-1].substr(hrefs[hrefs.length-1].indexOf('#')+1);
      var t = hrefs[hrefs.length-1].substr(0,hrefs[hrefs.length-1].indexOf('.')) || href[0]==='#' && site.no;
      var b = (hrefs.length>=3)? '/'+hrefs[hrefs.length-3]+'/' : site.board;
      var d = this.nickname;
      return [d,b,t,p];
    },
    link_dbtp2href: function(dbtp){
      return dbtp[1] + 'res/' + dbtp[2] + '.html#' + dbtp[3];
    },
    backlink_onclick: function(){
      highlightReply.call(this,parseInt(this.textContent.substr(2),10));
    },
//    backlink_class: 'mentioned',
    colorID: function(pn) {
      var id = pn.getElementsByClassName('poster_id')[0];
      if (id) {
        var bg = id.textContent;
        var bg_r = parseInt(bg.substr(0,2),16);
        var bg_g = parseInt(bg.substr(2,2),16);
        var bg_b = parseInt(bg.substr(4,2),16);
        var fg = (bg_r + bg_g + bg_b>384)? 0 : 255;
        id.setAttribute('style','padding: 0px 5px; border-radius: 8px; color: rgb('+fg+','+fg+','+fg+'); background-color: rgb('+bg_r+','+bg_g+','+bg_b+');');
      }
    },
//    localtime: function(pn) {
//      var pn_time = pn.getElementsByTagName('time');
//      for (var i=0;i<pn_time.length;i++)
//        pn_time[i].textContent = site2.common.change_utc_to_local(pn_time[i].getAttribute('datetime'));
//    },
//    post_com2txt: function(com){
//      return com.replace(/<[^>]*>/g,' ').replace(/&gt;/g,'>').replace(/&lt;/g,'<').replace(/&amp;/g,'&').replace(/&hellip;/g,'\u2026'); // most of lainchan, speed: 10.62/2.02
//    },
    post_container : function(post_pn,no) {
      var pn = document.createElement('div');
      pn.setAttribute('id','pc'+no);
      pn.setAttribute('class','postcontainer');
      pn.innerHTML = '<div class="sidearrows">&gt;&gt;</div>';
      pn.appendChild(post_pn);
      return pn;
    },
    short_link: function(name, nof_posts, num, kwd_head, kwd_tail){
      var dbt = comf.fullname2dbt(name);
      return (nof_posts>100)? '<a href="' +dbt[1]+ 'res/' +dbt[2]+ '+50.html" class="' + pref.cpfx+'link' + '">'+kwd_head+'50'+kwd_tail+'</a>' : '';
    },
    post_json2html: function(post, op, short_link) {
      var board = post.board;
      var pn = document.createElement('div');
      var time_unit = (post.parse_funcs && post.parse_funcs.time_unit) || 1;
      var date = new Date((post.time || 0) * time_unit);
      var html_str =
        '<div class="post ' +((op)? 'op':'reply')+ '" id="reply_'+post.no+'" style="border: none">'+
//        '<div class="post reply" id="reply_'+post.no+'" style="border: none">'+
          '<p class="intro">'+
            '<input type="checkbox" class="delete" name="delete_'+post.no+'" id="delete_'+post.no+'">'+
            '<label for="delete_'+post.no+'">'+
              ((post.sub)? '<span class="subject">'+post.sub+ ' </span>' : '')+
              ((post.email)? '<a class="email" href="mailto:' + post.email + '">' : '') +
                '<span class="name">'+(post.name || '')+'</span>'+
                ((post.trip)? '<span class="trip">' + post.trip + '</span>' : '') +
              ((post.capcode)? '<span class="capcode" style="color:orange;font-weight:bold">## ' + post.capcode + '</span>' : '') +
              ((post.email)? '</a> ' : ' ') +
              ((post.country)? '<img class="flag flag-' + post.country.toLowerCase() + '" src="/static/blank.gif" style="width:16px;height:11px;" alt="' + post.country_name + '" title="' + post.country_name +'"> ' : ' ')+
              '<time datetime="'+ date.toISOString() + '">' + date.toLocaleString()+'</time>'+
            '</label>'+
            ((post.id)? '<span class="poster_id">'+post.id+'</span>' : '')+
            '&nbsp;'+
            '<a class="post_no" id="post_no_'+post.no+'">No.</a>'+
//            '<a class="post_no" onclick="citeReply(12486)" href="/tech/res/12393.html#q12486">12486</a>'+
            '<a class="post_no">'+post.no+'</a>'+
            ((op)? ('<a href="' + site2[post.domain].make_url4([post.domain, board, post.no, 'thread_html'])[0] +'">[Reply]</a>'+
//            ((op)? ('<a href="' +board+ 'res/' +post.no+ '.html">[Reply]</a>'+
                   ((short_link)? short_link : '')) : '') +
//////////            '<span class="mentioned unimportant"><a class="mentioned-12497" onclick="highlightReply('12497');" href="#12497">&gt;&gt;12497</a></span>'+
          '</p>';
      if (post.filename) {
        html_str += '<div class="files">' +
                    this.post_json2html_file(post,board, post.extra_files);
        if (post.extra_files) for (var i=0;i<post.extra_files.length;i++) html_str += this.post_json2html_file(post.extra_files[i],board, true);
        html_str += '</div>';
      }
      html_str +=
          '<div class="body">'+ ((post.com)? post.com : '') +
//////////            '<a onclick="highlightReply('12446');" href="/tech/res/12393.html#12446">&gt;&gt;12446</a><br>'+
//////////            '<a onclick="highlightReply('12403');" href="/tech/res/12393.html#12403">&gt;&gt;12403</a><br>'+
//////////            '<span class="quote"><br>&gt;people saying nice things about openstack</span><br><br>Thanks, lainons. I work on developing openstack and people seem to love complaining about it, so it's heartening to hear somewhat happy people.'+
          '</div>'+
        '</div>';
      pn.innerHTML = html_str;
      return pn.childNodes[0];
    },
    post_pn2ce: function(pn){
      return (pn.lastChild.className==='body')? pn.lastChild : pn.getElementsByClassName('body')[0];
    },
    post_json2html_file : function(post, board, multifile) {
      var fsize_str = (((post.fsize>1048576)? post.fsize/1048576 : post.fsize/1024)+0.005).toString();
      fsize_str = fsize_str.substr(0,fsize_str.indexOf('.')+3) + ((post.fsize>1048576)? ' MB' : ' KB');
      var fname_server = site2[post.domain].post_json2html_fname_server(post);
      var fname = site2[post.domain].post_json2html_fname(post);
      var furl = site2[post.domain].catalog_json2html3_src(post,board);
      var turl = site2[post.domain].catalog_json2html3_thumbnail(post,board);
      var html_str = 
              '<div class="file' + ((multifile)?' multifile':'')+'"' +((multifile)?' style="width:'+(post.tn_w+40)+'px"':'') +'>'+
                '<p class="fileinfo">File: '+
                  '<a href="'+furl+'">'+fname_server+'</a> '+
                  '<span class="details">('+fsize_str+', '+post.w+'x'+post.h+', '+
                    '<span class="postfilename">'+fname+'</span>) '+
////////                  '<span class="unimportant image_id">'+
//////////                    '<a href="http://imgops.com/https://lainchan.org/tech/src/1445201930315.jpg" target="_blank">ImgOps</a>'+
//////////                    '<a href="http://regex.info/exif.cgi?url=https://lainchan.org/tech/src/1445201930315.jpg" target="_blank">Exif</a>'+
//////////                    '<a href="http://iqdb.org/?url=https://lainchan.org/tech/src/1445201930315.jpg" target="_blank">iqdb</a>'+
////////                  '</span>'+
                  '</span>'+
                '</p>'+
                '<a href="'+furl+'" target="_blank">'+
                  ((post.ext!=='.webm')? '<img class="post-image" src="'+turl+'" style="width: '+post.tn_w+'px; height: '+post.tn_h+'px; max-width: 98%;" alt=""'+
                                         ((post.tn_w===128 && post.tn_h===128)? 'onerror="this.onerror=null;this.src=\'/static/spoiler.png\';"' : '')+ '>' : // vichan doesn't have any information about spoilered.
                                         '<video class="post-image" src="'+furl+'" style="width: '+post.tn_w+'px; height: '+post.tn_h+'px; max-width: 98%;">')+
                '</a>'+
              '</div>';
      return html_str;
    },
    page_json2html3_skelton: function(obj, both){
      var th = document.createElement('div');
      th.setAttribute('class','thread');
      th.setAttribute('id','thread_'+obj.no);
      th.setAttribute('data-board',obj.board.slice(1,-1));
//      th.setAttribute('style','float: left; overflow: hidden;');
      var br = document.createElement('br');
      br.setAttribute('class','clear');
      th.appendChild(br);
      th.appendChild(document.createElement('hr'));
      return (both)? [th,br] : th;
    },
    page_json2html3: function(obj, clone, dst, force_expander, no_expander){
      var doms = this.page_json2html3_skelton(obj,true);
      var th = doms[0];
      var ref = doms[1];
      var op = this.post_json2html((obj.posts && obj.posts[0])? obj.posts[0] : obj, true, site2[obj.domain].short_link(obj.key, obj.nof_posts, 2, '[Last ',' Posts]'));
      var files = op.getElementsByClassName('files')[0];
      if (files) th.insertBefore(files, ref);
      if (clone && obj.posts[0].pn) op = obj.posts[0].pn.cloneNode(true);
      op.setAttribute('class','post op');
      if (obj.posts && obj.posts[0] && obj.posts[0].extra_files) op.setAttribute('style','clear:both;');
      th.insertBefore(op, ref);
//      if (assign_pn) obj.posts[0].pn = th;
      obj.posts[0].pn = op;
      if (obj.posts) {
        for (var i=1;i<obj.posts.length;i++) {
//          th.appendChild(this.post_json2html(obj.posts[i], board));
          var pn = clone && obj.posts[i].pn && obj.posts[i].pn.cloneNode(true) || this.post_json2html(obj.posts[i]);
          th.insertBefore(this.post_container(pn,obj.posts[i].no), ref);
          th.insertBefore(document.createElement('br'), ref);
          obj.posts[i].pn = pn;
        }
        if (!no_expander) this.page_json2html3_add_omitted_info(obj, dst, force_expander);
////////      } else { // WILL BE REDUNDANT
////////        var keys = Object.keys(obj); // obj.posts===undefined, but it is defined as undefined, so key contains 'posts'. 
////////        obj.posts = [];
////////        obj.posts[0] = {};
////////        for (var i=0;i<keys.length;i++) if (keys[i]!=='posts') obj.posts[0][keys[i]] = obj[keys[i]]; // remove props in prototype chain.
////////        obj.posts[0].pn = op;
      }
      return th;
    },
    page_json2html3_replace_expander : function(posts_old, idx, key) {
      var omit_info = posts_old[0].pn.getElementsByClassName('omitted')[0];
      if (omit_info) {
        if (omit_info.childNodes.length>=3) omit_info.removeChild(omit_info.childNodes[2]);
        if (omit_info.childNodes.length>=2) omit_info.removeChild(omit_info.childNodes[1]); // 'if ()' is a parch for a bug in lainchan, inconsistency between usual boards and /mega/
//        omit_info.removeChild(omit_info.childNodes[1]);
        omit_info.appendChild(cnst.config_expander(key, idx));
      }
    },
    page_json2html3_prep_omitted_info: function(posts0, th, force_expander){
      var omit_info = posts0.pn.getElementsByClassName('omitted')[0];
      if (!omit_info) {
        omit_info = document.createElement('span');
        omit_info.setAttribute('class','omitted');
//            omit_info.innerHTML = 'dummy<a href="javascript:void(0)">Click to expand</a>.';
        omit_info.innerHTML = 'dummy';
        posts0.pn.appendChild(omit_info);
      }
      if (force_expander) omit_info.appendChild(cnst.config_expander(th.key));
      return omit_info.childNodes[0];
    },
    page_json2html3_set_omitted_info: function(nof_posts_omitted, nof_files_omitted) {
      return (nof_posts_omitted<=0)? 'Showing all replies.'
        : nof_posts_omitted +' post' + ((nof_posts_omitted!==1)? 's':'') +
          ((nof_files_omitted)? ' and ' + nof_files_omitted + ' image repl'+ ((nof_files_omitted===1)? 'y' : 'ies') : '') + ' omitted. ';
    },
//    page_json2html3_add_omitted_info : function(th, th_old, force_expander) { // working code
//      var posts = th.posts;
//      var nof_files = 0;
//      for (var i=1;i<posts.length;i++) nof_files += (posts[i].type_data==='html')? posts[i].pn.getElementsByClassName('file').length :
//                                                    (!posts[i].filename)? 0 : (posts[i].extra_files)? posts[i].extra_files.length+1 : 1;
//      var posts_deleted = posts.filter(function(v){return v.deleted_after;});
//      var nof_files_omitted = th.nof_files - nof_files + posts_deleted.reduce(function(a,v){return a + ((!v.filename)? 0 : (v.extra_files)? v.extra_files.length+1 : 1);},0);
//      var nof_posts_omitted = th.nof_posts - posts.length + posts_deleted.length;
//      site2[th_old && th_old.domain_html || th.domain_html].page_json2html3_add_omitted_info_html(th, th_old, force_expander, nof_posts_omitted, nof_files_omitted); // 'th_old &&' is a patch for working regardless of pref.test_mode['140'], it can be removed when I fix the test_mode to true.
//    },
//    page_json2html3_add_omitted_info_html: function(th, th_old, force_expander, nof_posts_omitted, nof_files_omitted, from_initial) {
//if (pref.test_mode['140']) {
//      if (!th_old.pn_summary) {
//        force_expander |= th.domain!==this.nickname;
//        if (nof_posts_omitted>0 || force_expander) {
//          var posts0 = th_old.posts? th_old.posts[0] : th.posts[0];
//          var omit_info = posts0.pn.getElementsByClassName('omitted')[0];
//          if (!omit_info) {
//            omit_info = document.createElement('span');
//            omit_info.setAttribute('class','omitted');
////            omit_info.innerHTML = 'dummy<a href="javascript:void(0)">Click to expand</a>.';
//            omit_info.innerHTML = 'dummy';
//            posts0.pn.appendChild(omit_info);
//          }
//          if (force_expander) omit_info.appendChild(cnst.config_expander(th.key));
//          th_old.pn_summary = omit_info.childNodes[0];
//        }
//      }
//      if (th_old.pn_summary && !from_initial)
//        th_old.pn_summary.textContent = (nof_posts_omitted<=0)? 'Showing all replies.'
//                                      : nof_posts_omitted +' post' + ((nof_posts_omitted!==1)? 's':'') +
//                                        ((nof_files_omitted)? ' and ' + nof_files_omitted + ' image repl'+ ((nof_files_omitted===1)? 'y' : 'ies') : '') + ' omitted. ';
//} else {
//  var posts_old = th.posts.pn && th.posts;
//      var omit_info = (posts_old)? posts_old[0].pn.getElementsByClassName('omitted')[0] : null;
//      if (nof_posts_omitted!=0) {
////      if (th.nof_posts>1) {
//        if (!omit_info) {
//          omit_info = document.createElement('span');
//          omit_info.setAttribute('class','omitted');
////          omit_info.innerHTML = 'dummy<a href="javascript:void(0)">Click to expand</a>.';
//          omit_info.innerHTML = 'dummy';
//          omit_info.appendChild(cnst.config_expander(th.key));
//          if (posts_old) posts_old[0].pn.appendChild(omit_info);
//        }
////        omit_info.childNodes[0].textContent = (nof_posts_omitted!==0)? (nof_posts_omitted +' post' + ((nof_posts_omitted!==1)? 's':'') +
////          ((nof_files_omitted)? ' and ' + nof_files_omitted + ' image repl'+ ((nof_files_omitted===1)? 'y' : 'ies') : '') + ' omitted. ') : '';
//        omit_info.childNodes[0].textContent = nof_posts_omitted +' post' + ((nof_posts_omitted!==1)? 's':'') +
//          ((nof_files_omitted)? ' and ' + nof_files_omitted + ' image repl'+ ((nof_files_omitted===1)? 'y' : 'ies') : '') + ' omitted. ';
//      } else if (omit_info) omit_info.childNodes[0].textContent = 'Showing all posts.'; //if (omit_info && posts_old) posts_old[0].pn.removeChild(omit_info);
//      return omit_info;
//}
//    },
    catalog_json2html3_thumbnail: function(obj, board) {
      return ((obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png')? 'https://' + this.domain_url + board + 'thumb/' + obj.tim + obj.ext : '');
    },
    catalog_json2html3_src: function(obj, board) {
      return ((obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png' || obj.ext==='.webm')? 'https://' + this.domain_url + board + 'src/' + obj.tim + obj.ext : '');
    },
    catalog_json2html3 : function(obj,board,thumb_url) {
      var th = document.createElement('div');
      th.setAttribute('class','mix');
      th.setAttribute('style','display: inline-block;');
//      if (obj.ext==='.gif' || obj.ext==='.png') obj.ext='.jpg';
//      if (obj.ext==='.gif' || obj.ext==='.jpeg') obj.ext='.jpg';
      th.innerHTML = '<div class="thread grid-li grid-size-' + site2[site.nickname].catalog_native_size + '">' +
                     '<a href="' + site2[obj.domain].make_url4([obj.domain,obj.board,obj.no,'thread_html'])[0] + '">' +
//                     '<a href="' + thumb_url + '">' +
//                     '<a>' +
                       '<img src="' + thumb_url +
                       '" id="img-' + obj.no +
                       '" data-subject="' + obj.sub +
                       '" data-name="' + obj.name +
                       '" data-muhdifference="" data-last-reply="" data-last-subject="" data-last-name="" data-last-difference=""' +
                       'class="' + board + ' thread-image" title="' + new Date(obj.last_modified*1000).toLocaleString() + ' '+obj.ext + '"></a>' +
                     '<div class="replies"><strong></strong>' + // (make_footer?' R: ' + (obj.nof_posts-1) +' / I: ' + obj.nof_files : '') + '</strong>' + 
                     ((obj.sub)? '<p class="intro"><span class="subject">' + obj.sub + '</span></p>' : '<br>') +
                     obj.com + '</div></div>';
      return th;
    },
//    catalog_json2html3 : function(obj,board) {
//      th = document.createElement('div');
////      if (obj.ext==='.gif' || obj.ext==='.png') obj.ext='.jpg';
//      if (obj.ext==='.gif' || obj.ext==='.jpeg') obj.ext='.jpg';
//      th.innerHTML = '<div class="thread grid-li grid-size-small"><a href="'
//                   + site2['8chan'].make_url3(board,obj.no) + '"><img src="'
//                   + ((obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png')? 'https://' + site2['8chan'].domain_url + board + 'thumb/' + obj.tim + obj.ext : '')
//                   + '" id="img-'
//                   + obj.no  + '" data-subject="'
//                   + obj.sub + '" data-name="'
//                   + obj.name+ '" data-muhdifference="" data-last-reply="" data-last-subject="" data-last-name="" data-last-difference=""'
////                   + 'class="scriptcdc thread-image" title="' + new Date(obj.last_modified*1000).toLocaleString() +'"></a>'
//                   + 'class="'+board+' thread-image" title="' + new Date(obj.last_modified*1000).toLocaleString() + ' '+obj.ext + '"></a>'
//                   + '<div class="replies"><strong>R: '
//                   + obj.replies +' / I: '
//                   + (obj.images+obj.omitted_images)
////                   + ((pref.catalog_footer_show_board_name)? ' '+board : '')
//                   + '</strong>'
//                   + ((obj.sub)? '<p class="intro"><span class="subject">' + obj.sub + '</span></p>' : '')
//                   + obj.com + '</div></div>';
//      return th;
//    },
    catalog_from_json3 : function(obj,board) {
      var ths;
if (pref.test_mode['0']) {
      ths = {domain:'8chan', board:site.board, obj:obj};
      this.parse_funcs['catalog_json'].entry(ths,this.parse_funcs['catalog_json']['full_hier']);
} else {
//      ths = {domain:'8chan', board:board, obj:obj, parse_funcs:this.parse_funcs['catalog_json'], __proto__:site4.parse_funcs_on_demand};
////      var parse_obj = {domain:'8chan', board:board, parse_funcs:this.parse_funcs['catalog_json'], __proto__:site4.parse_funcs_on_demand};
////      ths = {obj:obj, __proto__:parse_obj};
      return site2[this.nickname].wrap_to_parse.get(obj, this.nickname, board, 'catalog_json');
}
      return ths.ths;
    },
//    catalog_json2html2 : function(obj,board) {
//      th = document.createElement('div');
//      th.setAttribute('data-reply',obj.replies);
//      th.setAttribute('data-bump',obj.last_modified);
//      th.setAttribute('data-time',obj.time);
////      if (obj.ext==='.gif' || obj.ext==='.png') obj.ext='.jpg';
//      if (obj.ext==='.gif' || obj.ext==='.jpeg') obj.ext='.jpg';
//      th.innerHTML = '<div class="thread grid-li grid-size-small"><a href="'
//                   + site2['8chan'].make_url3(board,obj.no) + '"><img src="'
////                   + ((obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png')? 'https://media.' + site2['8chan'].domain_url + board + 'thumb/' + obj.tim + obj.ext : '')
//                   + ((obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png')? 'https://' + site2['8chan'].domain_url + board + 'thumb/' + obj.tim + obj.ext : '')
//                   + '" id="img-'
//                   + obj.no  + '" data-subject="'
//                   + obj.sub + '" data-name="'
//                   + obj.name+ '" data-muhdifference="" data-last-reply="" data-last-subject="" data-last-name="" data-last-difference=""'
////                   + 'class="scriptcdc thread-image" title="' + new Date(obj.last_modified*1000).toLocaleString() +'"></a>'
//                   + 'class="scriptcdc thread-image" title="' + new Date(obj.last_modified*1000).toLocaleString() + ' '+obj.ext + '"></a>'
//                   + '<div class="replies"><strong>R: '
//                   + obj.replies +' / I: '
//                   + (obj.images+obj.omitted_images)
////                   + ((pref.catalog_footer_show_board_name)? ' '+board : '')
//                   + '</strong>'
//                   + ((obj.sub)? '<p class="intro"><span class="subject">' + obj.sub + '</span></p>' : '')
//                   + obj.com + '</div></div>';
//      return th;
//    },
//    catalog_from_json2 : function(doc_json,board) {
//    catalog_from_json2 : function(obj,board) {
////      var obj = JSON.parse(doc_json);
//      var ths = [];
//      for (var i=0;i<obj.length;i++)
//        for (var j=0;j<obj[i].threads.length;j++) {
//          var name = '8chan' + board + obj[i].threads[j].no;
//          var pn = site2['8chan'].catalog_json2html2(obj[i].threads[j],board);
//          ths.push(site2['8chan'].catalog_from_native_1(pn,board));
//          ths[ths.length-1][14] = i + '.' +j;
//        }
//      return ths;
//    },
    patch:{
      expand_thumbnail_inline_load: function(){
        var pnode = this.parentNode && this.parentNode.parentNode;
        if (pnode && pnode.classList && pnode.classList.contains('multifile') && pnode.style.width) { // patch for multifile in vichan
          this.setAttribute('data-originalWidth',pnode.style.width);
          pnode.style = '';
        }
      },
    },
    proto : 'DEFAULT'
  };
}
if (pref.features.domains['KC']) {
  site2['KC'] = {
    nickname : 'KC',
    domain_url: 'krautchan.net',
//    features : {thread_reader: true},
    components: {
      boardlist: '.menu',
      postform_comment: '#postform_comment',
      postform_submit: '#postform_submit',
    },
    make_tack: function(){
      var tack = document.createElement('img');
      tack.setAttribute('src','/images/sticky.gif');
      return tack;
    },
    boards_json: site2['DEFAULT'].generate_boards_json(
      [['b',15],['int',20],'vip',
       'a','c','co','d','e','f','fb','fe','fit','jp','k','l','li',['m',5],'n','ng','p','ph','prog','sp',
       't',['trv',18],'tu','tv','v','w','we','wk','wp','z','zp','ft','r',['h',8],'s','bans','kc','rfk',['rvss',15]], 10),
    check_func : function(){
      if (!site2['KC'].force_https) site2['KC'].protocol = site.protocol;
      var href = window.location.href;
      if (href.search(/krautchan.net/)!=-1) { // Krautchan
        site.whereami = (document.head.innerHTML.indexOf('404 Not Found')!=-1)? '404'
                      : (href.search(/krautchan\.net\/?$/)!=-1)? 'frame'
                      : (href.search(/catalog/)!=-1)? 'catalog'
                      : (href.search(/thread/)!=-1)? 'thread'
                      : (href.search(/\/$|(index|[0-9]+)\.html|\/#all$/)!=-1)? 'page'
                      : 'other';
        if (site.whereami==='page' || site.whereami==='thread') {
          this.components.boardlist = 'body > div:nth-child(3)';
          delete this.show_boardlist_physical_extract_func;
        }
        site.config('krautchan.net','KC');
        site.max_page  = (site2['KC'].max_page_kc[site.board]==undefined)? 10 : site2['KC'].max_page_kc[site.board];
        if (site.whereami==='catalog') {
          if (href[href.length-1]==='/') href = href.substr(0,href.length-1);
          site.board = href.substr(href.lastIndexOf('/'))+'/';
        }
        if (site.whereami==='frame') {
          site.root_body2 = document.createElement('div');
          site.embed_frame = 'main';
          site.embed_frame_win = main;
          navigation.onload = function(){
            site.root_body = navigation.document.body;
            site.root_body.appendChild(site.root_body2);
          };
        }
        site.embed_to = (site.whereami==='thread' || site.whereami==='page')? {
          top: function(){return document.querySelector('form[action="/delete"]');},
          bottom: function(){return document.querySelector('form[action="/delete"]').nextSibling;}} : {};
//        if (site.whereami==='thread' || site.whereami==='page') {
//          site.embed_to['top']    = document.querySelector('form[action="/delete"]');
//          site.embed_to['bottom'] = document.querySelector('form[action="/delete"]').nextSibling;
////        } else if (site.whereami==='catalog') {
////          site.embed_to['top']    = document.getElementsByTagName('header')[0].nextSibling;
////          site.embed_to['bottom'] = document.getElementsByTagName('footer')[0];
//        }
        site.postform = document.getElementById('postform');
        site.postform_rules = document.getElementById('rules_row');
////        this.pref_default(pref); // working code.
        return true;
      } else return false;
    },
////    pref_default: function(pref){ // working code.
////      pref.thread_reader.own_posts_tracker = true;
////      pref.thread_reader.check_num_of_children = false;
////    },
    force_https : false,
    protocol : 'https:',
//    home : site.protocol + '//krautchan.net', // cause memory leak
    home : site.protocol + '//krautchan.net/regeln.html',
//    home : site.protocol + '//krautchan.net/favicon.ico',
    show_boardlist_physical_extract_func: function(key){return key.substr(1,key.length-2);},
    catalog_frame_prep: function(pn12){
      var source = [navigation.document.body, main.document.body];
//      for (var i=0;i<2;i++) {
//        var container = document.createElement('div');
//        container.style.display = 'none';
//        while (source[i].childNodes.length!=0) container.appendChild(source[i].childNodes[0]);
//        source[i].appendChild(container);
//      }
      site2['common'].absorb_children(source[0]).style.display = 'none';
      site2['common'].absorb_children(source[1]).style.display = 'none';
      source[0].insertBefore(pn12,source[0].firstChild);
      source[0].insertBefore(pn12,source[0].firstChild);
      site.root_body.appendChild(site.root_body2);
      this.catalog_embed_prep(pn12);
    },
////    catalog_background : '#cfcede',
////    catalog_background : '#dadafc',
//    catalog_background : '#e0e0fc',
//    catalog_bordercolor : '#aaaacc',
//    catalog_threads_in_page : function(doc){return doc.getElementsByClassName('thread');},
    catalog_threads_in_page : function(doc){return doc.getElementsByClassName('thread_body');},
//    remove_posts : function(doc){
//      var threads_body = doc.getElementsByClassName('thread_body');
//      var posts = [];
//      for (var i=0;i<threads_body.length;i++) {
//        posts_in_thread = threads_body[i].getElementsByTagName('table');
//        for (var j=0;j<posts_in_thread.length;j++) posts.push(posts_in_thread[j]);
//      }
//      for (var i=posts.length-1;i>=0;i--) posts[i].innerHTML = '';
//      return doc;
//    },
    max_page_kc : {
      '/int/' : 20,
      '/b/'   : 15,
      '/trv/' : 13,
      '/m/'   : 5
    },
    max_page: function(bn){return site2['KC'].max_page_kc[bn];},
    make_url4 : function(dbt){
      var url_prefix = this.protocol + '//' + this.domain_url;
      dbt[3] = dbt[3].replace(/_json/,'_html');
      if      (dbt[3]==='page_html')    return [url_prefix + dbt[1] + ((dbt[2]==0)? '' : dbt[2] + '.html'), 'html'];
      else if (dbt[3]==='catalog_html') return [url_prefix + '/catalog' + dbt[1].substr(0,dbt[1].length-1), 'html'];
      else if (dbt[3]==='thread_html')  return [url_prefix + dbt[1] + 'thread-' + dbt[2] + '.html', 'html'];
    },
    trim_list: 'no',
//////////    make_url : function(board,no){return [site2['KC'].protocol + '//krautchan.net' + board + ((no==0)? '' : no + '.html'), 'html'];}, // working code.
////////    make_url : function(board,no,key){
////////      var url_prefix = site2['KC'].protocol + '//krautchan.net';
////////      return (key==='p')? [url_prefix + board + ((no==0)? '' : no + '.html'), 'html'] : 
////////                          [url_prefix + '/catalog' + board.substr(0,board.length-1),'html'];
////////    },
////////    make_url3: function(board,th){return  site2['KC'].protocol + '//krautchan.net' + board + 'thread-' + th + '.html';},
    get_next_image: function(img,top){
      var imgs = Array.prototype.slice.call(document.querySelectorAll('img[id^=thumbnail]')).filter(function(v){return v.src.substr(-5,5)!=='.webm';});
//      var imgs = cataLog.parent.querySelectorAll('img[id^="thumbnail"]');
      return site2['DEFAULT'].get_next_image(img,top,imgs);
    },
    get_ops : function(doc){
      var ops = [];
      var divs = doc.getElementsByTagName('div');
      for (var i=0;i<divs.length;i++)
        if (divs[i].className == 'thread' || divs[i].className == 'thread kc_showReplies')
          ops.push(divs[i].id.substring(7)); // substring(7) for removing 'thread_'
      return ops;
    },
    get_posts : function(doc) {
      var posts = [];
      var anchors = doc.getElementsByTagName('a');
      for (var i=0;i<anchors.length;i++) if (anchors[i].name != '') posts.push(anchors[i].name);
      return posts;
    },
////////    get_thread_link : function(pn,boardname,del){
//////////      var keyword = (boardname=='/int/')? 'Reply' : 'Antworten';
////////      var as = pn.getElementsByClassName('postheader')[0].getElementsByTagName('a');
//////////      for (var i=0;i<as.length;i++) if (as[i][brwsr.innerText]==keyword) return as[i].getAttribute('href');
//////////      for (var i=0;i<as.length;i++) if (as[i].innerHTML==keyword) {
////////      for (var i=0;i<as.length;i++) if (as[i][brwsr.innerText]=='Reply' || as[i][brwsr.innerText]=='Antworten') {
////////        var href = as[i].getAttribute('href');
////////        if (del) {
////////          var j=0;
////////          var parent = as[i].parentNode;
////////          while (parent.childNodes[j]!=as[i]) j++;
////////          parent.removeChild(parent.childNodes[j+1]);
////////          parent.removeChild(as[i]);
////////          parent.removeChild(parent.childNodes[j-1]);
////////        } else as[i].setAttribute('target',(pref.catalog_open_in_new_tab)? '_blank' : '_self');
////////        return href;
////////      }
////////      return null;
////////    },
    modify_thread_link : function(pn){
      var retval = [];
      var as = pn.getElementsByClassName('postheader')[0].getElementsByTagName('a');
      for (var i=0;i<as.length;i++) if (as[i][brwsr.innerText]=='Reply' || as[i][brwsr.innerText]=='Antworten') {
        var href = as[i].getAttribute('href');
        retval.push([as[i],href]);
        as[i].removeAttribute('href');
      }
      return retval;
    },
//    add_thread_link : function(doc,url){
//      var pn = document.createElement('a');
//      pn.href = url.replace(/https*:\/\/krautchan\.net/,'');
//      pn.innerHTML = 'Reply';
//      var th = doc.getElementsByClassName('postheader')[0];
//      if (th) {
//        th.insertBefore(pn,th.firstChild);
//        th.insertBefore(document.createTextNode('['),pn);
//        th.insertBefore(document.createTextNode(']'),pn.nextSibling);
//      }
//    },
    time_offset : 1, // 1 for usual, 2 for summer time.
//    get_time_of_posts : function(doc){ // working code.
//      var postdates = doc.getElementsByClassName('postdate');
//      return [parseInt(brwsr.Date_parse(postdates[postdates.length-1][brwsr.innerText]),10) - site2['KC'].time_offset*3600000,
//              parseInt(brwsr.Date_parse(postdates[0                 ][brwsr.innerText]),10) - site2['KC'].time_offset*3600000];
//    },
//    get_time_of_posts : function(th){ // cause error
//      var posts = th.getElementsByTagName('table');
//      return [site2['KC'].get_time_of_post_in_utc(posts[posts.length-1]),site2['KC'].get_time_of_post_in_utc(posts[0])];
//    },
//    get_time_of_post_in_utc : function(post){ // working code
//      var postdates = post.getElementsByClassName('postdate');
//      if (postdates[0]) return parseInt(brwsr.Date_parse(postdates[0][brwsr.innerText]),10) - site2['KC'].time_offset*3600000;
//    },
//    mark_newer_posts: function(th,date,unmark, short_cut) {
//      var pn = site2['DEFAULT'].mark_newer_posts('KC',th.getElementsByTagName('table'),date, function(post){return post.getElementsByClassName('postreply')[0];}, unmark, short_cut);
//      return (pn!=null)? pn.offsetParent : null;
//    },
    localtime: function(pn){
      var postdate = pn.getElementsByClassName('postdate')[0];
      if (postdate) {
        var lt = postdate.cloneNode(false);
        lt.setAttribute('class','CatChan_localtime');
        lt.textContent = site2.common.change_utc_to_local(site2['KC'].parse_funcs.post_html.time_pn_1(postdate)); // use direct path to hope to be inline optimized.
        postdate.parentNode.insertBefore(lt,postdate.nextSibling);
        postdate.setAttribute('style','display:none');
      }
//      var times = pn.getElementsByClassName('postdate');
//      for (var i=0;i<times.length;i++) times[i].textContent = site2.common.change_utc_to_local(Date.parse(times[i][brwsr.innerText])-site2['KC'].time_offset*3600000);
//      for (var i=0;i<times.length;i++) times[i].textContent = site2.common.change_utc_to_local(brwsr.Date_parse(times[i][brwsr.innerText])-site2['KC'].time_offset*3600000);
    },
    remove_files_info : function(th){
      site2.common.remove_by_classname(th,'filename');
      site2.common.remove_by_classname(th,'fileinfo');
      var imgs = th.getElementsByTagName('img');
      for (var i=0;i<imgs.length;i++) if (imgs[i].id && imgs[i].id.search('thumbnail')!=-1) {
        imgs[i].removeAttribute('onmouseover');
        imgs[i].removeAttribute('onmouseout');
        imgs[i].removeAttribute('onload');
        site2.common.move_up_and_delete_parent([imgs[i]]);
      }
      var files = th.getElementsByClassName('file_thread');
      for (var i=0;i<files.length;i++) site2.common.remove_brs(files[i]);
      for (var i=0;i<files.length;i++) site2.common.remove_by_attribute([files[i]],'id','filename');
//      site2.common.remove_by_classname(th,'file_thread');
//      site2.common.remove_by_classname(th,'postbody');
    },
////////    insert_footer : function(th,page_no,bn,exe,date,nof_posts,nof_files){
////////      var key = (!brwsr.ff)? 'innerText' : 'innerHTML';
////////      var posts = th.getElementsByTagName('table');
////////      nof_posts += posts.length +1; // +1 for OP.
////////      var files = th.getElementsByClassName('file_thread');
////////      nof_files += th.getElementsByClassName('filename').length;
////////      var om_info = th.getElementsByClassName('omittedinfo');
////////      if (om_info[0]) {
////////        var str = om_info[0][key].replace(/\n/g,'');
////////        nof_posts += parseInt(str.replace(/\ post.*/,''),10);
////////        nof_files += parseInt('0'+str.replace(/\ file.*/,'').replace(/[^\ ]*\ /g,''),10);
////////      }
////////      if (exe) {
////////        var pn = document.createElement('div');
//////////        pn.name = 'catalog_footer';
////////        pn.setAttribute('name','catalog_footer');
//////////        pn[key] = bn + '  ' + nof_posts + '/' + nof_files + '/' + page_no + '  ';
////////        pn.innerHTML = '<span>' + bn + '  ' + nof_posts + '/' + nof_files + '/' + page_no + '  </span>';
////////        var imgs = th.getElementsByTagName('img');
////////        for (var i=0;i<imgs.length;i++) {
//////////          if (imgs[i].src && imgs[i].getAttribute('src').search(/images\/balls/)!=-1) pn.appendChild(imgs[i].cloneNode(false)); // doesn't work in KC.
////////          if (imgs[i].getAttribute('src') && imgs[i].getAttribute('src').search(/images\/balls/)!=-1) pn.appendChild(imgs[i].cloneNode(false));
////////        }
////////        th.insertBefore(pn,files[0]);
////////      }
////////      return [nof_posts,nof_files];
////////    },
    remove_posts : function(th,end){
//      site2.common.remove_by_tagname(th,'table',end);
//      site2.common.remove_double_tags(th,'A');
      var tgts = th.getElementsByTagName('table');
      for (var i=tgts.length-1-end;i>=0;i--) {
        if (tgts[i].previousSibling.previousSibling) tgts[i].parentNode.removeChild(tgts[i].previousSibling.previousSibling); // a tags
        tgts[i].parentNode.removeChild(tgts[i].previousSibling); // text
//if (!pref.test_mode['14']) tgts[i].setAttribute('style','display:none;'+(tgts[i].getAttribute('style') || ''));
//else
        tgts[i].parentNode.removeChild(tgts[i]);
      }
    },
//    format_thread_layout : function(th){
//      site2.common.add_attribute_by_classname(th,'file_thread','style','float:left;margin:5px');
//      site2.common.add_attribute_by_classname(th,'file_reply','style','float:left;margin:5px');
//      th.getElementsByTagName('blockquote')[0].setAttribute('style','margin:5px;clear:both');
////      var ph = th.getElementsByClassName('postheader');
///      th.insertBefore(ph,th.getElementsByClassName('postbody')[0]);
//    },
//    format_thread_style : function(th){
//      site2.common.add_attribute_by_classname(th,'postreply','style','background:#cacaec');
//    },
//    format_thread_contents : function(th){
//      site2.common.remove_by_tagname(th,'input');
//      site2.common.remove_by_classname(th,'report_parent');
//      site2.common.remove_by_attribute(th.getElementsByClassName('postheader'),'onclick','hideThread');
//      site2.common.remove_by_attribute(th.getElementsByClassName('file_thread'),'onclick','toggleThumbnail');
////      site2.common.add_attribute_by_attribute(th.getElementsByClassName('file_thread'),'onclick','toggleThumbnail','style','display:none');
//      site2.common.remove_by_attribute(th.getElementsByClassName('file_thread'),'href','shipainter');
////      site2.common.remove_by_classname(th,'thread_hidden');
//      site2.common.remove_by_tagname(th,'hr');
////      site2.common.remove_by_tagname(th,'br');
//      site2.common.remove_by_classname(th,'omittedinfo');
//    },
    preprocess_html : function(doc_txt,page){ // cause memory leak in chrome, but fail without this in FF or GreaseMonkey.
      if (doc_txt) {
//        if (page) while (doc_txt.search(/<script[>\ ](.|\n)*<\/script>/)!=-1) doc_txt = doc_txt.replace(/<script[>\ ](.|\n)*<\/script>/,''); // slower execution.
//        while (doc_txt.search(/onload="imageFinishedLoading\([0-9]*\)"/)!=-1) doc_txt = doc_txt.replace(/onload="imageFinishedLoading\([0-9]*\)"/,''); // slower execution.
//        if (page) doc_txt = doc_txt.replace(/<script[>\ ](.|\n)*<\/script>/mg,''); // cause leak in Chrome, probably a bug.
        if (page) doc_txt = doc_txt.replace(/<script[> ].*<\/script>/mg,''); // cause leak in Chrome, probably a bug. (remove \n)
        doc_txt = doc_txt.replace(/onload="imageFinishedLoading\([0-9]*\)"/mg,''); // cause leak in Chrome, probably a bug.
// sanitize
        doc_txt = doc_txt.replace(/onmouseover="[^"]*"/mg,'');
        doc_txt = doc_txt.replace(/onmouseout="[^"]*"/mg,'');
        doc_txt = doc_txt.replace(/onclick="[^"]*"/mg,'');
      }
      return doc_txt;
    },
//    preprocess_doc : function(doc){
////      site2.common.remove_by_tagname(doc,'script');
//      site2.common.remove_attribute(doc,'onload');
//      site2.common.remove_attribute(doc,'onmouseover');
//      site2.common.remove_attribute(doc,'onmouseout');
//      site2.common.remove_attribute(doc,'onclick');
//
//      site2.common.remove_attribute(doc,'onchange');
//      site2.common.remove_attribute(doc,'onfocus');
//      site2.common.remove_attribute(doc,'onsubmit');
//var pns = doc.getElementsByTagName('*');
//for (var i=pns.length-1;i>=0;i--)
//  if (pns[i].getAttribute && pns[i].getAttribute('href') && pns[i].getAttribute('href').indexOf('javascript:')==0) pns[i].removeAttribute('href');
//
//if (pref.test_mode['28']) {
//      doc.getElementsByTagName('html')[0].innerHTML = doc.getElementsByTagName('html')[0].innerHTML; // remake to sanitize. // Leak is caused by DOMParser itself.
//}
//    },
////////    thread2headline : function(doc){
////////      return site2.common.thread2headline(doc,'KC');
////////    },
    catalog_native_prep: function(pn_filter,pn_tb,pn_hi){
      var node_ref = (site.whereami==='catalog')? document.getElementsByClassName('catalog')[0]
                                                : document.querySelector('form[action="/delete"]');
//      var node_ref = (site.whereami==='catalog')? document.getElementById('settings')
//                                                : document.getElementsByName('postcontrols')[0];
//      pn_tb.setAttribute('style', 'float:right');
//      if (site.whereami==='catalog') {
      var selector_catchan = pn_filter.getElementsByTagName('select')['catalog.order.ordering'];
      selector_catchan.setAttribute('style','float:left');
      node_ref.parentNode.insertBefore(selector_catchan,node_ref);
      pn_tb.childNodes[3].setAttribute('style','float:left');
      node_ref.parentNode.insertBefore(pn_tb,node_ref);
////////      return site2['KC'].catalog_from_native(date,document,site.board,site.whereami+'_html');
    },
////////    catalog_from_native : function(date,doc,board,type) { // working code.
////////      return site2[this.nickname].wrap_to_parse.get(doc, this.nickname, board, type);
////////////      var parse_obj = {domain:'KC', board:board, parse_funcs:site2['KC'].parse_funcs[type], __proto__:site4.parse_funcs_on_demand};
////////////      var ths = {pn:doc, __proto__:parse_obj};
////////////      return ths.ths;
////////    },
    catalog_get_native_area: function(){
      if (site.whereami==='page') return document.querySelector('form[action="/delete"]');
      else return document.getElementsByClassName('catalog')[0];
    },
////    get_click_area: function(pn, th){
////      var areas = [];
////      if (th.type_html==='catalog_html') {
////        var tn = pn.getElementsByClassName('thumbnail');
////        for (var i=0;i<tn.length;i++) areas.push(tn[i].getElementsByTagName('img')[0]);
////      } else if (th.type_html==='page_html') {
////        var fths = pn.getElementsByClassName('file_thread');
////        for (var i=0;i<fths.length;i++) {
////          var imgs = fths[i].getElementsByTagName('img');
////          areas.push(imgs[imgs.length-1]);
////        }
////      }
////      return (areas.length!=0)? areas : [pn]; 
////    },
//    get_click_area: function(pn, th){
//      if (th.type_html==='catalog_html') {
//        var tn = pn.getElementsByClassName('thumbnail');
//        return (tn[0])? tn[0].getElementsByTagName('img')[0] : pn;
//      } else if (th.type_html==='page_html') {
//        var imgs = pn.getElementsByClassName('file_thread')[0].getElementsByTagName('img');
//        return imgs[imgs.length-1];
//      }
//    },
    no_JSONAPI: true,
    parse_funcs : { // KC
      'catalog_html' : {
        ths: function(doc) {
          var ths = this.ths_array(doc,doc.pn.getElementsByClassName('thread'));
          var t = Date.now() - pref.localtime_offset*3600000; // patch
          for (var i=0;i<ths.length;i++) {
            if (ths[i].time_bumped<0) ths[i].time_bumped += t;
//            var as = ths[i].pn.getElementsByTagName('a');
//            for (var j=0;j<as.length;j++) as[j].removeAttribute('href');
          }
          return ths;
        },
//        th_init: function(th) {
//          var as = th.pn.getElementsByTagName('a');
//          for (var j=0;j<as.length;j++) as[j].addEventListener('click',th.parse_funcs.preventDefault,false);
//        },
//        th_destroy: function(pn, parse_funcs){
//          var as = pn.getElementsByTagName('a');
//          for (var j=0;j<as.length;j++) as[j].removeEventListener('click',parse_funcs.preventDefault,false);
//        },

        no : function(th){return parseInt(th.pn.getAttribute('id').substr(7),10);}, // the same as 4chan.
//        time_bumped: function(th){return 0;},
        time_bumped: function(th){
          return -1 -parseInt(th.page.substr(0,th.page.indexOf('.')),10)*16 - parseInt(th.page.substr(th.page.indexOf('.')+1));},
        time_created : function(th){return 0;},
        nof_posts: function(th){
          var footer = th.pn.getElementsByClassName('omitted_text')[0];
//          Object.defineProperty(th, 'footer', {value:footer, enumerable:true, writable:true, configurable:true});
//          footer.innerHTML = '';
          var nof_posts = (footer)? parseInt(footer.textContent.match(/[0-9]+/),10) : 0;
          return (isNaN(nof_posts))? 1 : nof_posts + 1;
        },
        nof_files: function(th){return 0;},
        key: function(th){return th.domain + th.board + th.no;},
        sub: function(th){
          var sub = th.pn.getElementsByTagName('header')[0].textContent.trim();
          return (sub.search(/^#[0-9]+$/)==0)? '' : sub;
        },
        name: function(th){return '';},
//        com: function(th){return th.pn.getElementsByTagName('section')[0][brwsr.innerText];},
        com: function(th){return th.pn.getElementsByTagName('section')[0].innerHTML;},
        type_com: 'html',
        footer: function(th){
          var footer = th.pn.getElementsByClassName('omitted_text')[0]; // must parse pn because of mimic mode.
          footer.innerHTML = '';
          return footer;
        },
        sticky: function(th){return false;},
        op_img_url:function(th){
//          var img = th.pn.getElementsByClassName('thumbnail')[0].getElementsByTagName('img');
          var tn  = th.pn.getElementsByClassName('thumbnail');
          var img = (tn[0])? tn[0].getElementsByTagName('img') : null; // parse other site's html
          var url = (img && img[img.length-1])? img[img.length-1].getAttribute('src') : undefined;
          return url;
        },
        get_op_src: 'thread_html',
        filename: function (th){ // post can be accepted.
          var op = th.pn.classList.contains('thread') || th.pn.classList.contains('thread_body');
          var files = th.pn.getElementsByClassName('post_files')[0].getElementsByTagName('figure');
          if (files) 
            for (var i=0;i<files.length;i++) {
              var href = files[i].getElementsByTagName('img')[0].getAttribute('src');
              if (i===1) th.extra_files = [];
              if (i>=1) th.extra_files[i-1] = {domain:th.domain, board:th.board};
              var idx = href.lastIndexOf('/');
              var tgt = (i===0)? th : th.extra_files[i-1];
//              tgt.filename = href.substr(idx+1);
              tgt.filename = files[i].getElementsByTagName('img')[0].getAttribute('alt');
//              tgt.filename_server = href.substr(0,idx).replace(/[^\/]*\//g,'');
              tgt.filename_server = href.substr(idx+1);
            }
          return th.filename;
        },
        prep_mimic: 'page_html',
        tn_imgs: function(){return [];}, // dynamic, or function(th){return th.pn.getElementsByClassName('thumbnail');},
        img2src: function(img){return th.op_img_url.replace('thumbnails','files');},
//        dynamic_image_hover: true,
      },
      'catalog_json' : {
        proto: 'catalog_html',
      },
      'page_html' : {
        ths: function(doc) {return this.ths_array(doc, doc.pn.getElementsByClassName('thread'));},
        th_init: function(th) {
          th.pn.removeAttribute('class');
          th.pn.style.clear = 'none';
        },
//        th_destroy: function(pn, parse_funcs){},
//        no : function(th){return th.pn.id.substr(7);},
//        no : function(th){return parseInt(th.pn.id.substr((th.pn.classList.contains('thread'))? 7:5),10);}, // page and posts
        no : function(th){ // page and posts
          return parseInt((th.pn.id)? th.pn.id.replace(/[^_\-]*./,'') : // /int/
                                      th.pn.getElementsByClassName('postnumber')[0].getElementsByTagName('a')[1].textContent, 10);}, // other boards.
//        ths: function(doc) { // working code.
//          var ths = this.ths_array(doc, doc.pn.getElementsByClassName('thread'));
//          for (var i=0;i<ths.length;i++) {
//            Object.defineProperty(ths[i], 'no', {value:ths[i].pn.id.substr(7), enumerable:true, writable:true, configurable:true});
//            ths[i].pn.removeAttribute('class'); // collection ISN'T writable? and if wrote, its enumerator doesn't work.
//            ths[i].pn.style.clear = 'none';
//          }
//          return ths;
//        },
//        no : function(th){return parseInt(th.pn.getElementsByClassName('quotelink')[0][brwsr.innerText],10);},
        posts: function(th){return this.posts_array(th, th.pn.querySelectorAll('.thread_body, td.postreply'));},
//        posts: function(th){return this.posts_array(th, th.pn.querySelectorAll('.thread_body, .postreply'));}, // doesn't work for thread_reader.
//        posts: function(th){ // working code.
//          var posts = [];
//          posts[0] = {pn:th.pn.getElementsByClassName('thread_body')[0], __proto__:th.__proto__}; // OP
//          var replies = th.pn.getElementsByClassName('postreply');
//          for (var i=0;i<replies.length;i++) posts[posts.length] = {pn:replies[i], __proto__:th.__proto__};
//          return posts;
//        },
        time: 'post_html.time',
        nof_posts: function(th){
          var nof_posts = th.pn.getElementsByTagName('table').length +1; // +1 for OP.
          var nof_files = th.pn.getElementsByClassName('filename').length;
          var om_info = th.pn.getElementsByClassName('omittedinfo');
          if (om_info[0]) {
            var str = om_info[0][brwsr.innerText].replace(/\n/g,'');
            nof_posts += parseInt(str.replace(/\ post.*/,''),10);
            nof_files += parseInt('0'+str.replace(/\ file.*/,'').replace(/[^\ ]*\ /g,''),10);
          }
          Object.defineProperty(th,'nof_files',{value:nof_files, enumerable:true, configurable:true, writable:true});
          return nof_posts;
        },
        nof_files: function(th){
          if (!th.hasOwnProperty('nof_posts')) this['nof_posts'](th);
          return th.nof_files;
        },
        sub:  function(post){
          var sub = post.pn.getElementsByClassName('postsubject')[0];
          return (sub)? sub.textContent : '';},
        name: function(post){return post.pn.getElementsByClassName('postername')[0][brwsr.innerText];},
        pn_name: function(post){return post.pn.getElementsByClassName('postername')[0];},
//        com:  function(post){return (post.pn.getElementsByClassName('postbody')[0] || post.pn).getElementsByTagName('p')[0][brwsr.innerText];},
        com:  function(post){return (post.pn.getElementsByClassName('postbody')[0] || post.pn).getElementsByTagName('p')[0].innerHTML.replace(/^\s*/,'').replace(/\s*$/,'');},
        type_com: 'html',
        footer: function(th){return this.insert_footer4(th.pn.getElementsByClassName('file_thread')[0]);},
//        sticky: function(th){return (th.pn.getElementsByClassName('stickyIcon').length!=0);},
        flag: function(post){  // same as 8chan
          var balls = post.pn.getElementsByTagName('img');
          for (var i=0;i<balls.length;i++) if (balls[i].getAttribute('src').search(/images\/balls/)!=-1) break;
//          return (i<balls.length)? balls[i].cloneNode(false) : null; // may cause memory leak
//          return (i<balls.length)? document.importNode(balls[i],false) : null;
          if (i<balls.length) {
            var ball = document.importNode(balls[i],false);
            ball.setAttribute('src', site2['KC'].absolute_link_1(ball.getAttribute('src')));
            return ball;
          } else return null;
//          if (i<balls.length) { // didn't stop memory leak.
//            var ball = document.createElement('img');
//            ball.setAttribute('src',balls[i].getAttribute('src'));
//            return ball;
//          } else return null;
        },
        op_img_url:function(th){
//          var img = th.pn.getElementsByClassName('file_thread')[0].getElementsByTagName('img');
          var ft  = th.pn.getElementsByClassName('file_thread');
          var img = (ft[0])? ft[0].getElementsByTagName('img') : null; // parse other site's html
          var url = (img && img[img.length-1])? img[img.length-1].getAttribute('src') : undefined;
          if (ft.length>=2) {
            th.op_img_url2 = [];
            for (var i=1;i<ft.length;i++) {
              img = ft[i].getElementsByTagName('img');
              var url2 = (img && img[img.length-1])? img[img.length-1].getAttribute('src') : undefined;
              if (url2) th.op_img_url2[th.op_img_url2.length] = url2;
            }
          }
//          return url;
          return site2[th.domain].protocol + '//' + site2[th.domain].domain_url + url; // patch
        },
////        post_no: function(post){return parseInt(post.pn.getElementsByClassName('quotelink')[1][brwsr.innerText],10);},
        get_op_src: 'thread_html',
        get_thread_links : function(th){
          var as = th.pn.getElementsByClassName('postheader')[0].getElementsByTagName('a');
          for (var i=0;i<as.length;i++) 
            if (as[i].textContent==='Reply' || as[i].textContent==='Antworten') return [as[i]];
        },
        get_omitted_info : function(post){return post.pn.getElementsByClassName('omittedinfo')[0];},
        set_omitted_info : function(post, info){post.pn.insertBefore(info,post.pn.getElementsByClassName('postbody')[0].nextSibling);},
        replace_omitted_info2 : function(dst, src){dst.childNodes[0].textContent = (src)? src.childNodes[0].textContent : '';},
        filename: function (th){ // post can be accepted.
          var op = th.pn.classList.contains('thread') || th.pn.classList.contains('thread_body');
          var files = th.pn.getElementsByClassName((op)? 'file_thread' : 'file_reply');
          if (files) 
            for (var i=0;i<files.length;i++) {
              var href = files[i].getElementsByClassName('filename')[0].getElementsByTagName('a')[0].getAttribute('href');
              if (i===1) th.extra_files = [];
              if (i>=1) th.extra_files[i-1] = {domain:th.domain, board:th.board};
              var idx = href.lastIndexOf('/');
              var tgt = (i===0)? th : th.extra_files[i-1];
              tgt.filename = href.substr(idx+1);
//              tgt.filename_server = href.substr(0,idx).replace(/[^\/]*\//g,'');
              tgt.filename_server = href.substr(0,idx).replace(/[^\/]*\//g,'').replace(/jpeg$/,'jpg');
              var info = files[i].getElementsByClassName('fileinfo')[0].textContent.replace(/\s/g,'').split(',');
              tgt.w = parseInt(info[1].substr(0,info[1].indexOf('x')  ),10);
              tgt.h = parseInt(info[1].substr(  info[1].indexOf('x')+1),10);
              var fsize = info[1].split(/\s/);
              fsize[1] = (fsize[1]==='kB')? 1000 : (fsize[1]==='MB')? 1000000 : 1;
              tgt.fsize = parseInt(fsize[0],10) * fsize[1];
              var tn = files[i].querySelector('img[id^=thumbnail]');
              if (tn) {
                tgt.tn_w = parseInt(tn.getAttribute('width'),10);
                tgt.tn_h = parseInt(tn.getAttribute('height'),10);
              }
            }
          return th.filename;
        },
        tn_imgs: function(th){return th.pn.querySelectorAll('img[id^=thumbnail]');},
        prep_mimic: function (th){
//          th.posts[0].no = th.no; // doesn't work because posts[0].no has getter.
//          for (var i=0;i<th.posts.length;i++) Object.defineProperty(th.posts[i],'no',{value:th.no}); // test patch
          th.op_img_url = th.parse_funcs.op_img_url(th);
        },
        get_op_src: function(th){return site2['KC'].catalog_json2html3_src(th);},
        get_max_page: function(doc){
//          var pns = doc.querySelectorAll('form[action*=delete]')[0].lastChild.previousSibling.previousSibling.previousSibling.getElementsByTagName('*'); // CAN'T GET [1] because it's a text node.
          var pns = doc.querySelectorAll('form[action*=delete]')[0].lastChild.previousSibling.previousSibling.previousSibling.childNodes[1].childNodes[1].childNodes[0].childNodes[3].childNodes; // WORKS ONLY PAGE 0.
          var max = 0;
          for (var i=0;i<pns.length;i++) {
            var text = pns[i].textContent.replace(/\s+/g,'');
            if (max+1 == text || '['+(max+1)+']'===text) max++; // == used intentionally.
          }
          return max;
        },
      },
      'thread_html' : {
//        ths: function(doc) { // pn of thread isn't used.
//          var ths = site2['KC'].parse_funcs.page_html.ths(doc);
//          for (var i=0;i<ths.length;i++) {
//            var ph = ths[i].pn.getElementsByClassName('postheader')[0];
//            if (ph) {
//              ph.appendChild(document.createTextNode(' ['));
//              var link = document.createElement('a');
//              link.setAttribute('href',ths[i].board + 'thread-' + ths[i].no + '.html');
//              link.innerHTML = 'Reply';
//              ph.appendChild(link);
//              ph.appendChild(document.createTextNode('] '));
//            }
//          }
//          return ths;
//        },
//        ths: function(doc) {return this.ths_array(doc, doc.pn.getElementsByClassName('thread_body'));}, // patch, but for what???, de-patched for trial.
//        com:  function(post){return post.pn.getElementsByTagName('blockquote')[0][brwsr.innerText];},
        com:  function(post){return post.pn.getElementsByTagName('blockquote')[0].innerHTML.replace(/^\s*/,'').replace(/\s*$/,'');},
        type_com: 'html',
        time_created: function(th){
return th.parse_funcs.time(th.posts[0]);},
        time_bumped: function(th){
return th.parse_funcs.time(th.posts[th.posts.length-1]);},
////        pop_post_prep: function(th){ // working code.
////          th.children = th.pn.getElementsByClassName('postreply');
////          th.idx_pop = th.children.length-1;
////        },
////        pop_post: function(th){
////          if (th.idx_pop>=0) {
////            th.post = {pn:th.children[th.idx_pop--], parse_funcs:this, __proto__:th.__proto__};
////            return true;
////          }
////          return false;
////        },
        get_op_src: function(th){return th.op_img_url.replace('thumbnails','files');},
        proto: 'page_html',
      },
      'post_html' : {
        flag: 'page_html',
        time: function(post){
          return site2['KC'].parse_funcs.post_html.time_pn(post.pn); // use direct path to hope to be inline optimized.
        },
        time_pn: function(post_pn){
          var postdate = post_pn.getElementsByClassName('postdate')[0];
          return (postdate)? site2['KC'].parse_funcs.post_html.time_pn_1(postdate) : undefined; // use direct path to hope to be inline optimized.
        },
        time_pn_1: function(postdate){
          return Date.parse(postdate.textContent) + (pref.localtime_offset-site2['KC'].time_offset)*3600000; // FF accepts KC's format
        },
      },
      'post_json' : {
        time_unit: 1, // for archive.list_all
      }
    },

    general_event_handler:{
//      catalog:{ // working code
//        mouseover: function(e){
//          var et = e.target;
//          var et_tagName = et.tagName;
//          if (et_tagName==='IMG')
//            if (pref[cataLog.embed_mode].image_hover && et.parentNode.className==='thumbnail') cataLog.image_hover_add.call(e.target,e, e.target.src.replace(/thumbnail/,'file'));
//        },
//        __proto__: site2['DEFAULT'].general_event_handler.catalog
//      },
    },

    popups_href2dbtp: function(href){ //, src, th){
//      if (href[0]==='#' && th) {
//        href = this.link_dbtp2href([th.domain, th.board, th.no, href.substr(1)]);
//        src.setAttribute('href',href);
//      }
      var hrefs = href.split(/[\/#]/);
      var p = hrefs[hrefs.length-1];
      var t = hrefs[hrefs.length-2].replace('thread-','').replace('.html','');
      var b = (hrefs.length>=3)? '/'+hrefs[hrefs.length-3]+'/' : site.board;
      return ['KC',b,t,p]
    },
    link_dbtp2href: function(dbtp){
      return dbtp[1] + 'thread-' + dbtp[2] + '.html#' + dbtp[3];
    },

    post_json2html_fname_server: function(post){return post.filename_server;},
    post_json2html_fname: function(post){return post.filename;},
    catalog_json2html3_src: function(obj) {
      return site.protocol + '//' + site2[obj.domain].domain_url + '/files/' + obj.filename_server;
    },
    catalog_json2html3_thumbnail: function(obj) {
      return site.protocol + '//' + site2[obj.domain].domain_url + '/thumbnails/' + obj.filename_server;
    },
//    catalog_json2html3_thumbnail: function(obj, board) {
//      var ext = (obj.ext==='.jpg' || obj.ext==='.png' || obj.ext==='.gif' || obj.ext==='.webm')? '.jpg' : obj.ext;
//      return (obj.ext)? 'http://i.4cdn.org' + obj.board + obj.tim + 's' + ext : '';
//    },




    
    post_json2html: function(post, op, short_link, th_no) {
      var board = post.board;
      var pn = document.createElement('div');
      var time_unit = (post.parse_funcs && post.parse_funcs.time_unit) || 1;
      var date = new Date((post.time || 0) * time_unit -(pref.localtime_offset-site2['KC'].time_offset)*3600000);
      var html_file = '';
      if (post.filename) {
        html_file += this.post_json2html_file(post,board, post.extra_files);
        if (post.extra_files) for (var i=0;i<post.extra_files.length;i++) html_file += this.post_json2html_file(post.extra_files[i],board, true);
      }
      pn.innerHTML =
        '<div class="' +((op)? 'thread_body' : 'postreply" id="post-' +post.no) + '">'+ // <td> can't be used alone.
          '<div class="postheader">'+
            '<input name="post_' + post.no+ '" value="delete" type="checkbox">'+
            ((post.flag)? post.flag.outerHTML : '')+ //  '<img src="/images/balls/ca.png">'+
            '<span class="postsubject">' +post.sub+ '</span> '+
            '<span class="postername">' +post.name+ '</span> '+
            '<span class="postdate">' + date.toLocaleString()+ '</span> '+
            '<span class="postnumber">'+
              '<a href="/int/thread-' +th_no+ '.html#' +post.no+ '" class="quotelink">No.</a>'+
              '<a href="/int/thread-' +th_no+ '.html#q'+post.no+ '" class="quotelink">' +post.no+ '</a>'+
            '</span> '+
            '<div class="report_parent"><a href="/report/int/' +th_no +'" class="report_link"><img src="/images/icon-report.png"></a></div>'+
            ((op)? ' [<a href="' + board + 'thread-' + post.no + '.html">Reply</a>] ' : '')+
          '</div>'+
          html_file+
          '<div' + ((op)? ' class="postbody"' : '') + '>'+
            '<blockquote>'+
              '<p id="post_text_' +post.no+ '">'+ post.com + '</p>'+
            '</blockquote>'+
          '</div>'+
        '</div>'; //'<td>';
      return pn.childNodes[0];
    },
    post_pn2ce: function(pn){ // NOT DEBUGGED BECAUSE KC WAS CLOSED.
      return (pn.lastChild.firstChild.tagName==='BLOCKQUOTE')? pn.lastChild.firstChild : pn.getElementsByTagName('blockquote')[0];
    },
    post_json2html_file : function(post, board, multifile) {
      var fsize_str = (((post.fsize>1048576)? post.fsize/1048576 : post.fsize/1024)+0.005).toString();
      fsize_str = fsize_str.substr(0,fsize_str.indexOf('.')+3) + ((post.fsize>1048576)? ' MB' : ' KB');
      var fname_server = site2[post.domain].post_json2html_fname_server(post);
      var fname = site2[post.domain].post_json2html_fname(post);
      var furl = site2[post.domain].catalog_json2html3_src(post,board);
      var turl = site2[post.domain].catalog_json2html3_thumbnail(post,board);
      var html_str =
        '<div class="file_thread">'+
          '<span class="filename">'+
            '<a href="/download/' +fname_server+ '/' +fname+ '">' +fname+ '</a>'+
          '</span>'+
//          '<img id="button_expand_image_185478186" src="/images/button-expand.gif" style="cursor: pointer">'+
//          '<a href="/paint?action=new&amp;board=int&amp;thread=35608887&amp;modify=185478186&amp;applet=shipainter" onmouseover="helpTip('Modify picture')" onmouseout="UnTip()">'+
//            '<img src="/images/button-paint.gif" border="0" width="15" height="15"></a>
          '<br>'+
          '<span class="fileinfo"> '+ fname.substr(fname.lastIndexOf('.')+1).toUpperCase()+ ', ' +post.w+ 'x' +post.h+ ', ' + fsize_str + '</span><br>'+
          '<a href="' +furl+ '" target="_blank">'+
           '<img style="display: block" id="thumbnail_' +fname_server+ '" src="' +turl+ '" width="' +post.tn_w+ '" height="' +post.tn_h+ '">'+
          '</a>'+
//          '<span id="filename_XXXXXXXXX" style="display: none; margin-top: 0px; margin-left: 0px;">iPhone-SE-vs-iPhone-6S.jpg</span>'+
        '</div>'
      return html_str;
    },
    page_json2html3: function(obj, clone, dst, force_expander, no_expander){ // copied from vichan.
      var th = document.createElement('div');
      th.setAttribute('id','thread_'+obj.no);
      th.setAttribute('style','clear:both');
      th.innerHTML = '<hr><a name=' +obj.no+ '></a>';
//      th.setAttribute('style','float: left; overflow: hidden;');
      var op = this.post_json2html((obj.posts && obj.posts[0])? obj.posts[0] : obj, true, site2[obj.domain].short_link(obj.key, obj.nof_posts, 2, '[Last ',' Posts]'), obj.no);
//      var files = op.getElementsByClassName('file_thread');
//      if (files.length>0) for (i=0;i<files.length;i++) th.appendChild(files[i]);
//      if (obj.posts && obj.posts[0] && obj.posts[0].extra_files) op.setAttribute('style','clear:both;');
      th.appendChild(op);
//      if (assign_pn) obj.posts[0].pn = th;
      obj.posts[0].pn = op;
      if (obj.posts) {
        for (var i=1;i<obj.posts.length;i++) {
//          th.appendChild(this.post_json2html(obj.posts[i], board));
          var pn = this.post_json2html(obj.posts[i]);
          this.update_posts_insert_1(this.post_container(pn,obj.posts[i].no), obj.posts[i].no, null, th);
//          var pn = this.post_container(this.post_json2html(obj.posts[i], board),obj.posts[i].no); // WHY DID I DO THIS? PROBABLY THERE IS A PROBLEM IN A SOLUTION ABOVE.
//          this.update_posts_insert_1(pn, obj.posts[i].no, null, th);
//          th.appendChild(document.createElement('br'));
          obj.posts[i].pn = pn;
        }
        if (!no_expander) this.page_json2html3_add_omitted_info(obj,obj.posts,obj.posts);
////////      } else { // WILL BE REDUNDANT
////////        var keys = Object.keys(obj); // obj.posts===undefined, but it is defined as undefined, so key contains 'posts'. 
////////        obj.posts = [];
////////        obj.posts[0] = {};
////////        for (var i=0;i<keys.length;i++) if (keys[i]!=='posts') obj.posts[0][keys[i]] = obj[keys[i]]; // remove props in prototype chain.
////////        obj.posts[0].pn = op;
      }
//      var br = document.createElement('br');
//      br.setAttribute('class','clear');
//      th.appendChild(br);
//      th.appendChild(document.createElement('hr'));
      return th;
    },
    page_json2html3_replace_expander : function(posts_old, idx, key) {
      var omit_info = posts_old[0].pn.getElementsByClassName('omittedinfo')[0];
      if (omit_info) {
//        omit_info.removeChild(omit_info.childNodes[1]);
        omit_info.appendChild(cnst.config_expander(key, idx));
      }
    },
    page_json2html3_add_omitted_info : function(th,posts_old,posts) {
      var nof_files = 0;
      for (var i=1;i<posts.length;i++) nof_files += (posts[i].type_data==='html')? posts[i].pn.getElementsByClassName('file_thread').length :
                                                    (!posts[i].filename)? 0 :
                                                    (posts[i].extra_files)? posts[i].extra_files.length+1 : 1;
      var nof_files_omitted = th.nof_files - nof_files;
      var nof_posts_omitted = th.nof_posts - posts.length;

      var omit_info = (posts_old)? posts_old[0].pn.getElementsByClassName('omittedinfo')[0] : null;
      if (th.nof_posts>1) { // for expand ALL
        if (!omit_info) {
          omit_info = document.createElement('span');
          omit_info.setAttribute('class','omittedinfo');
//          omit_info.innerHTML = 'dummy<a href="javascript:void(0)">Click to expand</a>.';
          omit_info.innerHTML = 'dummy';
          omit_info.appendChild(cnst.config_expander(th.key));
          if (posts_old) posts_old[0].pn.insertBefore(omit_info, posts_old[0].pn.getElementsByClassName('postbody')[0].nextSibling);
        }
        omit_info.childNodes[0].textContent = (nof_posts_omitted!==0)? (nof_posts_omitted +' post' + ((nof_posts_omitted!==1)? 's':'') +
          ((nof_files_omitted)? ' and ' + nof_files_omitted + ' file'+ ((nof_files_omitted===1)? '' : 's') : '') + ' are not shown. ') : '';
      } else if (omit_info) omit_info.childNodes[0].textContent = 'Showing all posts.'; //if (omit_info && posts_old) posts_old[0].pn.removeChild(omit_info);
      return omit_info;
    },
    update_posts_remove: function(th_old,i,pnode,merge){
      pnode = pnode.getElementsByClassName('thread_body')[0];
      var tgt = th_old.posts[i].pn;
      if (!merge || tgt.parentNode!==pnode) tgt = tgt.parentNode.parentNode.parentNode;
      else this.update_posts_insert_pack(tgt);
      if (tgt.previousSibling && tgt.previousSibling.tagName==='A') pnode.removeChild(tgt.previousSibling); // <a>
      pnode.removeChild(tgt);
    },
    update_posts_insert: function(src,dst,i,j,pnode){
      var ref = dst[j];
//      var ref = dst[(j<dst.length)? j : dst.length-1];
      ref = (ref)? ref.pn : null;
      var ref_pnode2 = ref && ref.parentNode && ref.parentNode.parentNode && ref.parentNode.parentNode.parentNode;
      if (ref_pnode2 && ref_pnode2.parentNode && ref_pnode2.parentNode.parentNode===pnode) ref = ref_pnode2; // remove container
//      if (j>=dst.length) ref = ref && ref.nextSibling && ref.nextSibling.nextSibling || null;
      if (!src[i].pn) src[i].pn = this.post_json2html(src[i]);
      if (i===0) this.update_posts_insert_pack(src[0].pn);
      if (j===0 && ref) this.update_posts_insert_pack(dst[0].pn);
      var tgt = (j===0 && i===0)? src[i].pn : this.post_container(src[i].pn,src[i].no); // patch for thread merging.
      this.update_posts_insert_1(tgt,src[i].no,ref,pnode);
      if (i===0 && j===0) this.update_posts_insert_unpack(src[0].pn);
    },
    update_posts_insert_1: function(tgt,no,ref,pnode){
      pnode = pnode.getElementsByClassName('thread_body')[0];
      var pn_a = document.createElement('a');
      pn_a.setAttribute('name',no);
      pnode.insertBefore(pn_a,ref);
      pnode.insertBefore(tgt, ref);
    },
    update_posts0_class: site2['vichan'].update_posts0_class,
    update_posts_insert_pack: site2['vichan'].update_posts_insert_pack,
    update_posts_insert_pack_iter: function(pn, func){
      var prev = pn.previousSibling;
      while (prev) {
        var prev_prev = prev.previousSibling; // prev is livelist.
        if (prev.classList && (prev.classList.contains(pref.cpfx+'footer') || prev.classList.contains('file_thread') || prev.classList.contains('post_header'))) func(prev);
        if (prev.classList &&  prev.classList.contains('postbody')) break;
        prev = prev_prev;
      }
    },
    update_posts_insert_unpack: function(pn){
      while (1) {
        var fchild = pn.firstChild;
        if (fchild && fchild.classList && (fchild.classList.contains(pref.cpfx+'footer') || fchild.classList.contains('file_thread') || fchild.classList.contains('post_header'))) pn.parentNode.insertBefore(fchild, pn);
        else break;
      }
    },
    post_container : function(post_pn,no) {
      var pn = document.createElement('table');
      pn.innerHTML = '<tbody><tr><td valign="top">&gt;&gt;</td></tr></tbody>';
      pn.childNodes[0].childNodes[0].appendChild(post_pn);
      return pn;
    },




    catalog_json2html3 : function(obj,board,thumb_url) {
      var th = document.createElement('article');
      th.setAttribute('class','thread teaser');
      th.setAttribute('id','thread-'+obj.no);
      var post_flag = null;
      if (obj.flags && obj.flags[0]) {
//        post_flag = obj.flags[0].cloneNode(false);
        post_flag = document.importNode(obj.flags[0],false);
        post_flag.setAttribute('class','post_country');
      }
//      th.innerHTML = '<a href="http://boards.4chan.org' + obj.board + 'thread/' + obj.no + ((obj.sub)? '/'+obj.sub.replace(/ /,'-') : '') + '">' + // cause direct jump

        th.innerHTML =
//          '<article tabindex="0" id="thread_' + obj.no + '" class="thread teaser">'+
            '<article class="thread_OP" id="' + obj.no + '">'+
              '<div class="post">'+
                '<header>'+
                  '<a><h1>'+
                    ((post_flag)? post_flag.outerHTML : '') + 
//                    '<img class="post_country" src="http://krautchan.net/images/balls/kz.png" style="cursor:pointer" name="KC/catalog/01234567">/+
                        ((obj.sub)? obj.sub : '#'+obj.no )+ 
//                        obj.sub + 
                  '</h1></a>'+
                '</header>'+
                '<div class="post_body">'+
                  '<a>'+
                    '<div class="post_files multiple">'+
                      '<figure>'+
                        '<div class="thumbnail">'+
                          '<img src="' + obj.op_img_url + '">'+
                        '</div>'+
                      '</figure>'+
                      ((obj.op_img_url2 && obj.op_img_url2[0])? (
                        '<figure>'+
                          '<div class="thumbnail">'+
                            '<img src="' + obj.op_img_url2[0] + '">'+
                          '</div>'+
                        '</figure>'+
                        ((obj.op_img_url2 && obj.op_img_url2[1])? (
                          '<figure>'+
                            '<div class="thumbnail">'+
                              '<img src="' + obj.op_img_url2[1] +'">'+
                            '</div>'+
                          '</figure>'+
                          ((obj.op_img_url2 && obj.op_img_url2[2])? (
                            '<figure>'+
                              '<div class="thumbnail">'+
                                '<img src="' + obj.op_img_url2[2] + '">'+
                              '</div>'+
                            '</figure>') :
                          '')) :
                       '')) : '') +
                    '</div>'+
                  '</a>'+
                  '<div class="omittedposts">'+
                    '<span class="omitted_text"></span>'+ // (make_footer? 'R: 0 / I: 0 / P: 0.0&emsp;/catalog/':'')+'</span>'+
                  '</div>'+
                  '<div class="post_text"><section>'+
                    obj.com +
                  '</section></div>'+
                '</div>'+
              '</div>'+
            '</article>';
//          '</article>';
      var tns = th.getElementsByClassName('thumbnail');
      tns[0].childNodes[0].addEventListener('load',site2['KC'].catalog_json2html3_onload,false);
      return th;
    },
    catalog_json2html3_onload : function() {
      this.removeEventListener('load',site2['KC'].catalog_json2html3_onload,false);
      var w = this.naturalWidth;
      var h = this.naturalHeight;
      var f = ((w>h)? w : h) / 200;
      this.setAttribute('width', w/f);
      this.setAttribute('height', h/f);
    },
    favicon : {
      __proto__: site2['DEFAULT'].favicon,
      none: '/favicon.ico',
      reply: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAKJJREFUOE9jZPh/hgECfv4yts59/PgxlAujZGVlzx6dzMDOBhMAaoCjtEA01SAuUBBZDQrnwEwsGoCCKBp+HEPwPx6QkZFB1gPifjyAUABUbGxsDLIUaAxEItQFxRIgFygIlAIqSAsEKYYDkGGhLpaWlsgaQNxQFxRrxcTEsLgbhxBI8SDUQLKnSQ9WUiOO9KRBfuL7cQzoH2C0oAGQJ5GcDQBwM6RhinByrgAAAABJRU5ErkJggg==',
      reply_to_me: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAMxJREFUOE9jZPh/hgECfv4yts59/PgxlAujZGVlzx6dzMDOBhMAavh/5j8MoKkGcdMCQYbCEYSFT8OBmagafhxD1iAjI4NsCYj78QBCA1CxsbEx0FKEDaEuKK4CcoFOAOoB2pMWCFIMAXANlpaWyBpA3FAXFGvFxMSQNWDxNJIQSDFEA5EAoQFfKGG1gQQNcP0gn4W6EPY0JFhBoQYJb8LBCo44KPp4gHDEoUQ70B5MgJ40kBMW0G2YAGviA9nz4xjQP8CQRgMgTyI5GwD2xdfU779fsgAAAABJRU5ErkJggg=='
    },
    add_backlinks: function(pn,backlinks,target,th){}, // dummy
  };
}
if (pref.features.domains['4chan'] || pref.features.domains['meguca']) {
  site2['4chan_i'] = {
//    home:undefined,
//    nickname:'4chan_i',
//    nickname_href2domain:'4chan',
    domain_url: 'i.4cdn.org',
    check_func: site2['DEFAULT'].check_func,
    proto:'4chan'
  };
  site2['4chan_s'] = {
    domain_url: 's.4cdn.org',
    check_func: site2['DEFAULT'].check_func,
    proto:'4chan'
  };
  site2['4chan'] = {
    nickname : '4chan',
    get home(){return (this.protocol||site.protocol) + '//' + this.domain_url + '/favicon.ico';}, // 4chan.org is redirected to 4channel.org, see site2['4chanR'].home
//    home : site.protocol + '//boards.4chan.org/favicon.ico',
//    home : site.protocol + '//boards.4channel.org/int/',
    protocol: 'https:', // https always since 20200330
    make_tack: function(){
      var tack = document.createElement('img');
      tack.setAttribute('src','//s.4cdn.org/image/sticky.gif');
      tack.setAttribute('class','stickyIcon retina');
      return tack;
    },
    postform_rules: null,
    postform: {
      activation: function(){
        document.getElementById('togglePostFormLink').getElementsByTagName('a')[0].click();
        this.activation = null;
      },
      __proto__:site2['DEFAULT'].postform
    },
    features : {uip_tracker: true},
    components: {
      header: '#header',
      boardlist: '.boardList',
      boardlist2: ['.boardList',1],
      postform_comment: 'textarea[name="com"]',
      postform_submit: 'input[type="submit"]',
      postform_comment2: ['textarea[name="com"]',1],
      postform_submit2: ['input[type="submit"]',2],
      topnav: '#topnav',
    },
    spoiler_text: {
      open_rule: 's,s a:not(:hover) {color: #fff!important;}',
    },
    general_event_handler:(function(){
      var obj = {
        thread: {
////          last_viewed: function(e){return e.target.classList && e.target.classList.contains('post');}, // working code, but not used.
////          get_next_mark: function(pn){
////            var parent_next = pn.parentNode && pn.parentNode.nextSibling;
////            if (parent_next.tagName==='SPAN') parent_next = parent_next.nextSibling;
////            var next_post = parent_next && parent_next.getElementsByClassName('post')[0];
////            if (!next_post) {
////              var parent_parent_next = pn.parentNode.parentNode.nextSibling;
////              next_post = parent_parent_next.getElementsByClassName('post')[0];
////            }
////            return next_post;
////          },
////          get_prev_mark: function(pn){
////            var parent_prev = pn.parentNode && pn.parentNode.previousSibling;
////            if (parent_prev.tagName==='SPAN') parent_prev = parent_prev.previousSibling;
////            var prev_post = parent_prev && parent_prev.getElementsByClassName('post')[0];
////            if (!prev_post) {
////              var parent_parent_prev = pn.parentNode.parentNode.previousSibling;
////              prev_post = parent_parent_prev.getElementsByClassName('post')[0];
////            }
////            return prev_post;
////          },
          get_mark: function(pn, clientY){
            while (pn && pn!== cataLog.parent) {
              if (pn.classList.contains('post')) return pn;
              if (pn.classList.contains('postContainer')) return pn.getElementsByClassName('post')[0];
              if (pn.parentNode && pn.parentNode.classList.contains('thread')) {
                pn = pn.previousSibling;
                if (pn) continue;
                else break;
              }
              if (pn.classList.contains('thread')) break;
              pn = pn.parentNode;
            }
            return this.get_mark_from_height(clientY);
          },
          get_mark_from_height: function(now_height){
            return this.__proto__.get_mark_from_height(now_height, document.getElementsByClassName('post'));
          },
          image_hover_check_mode: function(img){
            return img.parentNode.classList.contains('fileThumb')? 'page' : 'catalog';
          },
          getID: function(pn){return pn.classList.contains('hand') && pn.textContent;},
          getCountry: function(pn){
            return pn.classList.contains('flag') && pn.getAttribute('class').replace(/\s*flag\-*/g,'').toUpperCase() ||
              pn.classList.contains('bfl') && pn.className.replace(/\s*bfl\-*/g,'').toLowerCase();}, // lower case for bfl
//              pn.className==='countryFlag' && pn.getAttribute('alt').toLowerCase();}, // lower case for troll flag
          getTrip: function(pn){return pn.classList.contains('postertrip') && pn.textContent;},
          __proto__: site2['DEFAULT'].general_event_handler.common,
        },
      };
//      obj.catalog = { // working code
//        mouseover: function(e){
//          var et = e.target;
//          var et_tagName = et.tagName;
//          if (et_tagName==='IMG')
//            if (pref[cataLog.embed_mode].image_hover && et.parentNode.tagName==='A') cataLog.image_hover_add.call(et, e);
//        },
//        __proto__:obj.thread
//      };
      obj.catalog = Object.create(obj.thread);
      obj.page = Object.create(obj.thread);
      obj.archive = Object.create(obj.thread);
      return obj;
    })(),
    domain_url: 'boards.4chan.org',
    check_func : function(){
      var href = window.location.href;
      var domain = (href.indexOf(site2['4chanB'].domain_url)!==-1)? '4chanB' : (href.indexOf(site2['4chanR'].domain_url)!==-1)? '4chanR' : null;
      if (!domain) return false;
//      if (href.search(this.domain_url)===-1) return false;
      this.domain_url = site2[domain].domain_url;
      site.whereami = (document.title.indexOf('404 Not Found')!=-1)? '404'
                    : (href.search(/catalog/)!=-1)? 'catalog'
                    : (href.search(/thread\/[0-9]+/)!=-1)? 'thread'
                    : (href.search(/archive/)!=-1)? 'archive'
                    : (href.search(/\/$|(index|[0-9]+)(\.html)*|\/#all$/)!=-1)? 'page'
                    : 'other';
      if (site.whereami==='thread') site.no = parseInt(href.replace(/.*thread\/([0-9]+)/,'$1'),10);
      else if (site.whereami==='page') site.page = parseInt((href.match(/\/([0-9]+)\/*/)||[])[1],10) || 0;
      else if (site.whereami==='catalog' && href.indexOf('catalog#s=')!=-1) this.catalog_native_prep_wait_loop = this.catalog_native_prep_wait_loop_if_searched;
      site.config(this.domain_url,'4chan');
      this.postform_prep();
      site.max_page = site2['4chan'].max_page(site.board);
//      site.header_height = function(){
//        var header = document.getElementById('header');
//        return (header)? header.offsetHeight : 0;
//      }
      site.embed_to = (site.whereami==='thread')? {
        top:function(){return document.getElementsByClassName('navLinks')[0];},
        bottom:function(){return document.getElementsByClassName('bottomad')[0] || document.getElementById('boardNavDesktopFoot');}
//          site.embed_to['bottom'] = document.getElementsByClassName('bottomad')[0];
      } : (site.whereami==='page')? {
        top:function(){return document.getElementById('ctrl-top');},
//          site.embed_to['bottom'] = document.getElementsByClassName('board')[0].nextSibling; // fail
        bottom:function(){return document.getElementsByClassName('pagelist')[0];}
      } : (site.whereami==='catalog')? {
        top:function(){return document.getElementById('content').previousSibling;},
        bottom:function(){return document.getElementsByClassName('navLinksBottom')[0].nextSibling;}
      } : {};
      site.postform = document.getElementsByClassName('postForm')[0];
      this.styles = '.'+pref.cpfx+'catalogView{text-align:center}\n' +
        '.'+pref.cpfx+'infoPv{background:black;border-radius:3px;color:#dedede;padding:5px 8px 4px}\n' +
        (site.whereami==='catalog'? '._thumb {display: block; margin: auto; z-index: 2; box-shadow: 0 0 5px rgba(0,0,0,.25); min-height: 50px; min-width: 50px}\n' : 
          '.teaser{display:none}\n'+
          '.extended-small .teaser, .extended-large .teaser{display: block}\n'+
          '.small .thread{width: 165px}\n'+
          '.large .thread{width: 270px}\n'+
          '.extended-small .thread{width: 180px; max-height: 320px}\n'+
          '.extended-large .thread{width: 270px; max-height: 410px}\n');
      return true;
    },
//    catalog_background : '#ffffee',
    //    catalog_bordercolor : '#f0e0d6',
    get_next_image: function(img,top){
      var imgs = cataLog.parent.querySelectorAll('img[data-md5]');
      if (imgs.length===0) imgs = site.popup_body.querySelectorAll('img[data-md5]'); // for popup in catalog
      var idx = Array.prototype.indexOf.call(imgs,img);
      if (idx==-1) idx = Array.prototype.indexOf.call(imgs,img.previousSibling);
      if (top) while (imgs[idx+1] && imgs[idx+1].offsetTop<top) idx++;
      if (idx>=0) {
        if (pref.test_mode['164']) while (imgs[idx+1] && imgs[idx+1].parentNode.getAttribute('href').search(/\.webm$/)!=-1) idx++; // skip webm
        return imgs[idx+1];
      } else return null;
    },
    format_expanded_thumbnail: function(img){
      img.removeAttribute('data-md5');
      img.className = 'expanded-thumb';
    },
    catalog_threads_in_page : function(doc){return doc.getElementsByClassName('thread');},
    catalog_posts_in_thread : function(doc){return doc.getElementsByClassName('replyContainer');},
    max_page : function(){return 10;},
    protocol: 'https:',
    make_url4 : function(dbt, trim_page){ // dbt[2] is string.
      var url_prefix  = (this.protocol||site.protocol) + '//' + this.domain_url  + dbt[1];
      var url_prefix2 = (this.protocol||site.protocol) + '//' + 'a.4cdn.org' + dbt[1];
      if (dbt[3]==='catalog_html' || (trim_page && dbt[2]==0 && dbt[3]==='page_json')) dbt[3] = 'catalog_json'; // catalog_html is a skelton.
      return (dbt[3]==='page_json')?       [url_prefix2 + (parseInt(dbt[2],10)+1) + '.json', 'json', (trim_page)? '4chan'+dbt[1]+'j0' : undefined] : // dbt[2] is string.
             (dbt[3]==='catalog_json')?    [url_prefix2 + 'catalog.json', 'json', '4chan'+dbt[1]+'j0'] :
             (dbt[3]==='thread_json')?     [url_prefix2 + 'thread/' + dbt[2] + '.json', 'json'] :
             (dbt[3]==='page_html')?       [url_prefix  + ((dbt[2]!=0)? (parseInt(dbt[2],10)+1) :''), 'html'] :
             (dbt[3]==='thread_html')?     [url_prefix  + 'thread/' + dbt[2], 'html'] : null;
    },
    trim_list: 'force_init',
//////////    make_url : function(board,no){return [site.protocol+'//boards.4chan.org' + board + ((no!=0)? (no+1) :''), 'html'];},  // worling code.
//////////    make_url3: function(board,th){return  site.protocol+'//boards.4chan.org' + board + 'thread/' + th;},
////////    make_url : function(board,no,key){
////////      var url_prefix = site.protocol+'//boards.4chan.org' + board;
////////      return (key==='p')? [url_prefix + ((no!=0)? (no+1) :''), 'html'] :
////////                          [url_prefix + 'catalog.json'       , 'json']; // 4chan's html is skelton.
//////////             (key==='j')? [url_prefix + 'catalog.json'       , 'json'] :
//////////                          [url_prefix + 'catalog'            , 'html'];
////////    },
////////    make_url3: function(board,th){return site.protocol+'//boards.4chan.org' + board + 'thread/' + ((th[0]!=='t')? th : th.substr(1)+'.json');},
    url_boards_json : function(){return [site.protocol+'//a.4cdn.org/boards.json', 'json'];},
    archive_patch_domain: function(proto){
      Object.defineProperty(proto,'domain_xhr',{get:function(){return (this.url.indexOf('i.4cdn.org/')!==-1)? '4chan_i'
                                                                    : (this.url.indexOf('s.4cdn.org/')!==-1)? '4chan_s' : null;}, configurable:true, enumerable:true});
    },
    get_ops : function(doc){
      var ops = [];
      var threads = doc.getElementsByClassName('thread');
      for (var i=0;i<threads.length;i++) ops.push(threads[i].id.substring(1));
      return ops;
    },
    get_posts : function(doc) {
      var posts = [];
      var post_containers = doc.getElementsByClassName('postContainer');
      for (var i=0;i<post_containers.length;i++) posts.push(parseInt(post_containers[i].id.substring(2),10));
      return posts;
    },
////////    get_thread_link : function(pn,bn,del){
////////      var link = pn.getElementsByClassName('replylink')[0];
////////      if (link) {
//////////        if (del) link.parentNode.parentNode.removeChild(link.parentNode);
////////        if (del) {
////////          link.parentNode.removeChild(link.nextSibling);
////////          link.parentNode.removeChild(link.previousSibling);
////////          link.parentNode.removeChild(link);
////////        } else if (pref.catalog_open_in_new_tab) link.setAttribute('target','_blank');
////////        return link.getAttribute('href');
////////      } else return null;
////////    },
    modify_thread_link : function(pn){
      var link = pn.getElementsByClassName('replylink')[0];
      if (link) {
        var href = link.getAttribute('href');
        link.removeAttribute('href');
        return [[link, href]];
      } else return [];
    },
//    add_thread_link : function(doc,url){
////      <a href="thread/32599218/finland-general" class="replylink" rel="canonical">Reply</a>
//      var pn = document.createElement('a');
//      var prefix = new RegExp('https*://boards\.4chan\.org/[^/]*/');
//      pn.href = url.replace(prefix,'');
//      pn.className = 'replylink';
//      pn.rel = 'canonical';
//      pn.innerHTML = 'Reply';
//      var th = doc.getElementsByClassName('thread')[0];
//      if (th) {
//        th.insertBefore(pn,th.firstChild);
//        th.insertBefore(document.createTextNode('['),pn);
//        th.insertBefore(document.createTextNode(']'),pn.nextSibling);
//      }
//    },
    check_thread_archived : function(th){
      return (th.getElementsByClassName('archivedIcon').length!=0);
    },
//    get_time_of_posts : function(doc){
//      var posts = doc.getElementsByClassName('postContainer');
//      var last_post = posts[posts.length-1];
//      return [(parseInt(posts[posts.length-1].getElementsByClassName('dateTime')[0].getAttribute('data-utc'),10)- pref.localtime_offset*3600)*1000,
//              (parseInt(posts[0             ].getElementsByClassName('dateTime')[0].getAttribute('data-utc'),10)- pref.localtime_offset*3600)*1000];
//    },
//    get_time_of_posts : function(th){ // working code.
//      var posts = th.getElementsByClassName('postContainer');
//      return [site2['4chan'].get_time_of_post_in_utc(posts[posts.length-1]),site2['4chan'].get_time_of_post_in_utc(posts[0])];
//    },
//    get_time_of_post_in_utc : function(post){ // working code
//      return parseInt(post.getElementsByClassName('dateTime')[0].getAttribute('data-utc'),10)*1000;
//    },
//    mark_newer_posts: function(th,date,unmark, short_cut) {
//      return site2['DEFAULT'].mark_newer_posts('4chan',th.getElementsByClassName('postContainer'),date,function(post){return post.getElementsByClassName('post')[0];}, unmark, short_cut);
////      return site2['DEFAULT'].mark_newer_posts('4chan',th.getElementsByClassName('postContainer'),date,function(post){return post.getElementsByClassName('post reply')[0];}, unmark, short_cut);
//    },
    localtime: function(pn){
      var times = pn.getElementsByClassName('dateTime');
      for (var i=0;i<times.length;i++) {
        var str = site2.common.change_utc_to_local(parseInt(times[i].getAttribute('data-utc'),10)*1000);
        if (times.length>1 && i===0) times[i].firstChild.textContent = str+' '; // keep mobile timestamp in page_view
        else times[i].textContent = str;
      }
//      for (var i=0;i<times.length;i++) times[i].textContent = site2.common.change_utc_to_local(parseInt(times[i].getAttribute('data-utc'),10)*1000);
    },
    remove_files_info : function(th){
      site2.common.remove_by_classname(th,'fileText');
      site2.common.remove_by_classname(th,'mFileInfo');
      var filethumbs = th.getElementsByClassName('fileThumb');
      for (var i=0;i<filethumbs.length;i++) site2.common.move_up_and_delete_parent([filethumbs[i].childNodes[0]]);
    },
//    remove_checkboxes : function(doc){
//      var cbxs = doc.getElementsByTagName('input');
//      for (var i=cbxs.length-1;i>=0;i--) cbxs[i].outerHTML = '';
//      return doc;
//    },
    remove_posts : function(th,end){
      site2.common.remove_by_classname(th,'postContainer replyContainer',end,true);
//      site2.common.remove_double_br(th);
    },
////////    insert_footer : function(th,page_no,bn,exe,date,nof_posts,nof_files){
////////      var key = (!brwsr.ff)? 'innerText' : 'innerHTML';
////////      nof_posts += th.getElementsByClassName('postContainer').length;
////////      nof_files += th.getElementsByClassName('fileText').length;
////////      var om_info = th.getElementsByClassName('summary desktop');
////////      if (om_info[0]) {
////////        var str = om_info[0][key].replace(/\n/g,'');
////////        nof_posts += parseInt(str.replace(/\ post.*/,''),10);
////////        nof_files += parseInt('0'+str.replace(/\ image.*/,'').replace(/[^\ ]*\ /g,''),10);
////////      }
////////      if (exe) {
////////        var pn = document.createElement('div');
////////        pn.setAttribute('name','catalog_footer');
////////        pn.innerHTML = '<span>' + bn + '  ' + nof_posts + '/' + nof_files + '/' + page_no + '  </span>';
////////        var flags = th.getElementsByClassName('flag');
////////        for (var i=0;i<flags.length;i+=2) { // contains both mobile and desktop.
////////          pn.appendChild(flags[i].cloneNode(false));
//////////          pn.appendChild(document.createTextNode(' '));
////////        }
////////        var op_info = th.getElementsByClassName('postInfo desktop')[0];
////////        op_info.parentNode.insertBefore(pn,op_info);
////////      }
////////      return [nof_posts,nof_files];
////////    },
//    format_thread_layout : function(th){
//      site2.common.add_attribute_by_classname(th,'post op','style','padding:0px');
////      site2.common.add_attribute_by_classname(th,'file','style','float:left');
//      site2.common.add_attribute_by_tagname(th,'blockquote','style','margin:12px 5px');
//    },
//    format_thread_contents : function(th){
//      site2.common.remove_by_tagname(th,'input');
//      site2.common.remove_by_classname(th,'mobile');
////      site2.common.remove_by_classname(th,'postLink mobile');
////      site2.common.remove_by_classname(th,'postInfoM mobile');
//      site2.common.remove_by_classname(th,'sideArrows');
//      site2.common.remove_by_classname(th,'summary');
//    },
////////    thread2headline : function(doc){
////////      return site2.common.thread2headline(doc,'4chan');
////////    },
//    get_json_url_thread: function(board,thread){
//      return site.protocol + '//a.4cdn.org' + board +'thread/' + thread + '.json';
//    },
    get_json_url_catalog: function(board){
      return site.protocol + '//a.4cdn.org' + board +'catalog.json';
    },
    uip_check: function(callback_in, callback_sage){
//      var dbt = [site.nickname,site.board,site.no,(pref.uip_tracker.sage.detect)? 'catalog_json' : 'thread_json']; // catalog doesn't contain 'unique_ips'
      var dbt = [site.nickname,site.board,site.no,'thread_json']; // [site.protocol + '//a.4cdn.org' + site.board +'thread/' + site.no + '.json', 'json'];
      var callback = (pref.test_mode['136'] && pClg)? function(key,value,args){
        callback_in(key,value,args);
        var ths = cataLog.scan_boards_keyword_callback2(key,value, ['sage_detect',{refresh:null, __proto__:cataLog.scan_boards_keyword_callback2_default_args}]);
      } : callback_in; // callback2 merges deleted posts to show.
//      var callback = (pref.test_mode['136'] && pClg)? function(key,value,args){cataLog.scan_boards_keyword_callback2(key,value, ['sage_detect',{refresh:null, __proto__:cataLog.scan_boards_keyword_callback2_default_args}]); callback_in(key,value,args);} : callback_in;
      http_req.get('uip',dbt.join(),'',callback,false,false);
      if (pref.uip_tracker.sage.detect) {
        dbt[3] = 'catalog_json';
        http_req.get('sage_detection',dbt.join(),'',this.sage_detect,false,false, callback_sage);
      }
    },
    sage_detect: function(key,value, callback){
      var dbt = key.split(',');
      var ths = pref.test_mode['136'] && pClg? cataLog.scan_boards_keyword_callback2(key,value, ['sage_detect',{refresh:null, __proto__:cataLog.scan_boards_keyword_callback2_default_args}])
              : site2['4chan'].wrap_to_parse.get(value.response, dbt[0], dbt[1], dbt[3], {page:0});
      var obj = site2['4chan'].parse_funcs['catalog_json'].add_sage(ths, site.no) || ths.filter(function(th){return th.no===site.no;})[0];
      callback(obj, value.response[0].threads.length);
    },
    sage_detect_all: function(ths){
      this.parse_funcs['catalog_json'].add_sage(ths, null);
      for (var i=0;i<ths.length;i++) {
        var posts = ths[i].posts;
        var lth = liveTag.mems.init(ths[i]); // ths[i].lth;
        for (var j=0;j<posts.length;j++) {
          if (posts[j].email==='sage') {
            if (!lth.sg) lth.sg = new Set();
            lth.sg.add(posts[j].no);
          }
        }
        if (pref.catalog.page_his) { // qeuivalent to show_page in uip_tracker
          if (!lth.pgh) lth.pgh = {};
          var pgh = lth.pgh;
          var no = ths[i].posts[ths[i].posts.length-1].no;
          if (!pgh[no]) pgh[no] = [i];
          else this.page_his_update(pgh[no],i);
        }
      }
    },
    page_his_update: function(pgh,now){
      var last = pgh[pgh.length-1];
      if (last!=now) pgh[pgh.length - (pgh.length<=1? 0 : last<now && pgh[pgh.length-2]<last? 1:0)] = now;
//      if (last!=now) pgh[pgh.length - (pgh.length>=3 && pgh.length%2===1? 0 : pgh.length>1 && last<now? 1:0)] = now;
      return last!=now;
    },
    parse_json_thread: function(obj,from_http){
      if (from_http) {
//        var obj = JSON.parse(txt);
        obj.posts[obj.posts.length-1]['unique_ips'] = obj.posts[0]['unique_ips'];
        return obj;
      } else {
        var obj = {posts: []};
        var uids = {};
        var nof_uids = 0;
        var posts = document.getElementsByClassName('postContainer');
        for (var i=0;i<posts.length;i++) {
          obj.posts[i] = {};
          obj.posts[i].no = posts[i].id.replace(/pc/,'');
          var id = posts[i].getElementsByClassName('posteruid')[0];
          if (id) {
            id = id.textContent;
            obj.posts[i].id = id;
            if (uids[id]===undefined) {
              nof_uids++;
              uids[id] = 1;
            }
          }
          obj.posts[i].unique_ips = nof_uids;
        }
//        obj.posts[posts.length-1].unique_ips = document.getElementById('unique-ips').textContent; // not refreshed.
        obj.posts[posts.length-1].unique_ips = 0;
        return obj;
      }
    },
    uip_tgt_post : function(no){
      return document.getElementById('p'+no);
//      return document.getElementById('pc'+no);
    },
    uip_post_num : function(tgt_post){
      return tgt_post.getElementsByClassName('postNum');
    },
    prep_own_posts_reg: /^4chan\-track\-[0-9A-z]+\-[0-9]+$/,
    prep_own_posts_event : function(e){
      if (e) if (site2['4chan'].prep_own_posts_1(e.key)) e = null;
      if (window.name==='4chan' && !e) send_message('parent',['OWN_POSTS', window.name, site3[window.name].own_posts]);
    },
    prep_own_posts : function(bt){
      site3[this.nickname].own_posts = {};
      if (localStorage) {
        var keys = (bt)? [('4chan-track'+bt).replace(/\//g,'-')] :
                         Object.keys(localStorage || '{}');
        for (var i=0;i<keys.length;i++) this.prep_own_posts_1(keys[i]);
      }
//console.log(site3[this.nickname].own_posts);
    },
    prep_own_posts_1 : function(key){
      if (this.prep_own_posts_reg.test(key)) {
        var board = '/' + key.replace(/^4chan\-track\-/,'').replace(/\-[0-9]+$/,'') + '/';
        if (!site3[this.nickname].own_posts[board]) site3[this.nickname].own_posts[board] = {};
        var nos = JSON.parse(localStorage[key] || '{}');
        for (var j in nos) site3[this.nickname].own_posts[board][j.substr(2)] = null;
        return true;
      }
      return false;
    },
    catalog_native_prep_wait_loop: null,
    catalog_native_prep_wait_loop_if_searched: function(whereami,invoke_func){
      var qf_ctrl = document.getElementById('qf-ctrl');
      var qf_clear = document.getElementById('qf-clear');
      var func = function(){
        qf_ctrl.removeEventListener('click',func,false);
        qf_clear.removeEventListener('click',func,false);
        setTimeout(invoke_func,100);
      };
      qf_ctrl.addEventListener('click',func,false);
      qf_clear.addEventListener('click',func,false);
    },
    catalog_native_prep_watcher: true,
    catalog_native_prep: function(pn_filter,pn_tb,pn_hi, clg){
      var node_ref = (clg.mode==='catalog')? document.getElementById('ctrl') :
                                             document.getElementById('ctrl-top');
      if (clg.mode!=='float') {
        if (clg.mode==='thread' || !node_ref) { // !node_ref for 4chan-X v1.13.8.7
          var node_ref = document.getElementsByClassName('navLinks');
          for (var i=0;i<node_ref.length;i++)
            if (window.getComputedStyle(node_ref[i]).display!=='none') {node_ref = node_ref[i]; break;}
        }
//      pn_tb.setAttribute('style', 'float:right');
        site2['DEFAULT'].catalog_native_prep_sel(pn_filter,pn_tb,clg.mode==='catalog'? document.getElementById('order-ctrl') : null);
      }
      var sels = pn_filter.getElementsByTagName('SELECT');
      var pn_size = sels['INST.catalog.thumb.size'];
      if (clg.mode==='catalog') cnst.swapAdd(pn_size, document.getElementById('size-ctrl'));
      var pn_teaser = sels['INST.catalog.op'];
      pn_teaser.innerHTML = '<option value="off">Off</option><option value="on">On</option>';
      if (clg.mode==='catalog') cnst.swapAdd(pn_teaser, document.getElementById('teaser-ctrl'));
//      var ref_size = clg.components.native_size;
//      var pn_size = (clg.mode==='catalog')? cnst.cloneSwap(document.getElementById('size-ctrl'))
//        : cnst.doms_insertBefore(null, '<br>&emsp;<select size="1"> <option value="small">Small</option> <option value="large">Large</option> </select>', ref_size);
//      pn_size.add(cnst.dom('<option>custom</option>'));
//      if (clg.mode==='float') pn_size.selectedIndex = pref.float.catalog_size;
//      clg.pref[clg.mode].format.thumb.size = pn_size.value;
//      var pn_teaser = (clg.mode==='catalog')? cnst.cloneSwap(document.getElementById('teaser-ctrl'))
//        : ref_size.parentNode.insertBefore(cnst.dom('<select size="1"> <option value="off">Off</option> <option value="on">On</option> </select>'), ref_size);
//      if (clg.mode==='float') pn_teaser.selectedIndex = pref.float.catalog_teaser;
if (this.nickname==='4chan') {
      var size_changed = size_changed_factory(clg, pn_size, pn_teaser);
      pref_func.settings.html_funcs.design_of_fc_size_doms_register(pn_size, pn_teaser);
      pn_size.addEventListener('change', size_changed, false);
      pn_teaser.addEventListener('change', size_changed, false);
}
//        pn_size.addEventListener('change', site2['4chan'].catalog_native_size_changed, false);

      if (clg.mode==='catalog') {
if (this.nickname==='4chan') {
          clg.kwd_init(document.getElementById('qf-box'), pn_filter, this.catalog_native_prep_wait_loop).id = null;
          document.getElementById('qf-ctrl').addEventListener('click',clg.kwd_clear,false);
          document.getElementById('qf-clear').addEventListener('click',clg.kwd_clear,false);
}

//        var pn_tb_new = document.createElement('span');
//        while (pn_tb.firstChild) pn_tb_new.appendChild(pn_tb.firstChild);
//        pn_tb = pn_tb_new;
//        pn_tb.appendChild(pn_tb.removeChild(pn_tb.childNodes[3]).firstChild);
        var settings = document.getElementById('settings');
        pn_tb.childNodes[3].style.float = 'left';
        pn_tb.style.float = 'right';
        node_ref.appendChild(pn_filter);
        node_ref.insertBefore(pn_tb.childNodes[3],settings);
        node_ref.insertBefore(pn_tb,settings);
      } else if (clg.mode==='page') {
        pn_tb.childNodes[3].style.display = 'inline';
        pn_tb.style.float = 'right';
        node_ref.appendChild(pn_tb.childNodes[3]);
        node_ref.appendChild(pn_tb);
        node_ref.appendChild(pn_filter);
//        var pctrls = document.getElementsByClassName('board')[0]; // test patch
//        for (var i=pctrls.childNodes.length-1;i>=0;i--) if (pctrls.childNodes[i].tagName==='HR') pctrls.removeChild(pctrls.childNodes[i]);
//        pctrls.parentNode.insertBefore(document.createElement('hr'),pctrls.nextSibling);
      } else if (clg.mode==='thread') {
//      node_ref.parentNode.insertBefore(pn_tb,node_ref);
        node_ref.appendChild(pn_tb);
        node_ref.appendChild(pn_filter);
        document.getElementsByClassName('navLinksBot')[0].appendChild(pn_hi);
      }
//      node_ref.parentNode.insertBefore(pn_filter,node_ref);
////////      return site2['4chan'].catalog_from_native(date,document,site.board,clg.mode+'_html');
      if (site.whereami==='archive') {
        styleSheet.register_cc('threads','padding:20px 0;text-align:center;');
        styleSheet.register_cc('threads>.thread','width:180px;max-height:320px;vertical-align:top;display:inline-block;word-wrap:break-word;overflow:hidden;margin-top:5px;margin-bottom:20px;padding:5px 0 3px;position:relative;');
      }
      clg.__proto__.dic_view_class = {catalog:'small', catalog1:'large', catalog01:'extended-small', catalog11:'extended-large'};
//      var teasers = ['','extended-'];
//      var sizes = ['small','large','custom'];
      function size_changed_factory(clg, pn_size, pn_teaser){
        return function size_changed(e){
//          clg.ppn.setAttribute('class',clg.ppn.getAttribute('class').replace(/(extended\-)*(small|large|custom)/g,'').replace(/\s\s*/,' ').replace(/^\s|\s$/,''));
//          if (clg.view==='catalog') clg.ppn.classList.add(teasers[pn_teaser.selectedIndex]+sizes[pn_size.selectedIndex]);
          if (!e) return;
//          if (e.target===pn_size) clg.image_resize_all(e.target.value);
          if (clg.mode==='catalog') if (localStorage) localStorage['catalog-settings'] = JSON.stringify({extended:pn_teaser.selectedIndex==1, large:pn_size.selectedIndex==1, orderby:'alt'});
          e.stopPropagation();
        };
      }
      clg.native.viewChanged = size_changed;
    },
////////    catalog_from_native : function(date,doc,board,type) { // working code.
////////      return this.wrap_to_parse.get(doc, this.nickname, board, type);
////////    },
//    catalog_from_native : function(date,doc,board,type) { // working code.
//      var parse_obj = {domain:'4chan', board:board, parse_funcs:site2['4chan'].parse_funcs[type], __proto__:site4.parse_funcs_on_demand};
//      var ths = {pn:doc, __proto__:parse_obj};
//      return ths.ths;
//    },
    catalog_get_native_area: function(){
      if (site.whereami==='archive') {
        var arc = document.getElementById('arc-list');
        var div = document.createElement('div');
        div.setAttribute('class',pref.cpfx+'threads');
        arc.parentNode.insertBefore(div,arc);
        var btn = document.createElement('button');
        btn.textContent= 'More threads';
        arc.parentNode.insertBefore(btn,arc);
        btn.onclick = function(){
          cataLog.insert_myself(null,null,true);}; // argument init is a patch for patch, should be removed.
        return div;
      }
      return (site.whereami==='catalog')? document.getElementById('threads') : document.getElementsByClassName('board')[0];
    },
//    catalog_native_size: (document.getElementById('size-ctrl'))? document.getElementById('size-ctrl').value : 'small',
//    catalog_native_size_changed: function(e){ // working code
//      pref.catalog.format.thumb.size = e.target.value;
////      site2['4chan'].catalog_native_size = e.target.value;
////      document.getElementById('threads').innerHTML = ''; // 4chan rewrites innerHTML, this can be used also.
//      var ths = document.getElementsByClassName('thread');
//      for (var i=ths.length-1;i>=0;i--) if (!gGEH.pns_all_keys.has(ths[i])) ths[i].parentNode.removeChild(ths[i]);
//      pClg.image_resize_all(e.target.value);
//      pClg.drawn_idx = 0;
//      pClg.show_catalog();
////      var threads = catalog_obj.catalog_func().get_threads();
////      for (var name in threads) if (threads[name][0]) site2['4chan'].catalog_json2html3_size_changed(threads[name][0].getElementsByTagName('img')[0]);
////      catalog_obj.catalog_func().show_catalog();
//    },
    catalog_float_prep: function(clg){
      if (site.whereami==='catalog') clg.ppn.appendChild(cnst.dom('<div style="clear:both"></div>'));
    },
    parse_funcs : { // 4chan
      'catalog_html': {
        ths_hook: function(callback){ // https://stackoverflow.com/questions/34235401/greasemonkey-sharing-data-between-two-scripts-running-in-same-tab
          var pn = document.createElement('script');
          pn.innerHTML = 'var clg; try{clg = JSON.stringify(catalog);} catch (e){clg = null;} document.body.lastChild.textContent = clg;';
          var ob = new MutationObserver(function(e){
            document.body.removeChild(pn);
            ob.disconnect();
            try {
              site.data_embedded = JSON.parse(pn.textContent);
            } catch (e) {}
            callback();
          });
          ob.observe(pn, {childList:true});
          document.body.appendChild(pn);
        },
        ths: function(doc){
          var pns = doc.pn.getElementsByClassName('thread');
          var obj = site.data_embedded;
          if (!obj) return this.ths_array(doc, pns);
          var ths= [];
          var proto = {
            get filename(){return this.file.slice(0,this.file.indexOf('.'));},
            get spoiler(){return this.imgspoiler;},
            get tim(){return this.imgurl;},
            get ext(){return this.file.slice(this.file.lastIndexOf('.'));},
            get name(){return this.author;},
            get com(){return Object.defineProperty(this,'com',{value:site2['4chan'].link_sanitizedTxt2html(this.teaser, this), writable:true, configurable:true, enumerable:true}).com;},
            parse_funcs: {type_com:'html', __proto__:this},
//            get com(){return this.teaser;},
            missing_info:undefined,
            _links: null, // for block prototype chain.
            __proto__:doc.__proto__
          };
          if (!pref.test_mode['146']) {
            var rx_anchor = /<a href="(\/[^\/]+\/)thread\/(\d+)#p(\d+)" class="quotelink">&gt;&gt;(\d+)<\/a>/g;
            var kill_if_pruned = function(m,board,no_th,no_post,no_caption){
              return board!==doc.board || (no_th in obj.threads)? m : '<span class="deadlink">&gt;&gt'+no_caption+'</span>';
            };
          }
          for (var i in obj.threads) {
            var th = obj.threads[i];
            if (th.b>pns.length) continue; // for safe
            th.no = parseInt(i,10); // patch for 4chan's bug. why do they use string for no?
            th.page = Math.floor(th.b/(obj.pagesize || 15))+'.'+th.b%(obj.pagesize || 15);
            th.nof_posts = th.r+1;
            th.nof_files = th.i+1;
            th.time_created = th.date * 1000;
            th.time_posted = (th.lr && th.lr.date || th.date) * 1000;
            th.time_bumped = th.time_posted;
            th.time = th.date;
            th.name = th.author || obj.anon;
            if (th.lr && th.lr.id!=i) {
              th.last_name = th.lr.author || obj.anon;
              th.last_no   = th.lr.id;
            }
            if (pref.test_mode['159']) {
              th.last_modified = th.lr && th.lr.date || th.date; // for add_sage
              var post_1 = (th.lr && th.lr.id!=i)? {name:th.lr.author || obj.anon, no:th.lr.id, time:th.lr.date} : null;
            }
//            th.pn = pns[th.b]; // copy for extracting params in remake_html_prep at merging/unmerging // doesn't work, pn was destroyed already at extraction
            th.__proto__ = proto;
            if (!pref.test_mode['146']) { // linkfy >>xxxx in OP
              var ts = pns[th.b].getElementsByClassName('teaser')[0];
              var html_new = (th.sub? '<b>'+th.sub+'</b>':'')+(th.sub&&th.com?': ':'')+th.com;
              if (html_new!==ts.innerHTML.replace(/\'/g,'&#039;')) ts.innerHTML = html_new.replace(rx_anchor, kill_if_pruned);
            }
            ths[th.b] = {
              pn: pns[th.b],
              posts: post_1? [th, post_1] : [th], __proto__:th};
          }
//            var lbd = liveTag.mems['4chan'][site.board]; // ths is captured here.
//            for (var i=0;i<ths.length;i++) {
//              var th = obj.threads[ths[i].no]; // obj.threads[x].b means i
//              if (th) {
//                th.no = ths[i].no;
////                th.page = Math.floor(i/(obj.pagesize || 15))+'.'+i%(obj.pagesize || 15);
//                th.pn = ths[i].pn;
//                th.__proto__ = proto;
//                lbd[ths[i].no].th = th;
//                lbd[ths[i].no].ta.posts= [th];
//              }
//            }
          if (pref.test_mode['159']) {
            site2['4chan'].parse_funcs['catalog_json'].add_sage(ths);
            for (var i=0;i<ths.length;i++) ths[i].posts.splice(1,1);
          }
          return ths;
        },
//        ths: 'thread_html',
//        th_init: function(th) {
//          th.pn.getElementsByTagName('a')[0].addEventListener('click',th.parse_funcs.preventDefault,false);
//        },
//        th_destroy: function(pn, parse_funcs){
//          pn.getElementsByTagName('a')[0].removeEventListener('click',parse_funcs.preventDefault,false);
//        },
//        ths: function(doc) {
//          var ths = this.ths_array(doc,doc.pn.getElementsByClassName('thread'));
//          for (var i=0;i<ths.length;i++)
//            ths[i].pn.getElementsByTagName('a')[0].removeAttribute('href');
//          return ths;
//        },
        no : function(th){return parseInt(th.pn.getAttribute('id').substr(7),10);},
        time_bumped: function(th){return 0;},
        time_created : function(th){return 0;},
        nof_posts: function(th){
          var tmp = th.footer.textContent.match(/[0-9]+/g);
          var nof_files = tmp && tmp[1] && parseInt(tmp[1],10)+1 || 1;
          Object.defineProperty(th, 'nof_files', {value:nof_files, enumerable:true, writable:true, configurable:true});
          return tmp && parseInt(tmp[0],10)+1 || 1;
        },
        nof_files: function(th){
          var tmp = th.footer.textContent.match(/[0-9]+/g);
          var nof_posts = tmp && parseInt(tmp[0],10)+1 || 1;
          Object.defineProperty(th, 'nof_posts', {value:nof_posts, enumerable:true, writable:true, configurable:true});
          return tmp && tmp[1] && parseInt(tmp[1],10)+1 || 1;
        },
        sub: function(th){
          var ts = th.pn.getElementsByClassName('teaser')[0];
          var sub = (ts)? ts.getElementsByTagName('b') : null;
          var com = (!ts)? '' : (sub && sub[0])? ((ts.childNodes[1])? ts.childNodes[1].textContent : '') : ts.childNodes[0].textContent;
          Object.defineProperty(th, 'com', {value:com, enumerable:true, writable:true, configurable:true});
          return (sub && sub[0])? sub[0].textContent : '';
        },
        name: function(th){return '';},
        com: function(th){
          if (!th.hasOwnProperty('sub')) Object.defineProperty(th, 'sub', {value:this['sub'](th), enumerable:true, writable:true, configurable:true});
          return th.com;
        },
        footer: function(th){
          return th.pn.getElementsByClassName('meta')[0]; // must parse pn because of mimic mode.
//          footer.innerHTML = '';
        },
        sticky: function(th){return th.pn.getElementsByClassName('stickyIcon')[0]!==undefined;},
        tn_as: function(th){return th.pn.getElementsByTagName('a');},
//        tn_imgs: function(th){ // doesn't work with merge
//          var a0 = th.pn.getElementsByTagName('a')[0];
//          return (a0)? [a0.getElementsByTagName('img')[0]] : [];}, // for chart popup, not using tn_as
//        tn_imgs: function(th){return (th.tn_as[0])? [th.tn_as[0].getElementsByTagName('img')[0]] : [];},
//        class_thread: 'thread',
//        class_thumbnail: 'thumb',
//        op_img_url: function(th){ // working code.
//          var img = th.pn.getElementsByTagName('img')[0];
//          return (img)? img.getAttribute('src') : undefined; // patch.
//        },
        op_img_url: function(th){return site2['4chan'].catalog_json2html3_thumbnail(th,th.board) || th.tn_imgs[0] && th.tn_imgs[0].getAttribute('src') || undefined;}, // patch. // BUG IN MIMIC MODE, BECAUSE THIS REQURES TH.PN AND TH.PN WILL BE WRITTEN AFTERWARDS. // moved from DEFAULT.common
        get_op_src: 'thread_json',
//        get_op_src: function(th){ // working code.
//          return (th.op_img_url.indexOf('s.4cdn.org')!=-1)? th.op_img_url : th.op_img_url.replace(/s(\.\w+)$/,'$1');}, // TEMPORAL PATCH
//        dynamic_image_hover: true,
        missing_info: 1,
        img2src: function(img){
          var dbt = site2['4chan'].popups_href2dbtp(img.parentNode.getAttribute('href'));
          var lth = liveTag.mems.getFromName(dbt[0]+dbt[1]+dbt[2]);
          return (!lth)? null
            : (lth.th && lth.th.ext)? (!lth.th.spoiler? img.src.replace(/thumb/,'src').replace(/s\.[^\.]*$/,lth.th.ext) : '//i.4cdn.org'+lth.th.board+lth.th.tim+lth.th.ext)
            : (lth.th.missing_info && cataLog.DIH.fetch_and_reentry([lth.domain+lth.board], img), img.src);
        },
        time_unit: 1000,
//        posts: function(th){
//          return (pref.test_mode['126'])? [{sub:th.sub, com:th.com, name:th.name, pn:undefined, __proto__:th.__proto__}] : [{sub:th.sub, com:th.com, name:th.name, get pn(){console.trace();return this._pn;}, _pn:th.pn}];}, // for debug
        footer_prep: function(th, footer){
          var pn = document.createElement('span');
          var menu = footer.lastChild;
          if (menu) footer.removeChild(menu); // remove native menu, which introduces inconsistencies.
          footer.insertBefore(pn, footer.firstChild);
//          footer.removeChild(footer.lastChild); // remove menu
          var pn_next;
          while (pn_next = pn.nextSibling) pn.appendChild(pn_next);
          return pn;
        },
      },
      'catalog_json' : {
        ths: function(obj, parse_obj) {
          var ths = [];
          for (var i=0;i<obj.length;i++)
            if (obj[i].threads) for (var j=0;j<obj[i].threads.length;j++) {
              var tgt = obj[i].threads[j];
              tgt.__proto__ = parse_obj;
              ths[ths.length] = {
                page: i + '.' + j,
                key: parse_obj.domain + parse_obj.board + tgt.no,
                __proto__: tgt,
              }
            }
//          this.add_sage(ths);
          return ths;
        },
//        ths: 'DEFAULT.catalog_json',
        time_posted: function(th){return (th.last_replies)? th.last_replies[th.last_replies.length-1].time*1000 : th.time_created;},
        time_bumped: function(th){return (th.bumplimit)? undefined : th.time_posted;},
        time_created : function(th){return th.time*1000;},
        nof_posts: function(th){return th.replies+1;}, // same as 8chan
        nof_files: function(th){return th.images+1;},
////        key: function(th){return th.domain + th.board + th.no;}, // same as 8chan
////        sub: function(th){return (th.hasOwnProperty('sub'))? th.sub : '';},
////        name: function(th){return (th.hasOwnProperty('name'))? th.name : '';},
////        com: function(th){return (th.hasOwnProperty('com'))? th.com : '';},
        op_img_url: 'DEFAULT.common',
////        op_img_url: function(th) {
////          return site2['4chan'].catalog_json2html3_thumbnail(th, th.board);},
        //        footer: function(th){return th.pn.getElementsByClassName('meta')[0];},
        posts: function(th){
          return (th.last_replies)? [th.__proto__].concat(th.last_replies) : [th.__proto__];
        },
////        posts: function(th){
////          var posts = site2['DEFAULT'].parse_funcs.catalog_json.posts(th);
////          if (th.last_replies) posts = posts.concat(th.last_replies);
////          return posts;
////        },
//////        posts: function(th){return th.last_replies;}, // can't show icon in desktop notification. // BUG, don't hit at search.
//////        posts: function(th){ // work, but parse redundantly
//////          if (th.last_replies) for (var i=0;i<th.last_replies.length;i++)
//////            th.last_replies[i].op_img_url = site2['4chan'].catalog_json2html3_thumbnail(th.last_replies[i], th.board);
//////          return th.last_replies;
//////        },
        has_posts: true,
////        last_replies: function(th){return undefined;}, // stop parsing loop between 'posts' and 'last_replies'
//        add_op_img_url: function(th){  // slow in chrome because of making needless prefetch.
//          for (var i=0;i<th.posts.length;i++)
//            th.posts[i].op_img_url = site2['4chan'].catalog_json2html3_thumbnail(th.posts[i], th.board);
//        },
//        add_op_img_url: function(posts,board){
//          for (var i=0;i<posts.length;i++)
//            posts[i].op_img_url = site2['4chan'].catalog_json2html3_thumbnail(posts[i], board);
//        },
        add_op_img_url: site2['DEFAULT'].parse_parts.add_op_img_url,
        time_unit: 1000,
        get_op_src: 'thread_json',
        type_com: 'html',
        posts_full: null,
////        posts_full: 'DEFAULT.catalog_json',
        missing_info: undefined,
        tn_as: 'catalog_html',
//        tn_imgs: 'catalog_html',
        footer: 'catalog_html',
//        class_thread: 'catalog_html',
//        class_thumbnail: 'catalog_html',
        add_sage: (function(){
          var bumps = {};
          var pages = {};
//          function i2pkey(i,skey,post){
//            return 'p'+Math.floor(i/15)+'.'+(i%15)+', '+skey+'#'+post.no;
//          }
          function log_post(th,j){
            var skey = (th.domain!==site.nickname? th.domain : '') + (th.board!==site.board? th.board : '') + th.no;
            return 'p'+th.page+', '+skey+'#'+th.posts[j].no+', '+th.posts[j].time;
          }
//          function debug_out(ths, i, cap, old){
//            var hist = (old)? history_latest_bump_old : history_latest_bump;
//            ths.slice(i>=5?i-5:0,i+5).map(function(th,i){console.log(cap, th.no, hist[i], new Date(hist[i]*1000).toLocaleString(), th.last_modified, new Date(th.last_modified*1000).toLocaleString(), th.page, th.bumplimit);});
//          }
//          var history_latest_bump = [];
//          var history_latest_bump_old;
//          var ths_old;
          return function(ths, end_no){
            var pf = pref.uip_tracker.sage;
            var i=0;
            while (i<ths.length && ths[i].sticky) if (ths[i].no===end_no) return ths[i]; else i++;
            if (pf.patch_bug3) { // ignore not updated data
              var skeyb = (ths[0].domain!==site.nickname? ths[0].domain : '') + ths[0].board;
              var latest_bump = bumps[skeyb] || 0;
              while (i<ths.length && ths[i].last_modified<latest_bump) if (ths[i].no===end_no) return ths[i]; else i++;
              if (i<ths.length) bumps[skeyb] = ths[i].last_modified;
              var j=i;
              while (--j>=0) Object.defineProperty(ths[j],'time_bumped',{value:ths[i].last_modified*tu, enumerable:true, configurable:true, writable:true});
            }
            if (i==ths.length) return;
            if (ths[i].no==end_no) return ths[i];
            latest_bump = ths[i].last_modified; // for deletion of the last post, instead of ths[i].posts[ths[i].posts.length-1].time;
            var skey0 = (ths[0].domain!==site.nickname? ths[0].domain : '') + (ths[0].board!==site.board? ths[0].board : '');
            var tu = this.time_unit;
            var idxs_aged = [];
            var bad_data = false;
            while (++i<ths.length) { // can't use stateful fast approach because of 4chan's bug.
              var th = ths[i];
//              if (pref.debug_mode['30']) history_latest_bump[i] = latest_bump;
              var skey = skey0? skey0 + th.no : th.no; // retain numeric type.
              if (!th.bumplimit) {
                for (var j=th.posts.length-1;j>=1;j--) { // annotate all
                  var time = th.posts[j].time;
                  if (bumps[skey]===time && i<pages[skey]) {
                    if (pref.debug_mode['30']) console.log('Bumped without any posts: p'+i+'<-'+pages[skey]+', '+log_post(th,j)+', '+(i<ths.length-2? ths[i+1].posts[ths[i+1].posts.length-1].time : '')+', '+latest_bump);
                    bad_data = true;
                    break;
                  }
                  if (bumps[skey]>=time) break; // for deletion and reentry
                  if (latest_bump+pf.tolerance>=time) break; // bug of 4chan at roundup, 1 tolerance is required.
  //                if (latest_bump>=time) break;
                  if (pf.patch_bug4) {
                    if (i<pages[skey] && (j>=2 || th.nof_posts===1) && th.posts[j-1].time<=bumps[skey]) { // In case of that all new posts are saged but page order is younger than before.
                      var sure = (j===th.posts.length-1) && j-1>=0 && th.posts[j-1].time===bumps[skey] && !(bumps[skey]+pf.tolerance>=latest_bump); // 100% sure if there is only one new post and its previous post is newer than 'latest_bump'
                      if (pref.debug_mode['30']) console.log('RECOVERED by page history: '+log_post(th,j)+', '+(sure?'sure':'maybe')+', p'+i+'<-'+pages[skey]+', '+time+'<-'+bumps[skey]+', '+latest_bump);
                      latest_bump = time;
                      var r = i;
                      while (--r>=0) {
                        var k = idxs_aged[r] || ths[r].posts.length-1; // refers aged last post
                        var time_recovered = ths[r].posts[k].time;
                        if (time_recovered+pf.tolerance>=latest_bump) break;
                        while (++k<ths[r].posts.length) {
                          idxs_aged[r] = k; // idxs_aged must refers existing post
                          ths[r].posts[k].email = '';
                          if (pref.debug_mode['30']) console.log('Recovered saged posts: '+log_post(ths[r],k)+', '+latest_bump);
                          time_recovered = ths[r].posts[k].time;
                          if (time_recovered+pf.tolerance>=latest_bump) {
                            latest_bump = (time_recovered>latest_bump)? time_recovered : latest_bump;
                            Object.defineProperty(ths[r],'time_bumped',{value:latest_bump*tu, enumerable:true, configurable:true, writable:true});
                            break;
                          }
                        }
                        if (k===ths[r].posts.length) if (pref.debug_mode['30']) console.log('There should be updated data after: '+log_post(ths[r],k-1)+', '+latest_bump);
                      }
//                      if (pref.debug_mode['30']) {debug_out(ths,i,'new',false);debug_out(ths_old,i,'old',true);}
                      bad_data = true;
                      break;
                    }
                  }
                  th.posts[j].email = 'sage'; // vichan has 'email' field.
                  if (pref.debug_mode['30']) console.log('detect: sage: '+log_post(th,j)+', '+time+', '+latest_bump);
                }
                idxs_aged[i] = j;
                if (j===0) time = (th.nof_posts===th.posts.length)? th.posts[0].time : latest_bump -1; //all posts are saged or new thread without any posts. // assuming -1 bump time
//                if (th.nof_posts===1 || th.posts[th.posts.length-1].time!=th.last_modified) time = th.last_modified; // for deletion of the last post, assumes last post was age. th.posts.length===1 for new threads which has no posts.
  //              if (time<th.last_modified) time = th.last_modified; // for deletion of the last post.
                var bumped = bumps[skey]!=time;
                if ((bumps[skey]||0)<time) bumps[skey] = time; // catalog doesn't have all posts.
                if (bumped) latest_bump = (pf.patch_bug2 && i<pf.patch_bug2nth)? th.last_modified : bumps[skey]; // for fast board like /v/.
//                Object.defineProperty(th,'time_bumped',{value:latest_bump*tu, enumerable:true, configurable:true, writable:true});
              } else latest_bump--; // assuming -1 bump time for bumplimited threads
              Object.defineProperty(th,'time_bumped',{value:latest_bump*tu, enumerable:true, configurable:true, writable:true});
              pages[skey] = i;
//              var obsolete = !bumped && i<pages[skey]; // )? pages[skey] : 0;
//              pages[skey] = (obsolete)? pages[skey] : i; // lock to pages[skey], 10000 is too strict for thread deletion.
//              pages[skey] = (obsolete)? 10000 : i; // lock to 10000 as a flag, since threads are bumped without new posts in 4chan.(bug)
              if (th.no==end_no) return th;
              if (pf.patch_bug5 && bad_data) {
//              if (pf.patch_bug5 && (recovered!==estimated || obsolete)) {
                if (i<ths.length-2 && time+pf.tolerance<ths[i+1].posts[ths[i+1].posts.length-1].time) {
                  if (pref.debug_mode['30']) console.log('Vailed out by bad data: p'+i);
//                  if (pref.debug_mode['30']) {debug_out(ths,i,'new',false);debug_out(ths_old,pages[skey],'old',true);}
                  break;
                }
              }
            }
//            if (pref.debug_mode['30']) {ths_old = ths; history_latest_bump_old = history_latest_bump; history_latest_bump = [];};
          };
        })(),
        flags: 'DEFAULT.page_json',
        hrefs: 'DEFAULT.post_json',
        proto: 'DEFAULT.common',
//        proto: 'DEFAULT.catalog_json',
      },
      'page_html' : {
        footer2: undefined,
        ths: function(doc) {
          var ths = this.ths_array(doc, doc.pn.getElementsByClassName('thread'));
          if (pref.test_mode['159']) {
            for (var i=0;i<ths.length;i++) {
              site2['4chan'].wrap_to_parse.posts(ths[i]);
              ths[i].last_modified = ths[i].posts[ths[i].posts.length-1].time;
            }
            site2['4chan'].parse_funcs['catalog_json'].add_sage(ths);
          }
          return ths;
        },
        proto: 'thread_html',
      },
      'thread_html' : {
        time_created: 'DEFAULT.thread_json',
        time_bumped: 'DEFAULT.thread_json',
        time_posted: 'DEFAULT.thread_json',
        posts: function(th){return this.posts_array(th, th.pn.querySelectorAll(':scope>.postContainer>.post'));}, // must be able to be used in live thread which may have poopups.
        ths: function(doc) {return this.ths_array(doc, doc.pn.getElementsByClassName('thread'));},
//        ths: function(doc) {return site2['DEFAULT'].parse_funcs['thread_html'].ths_array(doc, doc.pn.getElementById('t'+doc.thread));}, // working code.
//        ths: function(doc) { // working code.
//          return [{pn:doc.pn.getElementById('t'+doc.thread),
//                   type_html: 'thread_html',
//                   page: '?',
//                   __proto__: doc.__proto__}];
//        },

//        pop_post: function(th){ // debuging code.
//          th.post = th.posts[--th.idx_pop];
//          return th.post;
//        },
//        pop_post_prep: function(th){
//          delete th.posts;
//          th.idx_pop = th.posts.length;
//        },
////        pop_post: function(th){ // working code
////          while (th.idx_pop>=0) {
////            var pn = th.children[th.idx_pop--];
////            if (pn.className && pn.className.indexOf('postContainer')!=-1) {
////              th.post = {pn:pn, parse_funcs:this, __proto__:th.__proto__};
////              return true;
////            }
////          }
////          return false;
////        },
//////        post_no: function(post){return parseInt(post.pn.id.substr(3),10);},
////        post_no: function(post){return parseInt(post.pn.id.substr(2),10);}, // 2015.05.12, maybe depends on baord???
        id: 'DEFAULT.thread_html',
        country: 'DEFAULT.thread_html',
        trip: 'DEFAULT.thread_html',
        flag: 'DEFAULT.thread_html',
        name: 'DEFAULT.thread_html',

        th_init: function(th) {th.pn.removeAttribute('class');},
//        th_destroy: function(pn, parse_funcs){},
//        ths: function(doc) { // working code.
//          var ths = this.ths_array(doc, doc.pn.getElementsByClassName('thread'));
//          for (var i=0;i<ths.length;i++) {
//            Object.defineProperty(ths[i], 'no', {value:ths[i].pn.id.substr(1), enumerable:true, writable:true, configurable:true});
//            ths[i].pn.removeAttribute('class'); // collection ISN'T writable? and if wrote, its enumerator doesn't work.
//          }
//          return ths;
//        },
//        no : function(th){return parseInt(th.pn.getElementsByClassName('postContainer')[0].id.substring(2),10);},
//        last_replies: 'catalog_json',
        nof_posts: function(th){
          var nof_posts = th.pn.getElementsByClassName('postContainer').length;
          var nof_files = th.pn.getElementsByClassName('fileText').length;
          var om_info   = th.pn.getElementsByClassName('summary desktop')[0];
          if (om_info) {
            var str = om_info[brwsr.innerText].replace(/\n/g,'');
            nof_posts += parseInt(str.replace(/\ post.*/,''),10);
            nof_files += parseInt('0'+str.replace(/\ image.*/,'').replace(/[^\ ]*\ /g,''),10);
          }
          Object.defineProperty(th,'nof_files',{value:nof_files, enumerable:true, configurable:true, writable:true});
          return nof_posts;
        },
        nof_files: function(th){
          if (!th.hasOwnProperty('nof_posts')) this['nof_posts'](th);
          return th.nof_files;
        },
//        name: function(post){return post.pn.getElementsByClassName('name')[0][brwsr.innerText];}, // same as 8chan
        footer: function(th){return this.insert_footer4(th.pn.getElementsByClassName(site.mobile?'nameBlock':'postInfo')[0]);}, //  desktop')[0]);},
        footer2:function(th){return document.getElementsByClassName('navLinksBot')[0].appendChild(cnst.dom('<div style="clear:both;float:right"></div>'));},
        sticky: function(th){return (th.pn.getElementsByClassName('stickyIcon').length!=0);},
        op_img_url:function(th){
          var img = th.pn.querySelector('img[data-md5]');
//          var img = th.pn.getElementsByTagName('img')[0];
          var url = (img)? img.getAttribute('src') : undefined;
          return url;
        },
        get_thread_links : function(th){return th.pn.querySelectorAll('.replylink,.'+pref.cpfx+'link');}, // PATCH
//        get_thread_links : function(th){return th.pn.getElementsByClassName('replylink');}, 
//        get_omitted_info : function(post){return post.pn.getElementsByClassName('summary')[0];},
        get_omitted_info : function(post){
          var omit_info = post.pn.parentNode && post.pn.parentNode.nextSibling;
          return (omit_info && omit_info.classList.contains('summary'))? omit_info : undefined;
//          var omit_info = post.pn.parentNode.parentNode; // working, but slow.
//          if (omit_info) omit_info = omit_info.getElementsByClassName('summary')[0];
////          if (omit_info && omit_info.childNodes[1]) omit_info = omit_info.childNodes[1];
//          return omit_info;
        },
        set_omitted_info : function(post, info){post.pn.parentNode.parentNode.insertBefore(info, post.pn.parentNode.nextSibling);},
        replace_omitted_info : function(dst, src){dst.childNodes[1].textContent = src.childNodes[1].textContent;}, // not used, but reffered from meguca.
        replace_omitted_info2 : function(dst, src, th){
//          if (!dst.childNodes[1]) { // for 4chan-X v1.13.8.7 in therad, but this isn't required.
          if (dst.tagName==='A') { // for 4chan-X v1.13.8.7
            var span = document.createElement('span');
            span.setAttribute('class','summary');
            span.appendChild(document.createElement('span'));
            span.appendChild(dst);
            dst = span;
          }
//          if (!src) dst.parentNode.removeChild(dst); // BUG, when I choose ALL in expander, the expander will disappear.
          if (!src) {
            dst.childNodes[0].setAttribute('style','display:none');
            dst.childNodes[1].setAttribute('style','display:none');
          } else {
            if (dst.childNodes[1].style.display==='none') {
              if (th.domain===site.nickname) dst.childNodes[0].removeAttribute('style');
              dst.childNodes[1].removeAttribute('style');
            }
            this.replace_omitted_info(dst,src);
          }
        },
        get_op_src: 'thread_json',
        proto: 'post_html',
      },
      'page_json'  : {
        ths: 'DEFAULT.page_json',
        get_op_src: 'thread_json',
        proto: 'thread_json'
      },
      'thread_json'  : {
////        op_img_url: function(th){
////          return site2[th.domain].catalog_json2html3_thumbnail(th.posts[0],th.board);},
        tn_as: 'catalog_html.tn_as',
        sticky: function(th){return th.posts[0].sticky;},
//        time_bumped : function(obj){return (obj.posts[0].bumplimit)? undefined : obj.posts[obj.posts.length-1].time*1000;}, // 4chan doesn't have email field.
//                   obj.posts[site3[this.domain].boards[this.board].bump_limit-2].time*1000 :   // for safety, 2.
//        get_op_src: function(th){return th.op_img_url.replace(/s\..*/,th.ext);},
        get_op_src: function(th, img){
          if (th.op_img_url.indexOf('s.4cdn.org')!=-1) return th.op_img_url;
          if (!th.ext && th.missing_info) scan.scan_ui('image_hover', {tgts: [th.domain+th.board+'j0'], options:{callback:function(){cataLog.image_hover_reentry(img);}, priority:6}}); // NEVER ACTIVATED because catalog.json is read always at initial
          return (th.localArchive)? site2[th.domain].catalog_json2html3_src(th,th.board) :
                 (th.ext)? th.op_img_url.replace(/s(\.\w+)$/, th.ext) : img.src;
//          return (th.ext)? th.op_img_url.replace(/s(\.\w+)$/, th.ext) : img.src;
        },
        consolidate_IDB_result_sub: function(posts){
          posts[0].nof_posts = posts.length;
          var nof_files = 0;
          for (var i=0;i<posts.length;i++) if (posts[i].filename) nof_files++;
          posts[0].nof_files = nof_files;
        },
        flags: 'DEFAULT.page_json',
        hrefs: 'DEFAULT.post_json',
        proto: 'DEFAULT.thread_json'
      },
      'post_html': {
        no : function(th){
          var id = th.pn.getAttribute('id');
          return id? parseInt(id.substr(1),10) || parseInt(id.substr(2),10) : null}, // id check for thread_reader, it hits summary(expander)
        time: function(post){
          return parseInt(post.pn.getElementsByClassName('dateTime')[0].getAttribute('data-utc'),10);
        },
        time_pn: function(post_pn){return parseInt(post_pn.getElementsByClassName('dateTime')[0].getAttribute('data-utc'),10)*1000;}, // returns utc.
        flag: function(post){return post.pn.getElementsByClassName('flag')[1] || post.tFlag;},
        tFlag: function(post){return post.pn.getElementsByClassName('bfl')[1];}, // troll flag in /pol/ // countryFlag -> blf
        country: function(post){
          return (post.flag)? post.flag.getAttribute('class').replace(/\s*flag\-*/g,'').toUpperCase()
            : (post.tFlag)? post.tFlag.className.replace(/\s*bfl\-*/g,'').toLowerCase() : undefined; // getAttribute('alt').toLowerCase() // lower case for troll flag
        },
        country_name: function(post){
          return (post.flag)? post.flag.getAttribute('title') : (post.tFlag)? post.tFlag.getAttribute('title') : undefined;
        },
//        flag: function(post){
//          var flag = post.pn.getElementsByClassName('flag')[1];
////          var flag = (flags.length!=0)? document.importNode(flags[0],false) : null;
//          if (flag) var country = flag.getAttribute('class').replace(/\s*flag\-*/g,'').toUpperCase();
//          else {
//            flag = post.pn.getElementsByClassName('countryFlag')[1]; // for /pol/
//            if (flag) {
//              country = flag.getAttribute('alt');
//              post.troll_country = country.toUpperCase();
//            }
//          }
//          Object.defineProperty(post,'country',{value:country, writable:true, configurable:true, enumerable:true});
//          return flag;
//        },
//        country: function(post){
//          var flag = post.flag;
//          return post.country;
//        },
//        txt2com: function(txt){
//          txt = txt.replace(/(^>[^>].*$)/mg,'<span class="quote">$1</span>');
//          txt = txt.replace(/\*\*([^(\*\*)]*)((\*\*)|$)/g,'<s>$1</s>');
//          return txt.replace(/\n/g,'<br>');},
        txt2com_anchor_class: 'quotelink',
        txt2com_spoiler_replace_txt: '<s>$1</s>',
        pn_name: function(post){return post.pn.getElementsByClassName('desktop')[0].getElementsByClassName('name')[0];},
        name: function(post){return this.pn_name(post).textContent;},
        pn_id: function(post){
          var pn = post.pn.getElementsByClassName('posteruid')[1];
          return (pn)? pn.childNodes[1] : undefined;},
        id: function(post){return (post.pn_id)? post.pn_id.textContent : undefined;},
//        pn_id: function(post){return post.pn.getElementsByClassName('posteruid')[1];},
//        id: function(post){return (post.pn_id)? post.pn_id.className.replace(/\s*posteruid\s*/,'').replace(/id_/,'') : undefined;},
        pn_trip: function(post){return post.pn.getElementsByClassName('postertrip')[1];},
        trip: function(post){return (post.pn_trip)? post.pn_trip.textContent : undefined;},

        time_unit: 1000,
        com:  function(post){return post.pn.getElementsByClassName('postMessage')[0].innerHTML;},
        type_com: 'html',
        txt:  function(post){return post.pn.getElementsByClassName('postMessage')[0][brwsr.innerText];},
        sub: 'DEFAULT.post_html',
        img2src: 'DEFAULT.post_html',
        img2ext: 'DEFAULT.post_html',
        filename: function(post){
          var file = post.pn.getElementsByClassName('file')[0];
          if (!file) return undefined;
          var fileText0 = post.pn.getElementsByClassName('fileText')[0];
          if (!fileText0) return undefined; // Hit this line if file is deleted.
          var fileText_a = fileText0.getElementsByTagName('a')[0];
          var fname = fileText_a.getAttribute('title') || fileText_a.textContent;
          var idx = fname.lastIndexOf('.');
          var filename = fname.slice(0,idx);
          post.ext = fname.slice(idx);
          var info = fileText_a.nextSibling.textContent.replace(/^ \(/,'').replace(/\)$/,'').replace(',','').split(/ |x/);
          post.fsize = parseInt(info[0],10)*(info[1]==='KB'? 1024 : info[1]==='MB'? 1024000 : 1);
          post.w = parseInt(info[2],10);
          post.h = parseInt(info[3],10);
          var img = post.pn.getElementsByTagName('img')[0];
          post.md5 = img.getAttribute('data-md5');
          post.time = parseInt(post.pn.getElementsByClassName('dateTime')[0].getAttribute('data-utc'),10);
          post.tim = parseInt(fileText_a.href.slice(fileText_a.href.lastIndexOf('/')+1).split('.')[0],10);
          post.tn_w = img.naturalWidth;
          post.tn_h = img.naturalHeight;
          return filename;
        },
        prep_mimic: function(post){ // for archiving from html
          var name = post.name; // dummy for invoking getter
          var sub = post.sub;
          var com = post.com;
          var trip = post.trip;
          var id = post.id;
          var time = post.id;
          var country = post.country;
          post.country_name = post.parse_funcs.country_name(post); // no getter
        },
        nofFiles: 'DEFAULT.post_html',
        proto: 'DEFAULT.page_html', // test
//        proto: 'DEFAULT.post_html',
      },
      'post_json': {
        proto: 'DEFAULT.post_json',
      },
      'archive_html': {
        ths: function(doc) {
          var pns = Array.prototype.slice.call(doc.pn.getElementById('arc-list').getElementsByTagName('tr')).slice(1,pref.scan.max_archive+1);
          for (var i=0;i<pns.length;i++) pns[i].parentNode.removeChild(pns[i]);
          return this.ths_array(doc, pns);
        },
        no: function(th){return parseInt(th.pn.getElementsByTagName('td')[0].textContent,10);},
        sub: function(th){
          var sub = th.pn.getElementsByTagName('td')[1].firstChild;
          return sub.tagName==='B'? sub.textContent : '';
        },
        com: function(th){return th.pn.getElementsByTagName('td')[1].lastChild.textContent;},
        nof_posts: function(){return undefined;},
        nof_files: function(){return undefined;},
        filename: function(){return '';},
        footer: function(){return document.createElement('div');},
        proto: 'catalog_html',
      },
      'html_template': { // for test_mode['121']
//        get id(){return this.exe_sub('id');},
//        get pn_id(){return this.exe_sub('pn_id');},
        get country(){return this.exe_sub('country');},
        get trip(){return this.exe_sub('trip');},
        get pn_trip(){return this.exe_sub('pn_trip');},
//        get pn_name(){return this.exe_sub('pn_name');},
        get tFlag(){return this.exe_sub('tFlag');},
        get _links(){return this.exe_sub('_links');},
      },
      'json_template': { // for test_mode['121']
        get country(){return this.exe_sub('country') || this.board_flag && this.board_flag.toLowerCase() || undefined;},
        get _links(){return this.exe_sub('_links');},
      },
    },
    popups_posts_class_hlt: 'highlight',
    popups_href2dbtp: function(href){ //, src, th){
//      if (href[0]==='#' && th) {
//        href = this.link_dbtp2href([th.domain, th.board, th.no, href.substr(2)]);
//        src.setAttribute('href',href);
//      }
      // inputs are: 
      //   #PPPP
      //   /BBBB/thread/TTTT/#PPPP
      //   //boards.4chan.org/r/catalog#s=eddit  for >>>/r/eddit
      //   //boards.4channel.org/int/thread/TTTT
      //   /BBBB/
      //   //i.4cdn.org/BBBB/xxxx.webm
      href = href.replace(/^https*:/,'');
      var hrefs = href.split(/[\/#]/); // hrefs will be [ '', '1234'] when href is #1234.
      var len = hrefs.length;
      var p = hrefs[len-1].substr(1);
      var t = len>=2 && hrefs[len-2] || site.no;
      var b = (len>=4 && hrefs[len-4])? '/'+hrefs[len-4]+'/' : site.board;
      if (hrefs[len-2]==='catalog') {
        p = ''; // hrefs[len-1].slice(2,-3);
        t = ''; // p;
        b = (len>=3 && hrefs[len-3])? '/'+hrefs[len-3]+'/' : site.board;
      } else if (href[0]==='/') {
        if (href[1]==='/') {
          if (href.indexOf('//boards.4chan')===0) {
            p = len>=7? hrefs[6].slice(1) : '';
            t = len>=6? hrefs[5] : '';
            b = len>=4? '/'+hrefs[3]+'/' : site.board;
          } else return [];
        } else if (len!=5) {
          p = len>=5? hrefs[4].slice(1) : '';
          t = len>=4? hrefs[3] : '';
          b = '/'+hrefs[1]+'/';
        }
      }
      return ['4chan',b,t,p]
    },

    update_posts_remove: function(th_old,i,pnode){
if (pref.debug_mode['13'] && th_old.posts[i].pn.parentNode.parentNode!==pnode) console.log(th_old.posts[i].pn);
      var tgt = th_old.posts[i].pn.parentNode;
      var expander = (i==0)? tgt.nextSibling : null; // for merge
      pnode.removeChild(tgt);
      if (expander && expander.tagName==='SPAN') pnode.removeChild(expander);
    },
////    update_posts_remove_lock: function(th_old,i,pnode, now_height){ // TEST, slow implementation
////      var ref, ref_pos;
////      var tgt = th_old.posts[i].pn.parentNode;
////      if (now_height && tgt.offsetTop<now_height) ref = tgt.nextSibling || tgt.parentNode.nextSibling;
////      if (ref) ref_pos = ref.offsetTop;
////      pnode.removeChild(th_old.posts[i].pn.parentNode);
////      return (ref)? ref.offsetTop - ref_pos : 0;
////    },
    update_posts_insert: function(src,dst,i,j,pnode){
      var ref = (j<dst.length)? dst[j].pn.parentNode : null;
      if (!src[i].pn) src[i].pn = this.post_json2html(src[i]);
      var tgt = src[i].pn.parentNode || this.post_container(src[i].pn,src[i].no, i===0? src[i] : null);
      var expander = (i==0)? tgt.nextSibling : null; // for merge
      pnode.insertBefore(tgt, ref);
      if (expander && expander.tagName==='SPAN') pnode.insertBefore(expander, ref);
    },
//    update_posts_insert: function(src,dst,i,j,pnode){ // working code.
//      var ref;
//      if (j<dst.length) {
//        ref = dst[j].pn.parentNode;
//        if (ref.tagName==='SPAN') ref = ref.nextSibling; // skip summary, don't see j===1 for merge.
//      } else ref = null;
//      if (!src[i].pn) src[i].pn = this.post_json2html(src[i], src[i].board);
//      var tgt = src[i].pn.parentNode || this.post_container(src[i].pn,src[i].no);
//      pnode.insertBefore(tgt, ref);
//    },
////    update_posts_insert: function(th,th_old,i,j,pnode){ // working code.
//////      var ref = (j==1)? (th_old.posts[0].pn.parentNode.nextSibling || th_old.posts[0].pn.parentNode) : 
//////                         th_old.posts[th_old.posts.length-1].pn.parentNode;
////      var ref;
////      if (j<th_old.posts.length) {
////        ref = th_old.posts[j].pn.parentNode;
////        if (ref.tagName==='SPAN') ref = ref.nextSibling; // skip summary, don't see j===1 for merge.
////      } else ref = null;
////      if (!th.posts[i].pn) th.posts[i].pn = this.post_json2html(th.posts[i],th.board);
////      pnode.insertBefore(this.post_container(th.posts[i].pn,th.posts[i].no), ref);
////    },
////    update_posts_insert_lock: function(th,th_old,i,j,pnode, now_height){ // TOO SLOW
////      var ref, ref_pos;
////      if (j<th_old.posts.length) {
////        ref = th_old.posts[j].pn.parentNode;
////        if (ref.tagName==='SPAN') ref = ref.nextSibling; // skip summary, don't see j===1 for merge.
////      } else ref = null;
////      if (now_height && ref) ref_pos = ref.offsetTop;
////      if (!th.posts[i].pn) th.posts[i].pn = this.post_json2html(th.posts[i],th.board);
////      pnode.insertBefore(this.post_container(th.posts[i].pn,th.posts[i].no), ref);
////      return (ref_pos<now_height)? ref.offsetTop - ref_pos : null;
////    },
    update_posts0_class: function(pn,search_result) {
      site2['DEFAULT'].update_posts0_class(pn,search_result);
      var summary = pn.parentNode && pn.parentNode.nextSibling;
      if (summary && summary.classList.contains('summary')) site2['DEFAULT'].update_posts0_class(summary,search_result);
    },
    post_container : function(post_pn,no, op) {
      var pn = document.createElement('div');
      pn.setAttribute('id','pc'+no);
      pn.setAttribute('class','postContainer ' + ((op)? 'op' : 'reply') + 'Container');
      if (!op) pn.innerHTML = '<div class="sideArrows" id="sa'+no+'">&gt;&gt;</div>';
      pn.appendChild(post_pn);
      if (op) pn.appendChild(cnst.dom(
        '<div class="summaryContainer"><div class="postLink mobile"><a href="'+site2[op.domain].link_dbtp2href_abs([op.domain, op.board, op.no, op.no])+'" target="_blank" class="button">View Thread</a></div></div>'));
//<div class="postLink mobile"><span class="info">97 Replies / 26 Images</span><a href="thread/138122016" class="button">View Thread</a></div>
      return pn;
    },
    remove_backlink: function(pn,idx){
      var bks = pn && pn.getElementsByClassName('backlink')[0];
      var blk = bks && bks.childNodes[idx];
      if (blk) bks.removeChild(blk);
    },
    add_backlinks_bks: (function(){
      var bks = document.createElement('div'); // why div???
      bks.setAttribute('class','backlink');
      return function(){
        return bks.cloneNode(false);
      }
    })(),
    add_backlinks_add_1: (function(){
      var blk = document.createElement('a');
      blk.setAttribute('class','quotelink');
      var blk2 = document.createElement('span');
      blk2.appendChild(blk);
      blk2.appendChild(document.createTextNode(' '));
      return function(bks, dbtpth){
        blk.setAttribute('href',dbtpth[5]);
        blk.textContent = dbtpth[4];
        var pn = blk2.cloneNode(true);
        if (dbtpth[0]!=='4chan' || pref.test_mode['91']) { // TO BE REMOVED
          var pn1 = pn.firstChild;
          pn1.onclick = this.backlink_onclick;
//          pn1.onmouseover = this.popups_post_entry;
        }
        bks.appendChild(pn);
      };
    })(),
    add_backlinks_bks_query: function(pn, postMenu){
      return pn.getElementsByClassName(postMenu && site.mobile? 'nameBlock' : 'backlink')[0];
    },
////    add_backlinks: function(pn,backlinks,target, th){
////if (pref.test_mode['35']) return;
////      var bks_pn = pn.getElementsByClassName('backlink')[0];
////      var bks = bks_pn || this.add_backlinks_bks();
////      if (!target) bks.innerHTML = ''; // this hits target===0 also and clean up.
////      for (var i=(target || 0);i<backlinks.length;i++) {
////        var dbtp = this.popups_backlink2dbtpth(backlinks[i], th);
//////        var domain = dbtp[0];
//////        var board = dbtp[1];
//////        var post_no = dbtp[3];
//////        var txt = dbtp[4];
//////        var href = site2[domain].link_dbtp2href(dbtp);
//////        if (domain!==site.nickname) href = site2[domain].absolute_link_1(href);
////
//////        var blk = null; // IS THIS REQUIRED???
//////        if (target!==undefined) {
//////          var as = bks.getElementsByTagName('a');
//////          for (var j=0;j<as.length;j++)
//////            if (as[j].textContent===txt) {
//////              blk = as[j];
//////              break;
//////            }
//////        }
//////        if (!blk)
////        this.add_backlinks_add_1(bks, dbtp);
////        if (target) break;
////      }
////      if (!bks_pn) pn.getElementsByClassName('desktop')[0].appendChild(bks); // for 4chan-X v1.13.8.7
//////        var ref = this.backlink_parent_prevSib(pn);
//////        ref.parentNode.insertBefore(bks,ref.nextSibling);
////    },
    add_backlinks_bks_append: function(pn, bks){
      (site.mobile? pn : pn.getElementsByClassName('desktop')[0]).appendChild(bks); // for 4chan-X v1.13.8.7
    },
//    toplevel_anchor: function(pn, th){
//      var as = pn.getElementsByTagName('a');
//      for (var i=0;i<as.length;i++) {
//        var href = as[i].getAttribute('href');
//        if (!href) continue;
//        if (href[0]==='#') as[i].setAttribute('href',this.link_dbtp2href([th.domain, th.board, th.no, href.substr(2)]));
//        else if (href.indexOf('thread')===0) as[i].setAttribute('href',th.board+href);
//      }
//    },
    toplevel_anchor_pos:2,

    link_dbtp2href: function(dbtp, quote){
      return (dbtp[1]!=site.board || dbtp[2]!=site.no? dbtp[1]+'thread/'+dbtp[2] : '') + '#'+(quote?'q':'p')+dbtp[3];
    },
    backlink_onclick: function(){
      highlightReply.call(this,parseInt(this.textContent.substr(2),10)); // call native function in 4chan.
    },
//    backlink_class: 'quotelink',
    backlink_parent_prevSib: function(pn){return pn.getElementsByClassName('postNum')[1];},
    link_dbtp2html: function(dbtp, src_txt){
      return '<a href="'+this.link_dbtp2href(dbtp)+'" class="quotelink">'+(src_txt? src_txt : this.link_dbtp2txt(dbtp))+'</a>';
    },
    post_com2txt: function(post){
      return (!post.com)? '' : this.post_com2txt_finisher(post.com.replace(/<[^>]*>/g,' ').replace(/&gt;/g,'>').replace(/&lt;/g,'<').replace(/&quot;/g,'"').replace(/&#039;/g,"'").replace(/&#0*44;/g,',').replace(/&amp;/g,'&'), post); // most of 4chan. speed: 13.07/2.38 inlainchan.
    },
    post_json2html_file: function(post, board, tn_w, tn_h, additional_class) {
      var fsize_str = !post.fsize? '' : (((post.fsize>1048576)? post.fsize/1048576 : post.fsize/1024)+0.005).toString();
      if (fsize_str) fsize_str = fsize_str.substr(0,fsize_str.indexOf('.')+3) + ((post.fsize>1048576)? ' MB' : ' KB');
//      var fname_server = site2[post.domain].post_json2html_fname_server(post);
//      var fname = site2[post.domain].post_json2html_fname(post);
      var furl = site2[post.domain].catalog_json2html3_src(post,board);
      var turl = site2[post.domain].catalog_json2html3_thumbnail(post,board);
      var f_w, f_h;
      var f = tn_w===250 && tn_h===250? 1 // (op? 1 : 0.5) // assume thumbnail is calculated to fit to 250x250 as 4chan
            : (f_w = tn_w/post.tn_w, f_h = tn_h/post.tn_h, f_w<f_h? f_w : f_h);
//      var tn_f = (op)? 1 : ((post.tn_w>post.tn_h)? post.tn_w : post.tn_h) / 125; // 150;
      return '<div class="file'+(additional_class? ' '+additional_class:'')+'" id="f' + post.no + '">'+
          '<div class="fileText" id="fT' + post.no + '">File: '+
            '<a '+(post.filename.length>30? 'title="'+post.filename+post.ext+'" ':'')+'href="' + furl + '" target="_blank">'+
                  (post.spoiler? 'Spoiler Image' : post.filename.length>30? post.filename.slice(0,25)+'(...)':post.filename) + post.ext + '</a>'+
            ' (' + fsize_str + ', ' + post.w + 'x' + post.h + ')</div>'+
          '<a class="fileThumb" href="' +furl + '" target="_blank">'+
            '<'+(post.ext==='.mp4'? 'video':'img')+' src="' + turl + '" alt="' + fsize_str + '" data-md5="' + post.md5 + '" style="height: ' + ((post.spoiler)? 100 : post.tn_h*f) + 'px; width: ' + ((post.spoiler)? 100 : post.tn_w*f) +'px;"'+(post.ext==='.mp4'? ' controls></video>':'>')+ // <video> for distchan
            '<div data-tip="" data-tip-cb="mShowFull" class="mFileInfo mobile">' + fsize_str + ' ' + post.ext.substr(1).toUpperCase() + '</div>'+
          '</a>'+
        '</div>';
    },
    post_json2html: function(post, op, short_link, op_no, pftn) {
      var board = post.board;
      if (op_no===undefined) op_no = post.op || post.resto || post.no;
      if (op===undefined) op = post.resto===0 || post.no==post.op;
      if (pftn===undefined) {
        pftn = pClg.pref.INST['page'].thumb;
        pftn = pftn[pftn.size];
      }
      var file_html = post.ext? this.post_json2html_file(post, board, pftn.w1, pftn.h1, null, pftn) : '';
      var dbtp = [post.domain, board, op_no, post.no];
//      var href_prefix = (post.domain!=='4chan' || site.board!==board || site.whereami==='catalog')? board+'thread/' : (site.whereami==='page')? 'thread/' : '';
      var url_post  = site2[dbtp[0]].link_dbtp2href_abs(dbtp);
      var url_quote = site2[dbtp[0]].link_dbtp2href_abs(dbtp,true);
      var pn = cnst.dom(
        '<div id="p' + post.no + '" class="post reply">'+
          this. post_json2html_postinfo(post, url_post, url_quote)+'</div>'+
          ((post.ext && op)? file_html : '') +
          this.post_json2html_postinfo(post, url_post, url_quote, true)+
            ((op)? (' &nbsp; <span>[<a href="' + url_post +'" class="replylink">Reply</a>]</span>'+
                   ((short_link)? short_link.replace(/>\[/g,'>').replace(/<a/g,'<span>[<a').replace(/\]<\/a/g,'</a>]</span') : '')) : '') +
            (post.domain==='4chan' && site.nickname==='4chan'? '<a href="#" class="postMenuBtn" title="Post menu" data-cmd="post-menu">\u25b6</a>' : '')+
          '</div>' +
          ((post.ext && !op)? file_html : '') +
          '<blockquote class="postMessage" id="m' + post.no + '">'+ (post.com || '') + '</blockquote>'+ 
        '</div>');
//      if (post.ext && post.func_resize) post.func_resize(pn.childNodes[op? 1 : 2]);
      return pn;
    },
    page_after_onload: function(img, post){
      if (img.parentNode.previousElementSibling && img.parentNode.previousElementSibling.classList.contains(pref.cpfx+'fileText')) // severe chech for isTrusted===2
        img.parentNode.previousElementSibling.lastChild.textContent = ' ('+post.w+'x'+post.h+(img.duration?'x'+Math.round(img.duration)+'s':'')+')';
//      img.parentNode.previousSibling.replaceChild(document.createTextNode(' ('+post.w+'x'+post.h+(img.duration?'x'+Math.round(img.duration)+'s':'')+')'),img.parentNode.previousSibling.lastChild);
    },
    post_json2html_postinfo: function(post, url_post, url_quote, desktop) {
      var sub = post.sub?'<span class="subject">'+post.sub+'</span> ':''; // a blank is here
      return '<div class="postInfo'+(desktop?' desktop':'M mobile')+'" id="pi'+(desktop?'':'m')+post.no+'">'+
        (desktop? '<input type="checkbox" name="'+post.no+'" value="delete"> '+(sub||'') // a blank is here
                : (post.domain==='4chan' && site.nickname==='4chan'? '<a href="'+url_post+'" class="postMenuBtn" title="Post menu" data-cmd="post-menu">...</a>':''))+
        this.post_json2html_nameblock(post, desktop, sub)+
        '<span class="dateTime'+(desktop?'':' postNum')+'" data-utc="'+post.time_tu1000+'">'+site2['common'].change_utc_to_local(post.time_tu)+
        (desktop?'</span> <span class="postNum desktop">':' ')+
          '<a href="'+ url_post  + '" title="Link to this post">No.</a>'+
          '<a href="'+ url_quote + '" title="Reply to this post">' + post.no + '</a>'+
          (post.domain==='4chan' && post.archived? ' <img src="//s.4cdn.org/image/archived.gif" alt="Archived" title="Archived" class="archivedIcon retina">' : '')+
//        '<a href="'+ href_prefix + op_no + '#p' + post.no + '" title="Link to this post">No.</a>'+
//        '<a href="'+ href_prefix + op_no + '#q' + post.no + '" title="Reply to this post">' + post.no + '</a>'+
        '</span>';
    },
    post_json2html_nameblock: function(post, desktop, sub) {
      var color;
      return '<span class="nameBlock">'+
        '<span class="name">' + post.name + '</span> '+
        ((post.trip)? '<span class="postertrip">' + post.trip + '</span> ' : '') +
        ((post.capcode)? '<strong class="capcode hand id_mod" title="Highlight posts by Moderators">##' + post.capcode + '</strong>' : '')+
//        '<img src="//s.4cdn.org/image/modicon.gif" alt="Mod Icon" title="This user is a 4chan Moderator." class="identityIcon retina"></span>
        ((post.since4pass)? '<span title="Pass user since '+post.since4pass+'" class="n-pu"></span>':'')+
        ((post.id)? '<span class="posteruid id_'+post.id+'">(ID: '+
          '<span class="hand" title="Highlight posts by this ID"'+
          (desktop? (color = this.post_json2html_colorID(post), ' style="color: ' + color[1] + '; background-color: rgb(' + color[0] + ');"'):'') + '>'+post.id+'</span>'+
          ')</span> ' : '')+
        site2['4chan'].post_flag2html(post) +
        (desktop? '</span> ' : (site.nickname==='rssn'?'':'<br>')+sub+'</span>');
    },
    post_flag2html: function(post){
      return post.board_flag? '<span title="'+post.flag_name+'" class="bfl bfl-'+post.board_flag.toLowerCase()+'"></span>'
//      return post.troll_country? '<img src="//s.4cdn.org/image/country/troll/'+post.troll_country.toLowerCase()+'.gif" alt="'+post.troll_country+'" title="'+post.country_name+'" class="countryFlag">'
        : post.country? '<span '+(post.country_name? 'title="'+post.country_name+'" ':'')+'class="flag flag-'+post.country.toLowerCase()+'"></span>' : ''; // post.flag? post.flag.outerHTML : '';

    },
    post_json2html_colorID: function(post){
      var rgb = 0;
      for (var i=0;i<post.id.length;i++) rgb = (rgb << 5) - rgb + post.id.charCodeAt(i);
      var r = rgb >> 24 & 255;
      var g = rgb >> 16 & 255;
      var b = rgb >> 8  & 255;
      return [ r+','+g+','+b, (.299 * r + .587 * g + .114 * b > 125)? 'black' : 'white'];
    },
// codes from 4chan (extension.min.1076.js)
//    $.hash = function(e) {
//      var t, i, a = 0;
//      for (t = 0,
//           i = e.length; t < i; ++t)
//        a = (a << 5) - a + e.charCodeAt(t);
//      return a
//    }
//    IDColor.compute = function(e) {
//      var t, i;
//      return t = [],
//      i = $.hash(e),
//      t[0] = i >> 24 & 255,
//      t[1] = i >> 16 & 255,
//      t[2] = i >> 8 & 255,
//      t[3] = .299 * t[0] + .587 * t[1] + .114 * t[2] > 125,
//      this.ids[e] = t,
//      t
//    }
//    ,
//    IDColor.apply = function(e) {
//      var t;
//      t = IDColor.ids[e.textContent] || IDColor.compute(e.textContent),
//      e.style.cssText = "    background-color: rgb(" + t[0] + "," + t[1] + "," + t[2] + ");    color: " + (t[3] ? "black;" : "white;")
//    }
    page_json2html3_skelton: function(obj) {
      var th = document.createElement('div');
      th.setAttribute('class','thread');
      th.setAttribute('id','t'+obj.no);
      return th;
    },
    page_json2html3: function(obj, clone, dst, force_expander, no_expander, pftn){
      var th = this.page_json2html3_skelton(obj);
      var op = clone && this.page_json2html3_cloneOPWithoutFooter(obj.posts[0].pn) || this.post_json2html((obj.posts && obj.posts[0])? obj.posts[0] : obj, true, site2[obj.domain].short_link(obj.key, obj.nof_posts, 2, '[Last ',' Posts]'), obj.posts[0].no, pftn);
      op.setAttribute('class','post op');
      th.appendChild(this.post_container(op,obj.posts[0].no,obj.posts[0]));
      obj.posts[0].pn = op;
      if (obj.posts) {
        for (var i=1;i<obj.posts.length;i++) {
          var pn = clone && obj.posts[i].pn && obj.posts[i].pn.cloneNode(true) || this.post_json2html(obj.posts[i], null, null, null, pftn);
          th.appendChild(this.post_container(pn,obj.posts[i].no));
          obj.posts[i].pn = pn;
        }
        if (!no_expander) this.page_json2html3_add_omitted_info(obj, dst, force_expander);
      }
      return th;
    },
//    page_json2html3_replace_expander : function(posts_old, idx, key) {
//      var omit_info = this.parse_funcs['page_html'].get_omitted_info(posts_old[0]); // posts_old[0].pn.parentNode.parentNode.getElementsByClassName('summary')[0];
//      if (omit_info) omit_info.appendChild(cnst.config_expander(key, idx));
//    },
    page_json2html3_prep_omitted_info: function(posts0, th, force_expander) {
      var omit_info = this.parse_funcs['page_html'].get_omitted_info(posts0);
      if (!omit_info) {
        omit_info = document.createElement('span');
        omit_info.setAttribute('class','summary');
        omit_info.innerHTML = '<img class="extButton expbtn" title="Expand thread" alt="+" data-cmd="expand" data-id="' + th.posts[0].no + '" ' + 
          'src="//s.4cdn.org/image/buttons/burichan/post_expand_plus.png">' +
          '<span class="summary"></span><span style="display: none;">Showing all replies.</span>';
        if (th.domain!==site.nickname) omit_info.childNodes[0].style.display = 'none';
        this.parse_funcs['page_html'].set_omitted_info(posts0,omit_info);
      }
      if (force_expander) omit_info.appendChild(cnst.config_expander(th.key));
      return omit_info.childNodes[1];
    },
    page_json2html3_set_omitted_info: function(nof_posts_omitted, nof_files_omitted) {
      return (nof_posts_omitted<=0)? 'Showing all replies.'
        : nof_posts_omitted + ((nof_posts_omitted==1)? ' reply' : ' replies') +
          ((nof_files_omitted)? ' and ' + nof_files_omitted + ' image' + ((nof_posts_omitted!==1)? 's' : '') : '') + ' omitted.';
    },
//    page_json2html3_add_omitted_info: function(th, th_old, force_expander) { // working code
//      var posts = th.posts;
//      var nof_files = 0;
//      for (var i=0;i<posts.length;i++) nof_files += (posts[i].type_data==='html')? posts[i].pn.getElementsByClassName('file').length :
//                                                    (posts[i].filename)? 1 : 0;
//      var posts_deleted = posts.filter(function(v){return v.deleted_after;});
//      var nof_files_omitted = th.nof_files - nof_files + posts_deleted.filter(function(v){return v.filename;}).length;
//      var nof_posts_omitted = th.nof_posts - posts.length + posts_deleted.length;
//      site2[th_old.domain_html || th.domain_html].page_json2html3_add_omitted_info_html(th, th_old, force_expander, nof_posts_omitted, nof_files_omitted);
//    },
//    page_json2html3_add_omitted_info_html: function(th, th_old, force_expander, nof_posts_omitted, nof_files_omitted, from_initial) {
//      if (!th_old.pn_summary) {
//        force_expander |= th.domain!=='4chan'; //  || pref[cataLog.embed_mode].use_expander_always;
//        if (nof_posts_omitted>0 || force_expander) {
//          var posts0 = th_old.posts? th_old.posts[0] : th.posts[0];
//          var omit_info = this.parse_funcs['page_html'].get_omitted_info(posts0);
//          if (!omit_info) {
//            omit_info = document.createElement('span');
//            omit_info.setAttribute('class','summary');
//            omit_info.innerHTML = '<img class="extButton expbtn" title="Expand thread" alt="+" data-cmd="expand" data-id="' + th.posts[0].no + '" ' + 
//              'src="//s.4cdn.org/image/buttons/burichan/post_expand_plus.png">' +
//              '<span class="summary"></span><span style="display: none;">Showing all replies.</span>';
//            if (th.domain!==site.nickname) omit_info.childNodes[0].style.display = 'none';
//            this.parse_funcs['page_html'].set_omitted_info(posts0,omit_info);
//          }
//          if (force_expander) omit_info.appendChild(cnst.config_expander(th.key));
//          th_old.pn_summary = omit_info.childNodes[1];
//        }
//      }
//      if (th_old.pn_summary && !from_initial)
//        th_old.pn_summary.textContent = (nof_posts_omitted<=0)? 'Showing all replies.'
//                                      : nof_posts_omitted + ((nof_posts_omitted==1)? ' reply' : ' replies') +
//                                        ((nof_files_omitted)? ' and ' + nof_files_omitted + ' image' + ((nof_posts_omitted!==1)? 's' : '') : '') + ' omitted.';
//    },
    catalog_json2html3_thumbnail: function(obj, board) {
      return (!obj.ext)? ''
        : (obj.spoiler)? '//s.4cdn.org/image/spoiler-a1.png'
        : this.protocol+'//i.4cdn.org' + board + obj.tim + 's'  // not 'obj.board' but 'board' is for thread_json.
          + ((obj.ext==='.jpg' || obj.ext==='.png' || obj.ext==='.gif' || obj.ext==='.webm')? '.jpg' : obj.ext);
    },
    catalog_json2html3_src: function(obj, board) {
      return (obj.ext)? this.protocol+'//i.4cdn.org' + board + obj.tim + obj.ext : // not 'obj.board' but 'board' is for thread_json.
                        '';
    },
//    catalog_json2html3_src: function(obj, board) { // working code.
//      return (obj.ext)? this.protocol+'//i.4cdn.org' + board + obj.tim  // not 'obj.board' but 'board' is for thread_json.
//                          + ((obj.ext==='.jpg' || obj.ext==='.webm')? '.jpg' : obj.ext)
//                      : '';
//    },
    catalog_json2html3_file : function(obj,thumb_url, tn_w, tn_h, pftn) {
      var f_w, f_h;
      var f = tn_w===150 && tn_h===150? 3/5 // assume thumbnail is calculated to fit to 250x250 as 4chan
            : tn_w===250 && tn_h===250? 1
            : (f_w = tn_w/obj.tn_w, f_h = tn_h/obj.tn_h, f_w<f_h? f_w : f_h);
      return '<'+(obj.ext==='.mp4'?'video':'img')+' alt="" id="thumb-' + obj.no +
        '" class="'+(site.nickname==='4chan'?'_':'')+'thumb"' + // cause error in native script at trying to make infoPv('.post-previw' in native). This is invoked by the class "thumb".
        ((!obj.spoiler && obj.tn_w)? '" width="'  + obj.tn_w*f : '' ) +
        ((!obj.spoiler && obj.tn_h)? '" height="' + obj.tn_h*f : '' ) +
        '" src="' + thumb_url + '" data-id="' + obj.no + '"'+(obj.ext==='.mp4'?' controls></video>':'>') // <video> for distchan
    },
    catalog_json2html3 : function(obj,board,thumb_url, pftn) {
      var th = this.catalog_json2html3_skelton(obj);
      th.innerHTML = '<a href="'+site2[obj.domain].link_dbtp2href_abs([obj.domain, obj.board, obj.no, obj.no])+'">' + 
        this.catalog_json2html3_file(obj, thumb_url, pftn.w1, pftn.h1, pftn) + '</a>' +
        '<div id="meta-' + obj.no + '" class="meta"></div>' +
//        '<div title="(R)eplies / (I)mages" id="meta-' + obj.no + '" class="meta"></div>' +
//                       (make_footer? 'R: <b>' + obj.nof_posts + '</b> / I: <b>' + obj.nof_files + '</b>':'')+'</div>' +
        '<div class="teaser">' + ((obj.sub)?'<b>'+obj.sub+'</b>':'') + ((obj.com)? ((obj.sub)? ': ' : '' ) + obj.com : '') + '</div>';
      return th;
    },
//    catalog_json2html3 : function(obj,board,thumb_url, make_footer) { // working code
//      var th = this.catalog_json2html3_skelton(obj);
////      var th = document.createElement('div');
////      th.setAttribute('class','thread');
////      th.setAttribute('id','thread-'+obj.no);
//      var pf = pref.catalog.format.thumb;
//      var tmp_w, tmp_h;
//      var f = pf.size==='small' && pf.small.w1===150 && pf.small.h1===150? 3/5
//            : pf.size==='large' && pf.large.w1===250 && pf.large.h1===250? 1
//            : (tmp_w = pf[pf.size].w1/obj.tn_w, tmp_h = pf[pf.size].h1/obj.tn_h, tmp_w<tmp_h? tmp_w : tmp_h);
//      th.innerHTML = '<a href="'+site2[obj.domain].link_dbtp2href_abs([obj.domain, obj.board, obj.no, obj.no])+'">' + // (cause direct jump) fixed.
////      th.innerHTML = '<a href="' + this.protocol + '//boards.4chan.org' + obj.board + 'thread/' + obj.no + ((obj.sub)? '/'+obj.sub.replace(/ /,'-') : '') + '">' + // (cause direct jump) fixed. // cause error at parcing href, and 4chan adiscarded this.
//                     '<'+(obj.ext==='.mp4'?'video':'img')+' alt="" id="thumb-' + obj.no +
////                       '<img alt="" id="thumb-' + obj.no + '" class="thumb"' + // cause popup error, class will be removed in catalog_json2html3_onload
//                         ((!obj.spoiler && obj.tn_w)? '" width="'  + obj.tn_w*f : '' ) +
//                         ((!obj.spoiler && obj.tn_h)? '" height="' + obj.tn_h*f : '' ) +
//                         '" src="' + thumb_url + '" data-id="' + obj.no + '"'+(obj.ext==='.mp4'?' controls></video>':'>')+ // <video> for distchan
//                     '</a>' +
//                     '<div title="(R)eplies / (I)mages" id="meta-' + obj.no + '" class="meta">' +
//                       (make_footer? 'R: <b>' + obj.nof_posts + '</b> / I: <b>' + obj.nof_files + '</b>':'')+'</div>' +
//                     '<div class="teaser">' + ((obj.sub)?'<b>'+obj.sub+'</b>':'') + ((obj.com)? ((obj.sub)? ': ' : '' ) + obj.com : '') + '</div>';
////      if (!obj.tn_w || !obj.tn_h) {
////        th.childNodes[0].childNodes[0].addEventListener('load',site2['4chan'].catalog_json2html3_onload,false);
////      }
//      return th;
//    },
////    catalog_json2html3_onload : function() { // working code
////      this.removeEventListener('load',site2['4chan'].catalog_json2html3_onload,false);
////      if (!this.tn_w || !this.tn_h) site2['4chan'].catalog_json2html3_size_changed(this);
////      this.removeAttribute('class'); // remove popup.
////    },
////    catalog_json2html3_size_changed : function(myself) {
////      var w = myself.naturalWidth;
////      var h = myself.naturalHeight;
////      var f = ((w>h)? w : h) / ((site2['4chan'].catalog_native_size==='small')? 150 : 250);
////      myself.setAttribute('width', w/f);
////      myself.setAttribute('height', h/f);
////    },
    catalog_json2html3_skelton: function(obj) {
      return cnst.dom('<div class="thread" id="thread-'+obj.no+'"></div>');
//      var th = document.createElement('div');
//      th.setAttribute('class','thread');
//      th.setAttribute('id','thread-'+obj.no);
//      return th;
    },
    catalog_merge: function(src_pn, merge, clg, key){
      if (merge.isShown===1) {
        merge.pn.classList.add(pref.cpfx+'merged');
//        merge.pn.childNodes[0].style.float = 'left';
        var merge_icons = merge.pn.getElementsByClassName('threadIcons')[0];
        var footer = clg.footer.add_menu(merge.pn.insertBefore(cnst.dom('<div style="clear:both" class="meta"></div>'), merge.pn.childNodes[merge_icons? 2 : 1]));
        merge.footer = [footer];
        merge.cns = [footer, merge.pn.childNodes[merge_icons? 4:3], merge.pn.childNodes[0]]; // footer, teaser, thumbnail
      }
      var icons = src_pn.getElementsByClassName('threadIcons')[0];
      var cns = [src_pn.childNodes[icons? 2:1], src_pn.childNodes[icons? 3:2], src_pn.childNodes[0]];
      gGEH.pns_all_keys.set(cns[2], key); // for drag'n'drop merging GUI
      if (merge.pn!==src_pn) {
//        cns[2].style.float = 'left';
        merge.pn.insertBefore(cns[2], merge.cns[0]);
        merge.pn.insertBefore(cns[0], merge.cns[1]);
        merge.pn.appendChild(cns[1]);
//        console.log('complete:' +cns[2].firstChild.complete);
//        if (cns[2].firstChild.complete) {
//          setTimeout(function(){gClg.image_resize_th(merge.pn);},0); // avoid racing condition against 'image_resize[12]_onload' at start up.
        clg.image_resize_img(cns[2].firstChild); // clg.image_resize_th(merge.pn);
//        } else {
//          merge.pn.addEventListener('load', clg.image_resize_th_onload_bound, false); // doesn't fire
//          merge.pn.childNodes[1].firstChild.addEventListener('load', clg.image_resize_th_onload_bound, false); // test
//        }
      }
      return cns;
    },
    catalog_separate: function(merge, cns, tgt_th, clg){ // , first){
      var skl = this.catalog_json2html3_skelton(tgt_th);
      if (merge.cns && cns[1]===merge.cns[1]) { // merge.cns[0] refers merged footer.
        merge.cns[1] = cns[1].nextSibling;
        merge.cns[2] = cns[2].nextSibling;
        clg.image_resize_img(cns[2].nextSibling.firstChild, true);
      }
      if (merge.isShown===2) {
        merge.pn.classList.remove(pref.cpfx+'merged');
//        merge.cns[2].style.float = null;
//        merge.footer[0].parentNode.removeChild(merge.footer[0]);
//        merge.footer = null;
        merge.cns = null;
      }
//      cns[2].style.float = null;
      skl.appendChild(cns[2]);
      skl.appendChild(cns[0]);
      skl.appendChild(cns[1]);
      clg.image_resize_img(cns[2].firstChild, true);
      return skl;
    },
    catalog_reorder: function(cns, merge, clg, idx_new){
      if (idx_new==0) {
        clg.image_resize_img(cns[2].firstChild,true);
        clg.image_resize_img(merge.pn.firstChild.firstChild);
        merge.cns[1] = cns[1];
        merge.cns[2] = cns[2];
      }
      merge.pn.insertBefore(cns[2], merge.pn.childNodes[idx_new] || null); // thumbnail
      var idx_footer = merge.isShown + (merge.pn.childNodes[merge.isShown]===merge.footer[0]? 1 : 2); // +1 for merged footer
      merge.pn.insertBefore(cns[0], merge.pn.childNodes[idx_footer + idx_new] || null); // footer
      merge.pn.insertBefore(cns[1], merge.pn.childNodes[idx_footer + merge.isShown + idx_new] || null); // teaser
    },
    favicon : {
      __proto__: site2['DEFAULT'].favicon,
      none: '/favicon.ico',
      reply: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAoUlEQVQ4T61TwRGAMAiTmexMDuVMOlM1XOEoAj60L20gDUlLy8dF6Kd+dM/TqTEmK6shAe4GW8zfQlLVMAGat2NVgr2dUDWJymomBRlJdUCoAEdbFZ7A4qEHw9jHCM5U/k1TiEyMkiqj8ikIgY1YCXxUUQp2NCW3F8malZno9kk9qKLKMKj4nwAScaGiETw2KRix8RWG5MhEjynB24usXusFrlPCCCmAi/UAAAAASUVORK5CYII=',
      reply_to_me: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAvUlEQVQ4T61TXRrDIAiDXWmeabvTzlSvVGb8hFIK3ct8aeUnJkSZxmLZBF+/hBvHWLafRQDY6Wn5B3WKANUhrIkIADQF0Zqxt0NGbP5PACREDhXMDFZREr22g+Wn9VmTMnj3RlqwmKTNJYNfAGi8MPD61mAvEnzAZlDZmA0xs7q08Q7AW5wCVC54aWaxl4A5qFWVCyE+DF830Tf7Kd/ZCBb/BwBFyMgkxNyJgVKFv5CTXeWYM4DqLsTHlD33Lx2GzAg15eTiAAAAAElFTkSuQmCC'
    },
    stats_ID: function(th, posts_new, lth){
      if (!lth.sID) lth.sID = {ids:{}, cs:{}, nid:0, nc:0, np:0, h:null};
      var sID = lth.sID;
      if (th.unique_ips) {
        sID.np = th.unique_ips;
        if (!th.id && th.posts) {
          if (!sID.h) sID.h = [];
          var len = sID.h.length;
          if (sID.h[len-3]!=th.posts[th.posts.length-1].no || sID.h[len-2]!=th.nof_posts || sID.h[len-1]!=th.unique_ips) // not updated data may come because of delay and cache 
            sID.h.push(th.posts[th.posts.length-1].no, th.nof_posts, th.unique_ips);
          else if (th.nof_posts<lth.nof_posts) scan.list_nup.add(lth,0,true); // for cache delay, this is safe and never be infinite loop, because lth.nof_posts will be updated to small value copie3d from th.nof_posts.
        }
      }
      if (posts_new && posts_new.length>0 && (th.id || th.country)) {
        for (var i=0;i<posts_new.length;i++) {
          var c = posts_new[i].country;
          if (c) if (sID.cs[c]) sID.cs[c]++; else {sID.cs[c]=1;sID.nc++;}
          if (posts_new[i].id) {
            var id = (c || '') + posts_new[i].id;
            if (sID.ids[id]) sID.ids[id]++; else {sID.ids[id]=1;sID.nid++;}
          }
        }
      }
      if (th.nof_posts==1) {sID.np=1; return false;}
//      return posts_new && posts_new.length>0 && pref.threadStats.full && (!th.id && !th.unique_ips || th.unique_ips>sID.nid); // for thread moving, but this can't make an initial kick.(BUG)
      return posts_new && posts_new.length>0 && pref.threadStats.full && !th.id && !th.unique_ips; // TEMPORAL PATCH, !th.id SHOULD BE REPLAED BY INFO IN boards.json
    },
    get_icon : function(pn,type,kind){
      return kind=='sticky'? pn.getElementsByClassName('stickyIcon')[0] : null;
    },
    add_icon : function(pn,type,kind){
      var icon = (kind==='sticky')? this.make_tack() : this.make_showAlways();
      var dom, footer;
      var ref = (type!=='headline' && type!=='catalog')? pn.getElementsByClassName('postNum')[1] : pn.getElementsByClassName('threadIcons')[0] ||
        (dom = cnst.dom('<div class="threadIcons"></div>'),
          type==='headline'? (footer = pn.getElementsByClassName(pref.cpfx+'footer')[0], footer.parentNode.insertBefore(dom,footer))
         : pn.insertBefore(dom, pn.getElementsByClassName('meta')[0]));
//        pn.insertBefore(cnst.dom('<div class="threadIcons"></div>'), type==='headline'? pn.firstChild : pn.getElementsByClassName('meta')[0]);
      ref.appendChild(icon);
//      var ref;
////      if (type==='catalog') {
//        ref = pn.getElementsByClassName('threadIcons')[0];
//        if (!ref) {
//          ref = document.createElement('div');
//          ref.setAttribute('class','threadIcons');
//          pn.insertBefore(ref, type==='headline'? null : pn.getElementsByClassName('meta')[0]);
//        }
//        ref.appendChild(icon);
////      } else {
////        var op = pn.getElementsByClassName('op')[0];
////        ref = op.getElementsByClassName('mentioned')[0] || op.getElementsByClassName('post_no')[1].nextSibling;
////        ref.parentNode.insertBefore(icon,ref);
////      }
      return icon;
    },
//    domain_IDB_check: function(board){
//      var domain = (site2['4chanR'].boards[board]===null)? '4chanR' : '4chanB';
//      return (site2[domain].domain_url===site2['4chan'].domain_url)? '4chan' : (httpd.prep_iframe(domain), domain);
//    },
//    boards_prep: function(arr){
//      var obj = {};
//      for (var i=0;i<arr.length;i++) obj['/'+arr[i]+'/'] = null;
//      return obj;
//    },
  };
  site2['4chanB'] = {
    domain_url: 'boards.4channel.org',
//    boards: site2['4chan'].boards_prep(['3', 'a', 'adv', 'an', 'asp', 'biz', 'c', 'cgl', 'ck', 'cm', 'co', 'diy', 'fa', 'fit', 'g', 'gd', 'his', 'int', 'jp', 'k', 'lgbt', 'lit', 'm', 'mlp', 'mu', 'n', 'news', 'o', 'out', 'p', 'po', 'qa', 'qst', 'sci', 'sp', 'tg', 'toy', 'trv', 'tv', 'v', 'vg', 'vip', 'vp', 'vr', 'w', 'wsg', 'wsr', 'x']),
    proto:'4chan'
  };
  site2['4chanR'] = {
    domain_url: 'boards.4chan.org',
//    boards: site2['4chan'].boards_prep(['aco', 'b', 'bant', 'd', 'e', 'f', 'gif', 'h', 'hc', 'hm', 'hr', 'i', 'ic', 'pol', 'r', 'r9k', 's', 's4s', 'soc', 't', 'trash', 'u', 'wg', 'y']),
    home: site.protocol + '//boards.4chan.org/pol/',
    proto:'4chan'
  };
//  site2['4channel'] = {
////    nickname:'4channel',
//    domain_url: 'boards.4channel.org',
//    check_func: site2['4chan'].check_func,
//    proto:'4chan'
//  };
}
if (pref.features.domains['meguca']) {
  site2['meguca1'] = { // meguca.org v1
    nickname : 'meguca',
    protocol: 'https:',
    home : 'https://meguca.org/favicon.ico',
    domain_url: 'meguca.org',
    domain_url_image: 'meguca.org',
    features : {page: false, graph: false, setting2: false, thread_reader:false},
//    features : {page: false, graph: false, setting2: false, thread_reader:false, catalog:false, setting: false}, // temporal
    components: {
      boardlist: '#navTop'
    },
    pref_default: {
//      patch: {delayed_invoke: {use: true, sec:10}},
      catalog:{image_hover:true},
    },
    boards_json:{boards:[{board:'a'}, {board:'an'}, {board:'cr'}, {board:'g'}, {board:'v'}]},
    check_func : function(){
      var href = window.location.href;
      if (href.indexOf('/meguca.org/')!=-1) {
        site.whereami = (href.indexOf('/catalog')!=-1)? 'catalog'
                      : (href.search(/page[0-9]/)!=-1)? 'page'
                      : (href.search(/[0-9]+$/)!=-1)? 'thread'
//                      : (document.getElementsByTagName('title')[0] && document.getElementsByTagName('title')[0].textContent==='404')? '404' 
                      : 'other';
        site.config('meguca.org',this.nickname);
        site.max_page = 3;
        site.header_height = function(){
          var header = document.getElementById('banner');
          if (header) return header.offsetHeight;
          else return 0;
        }
////        site.myself = (site.whereami==='thread')? parseInt(href.replace(/.*res\//,'').replace(/\.html/,''),10) : 0;
////        if (site.whereami==='thread' || site.whereami==='page') {
////          site.embed_to['top']    = document.getElementsByName('postcontrols')[0];
////          site.embed_to['bottom'] = document.getElementsByTagName('footer')[0];
////        } else if (site.whereami==='catalog') {
////          site.embed_to['top']    = document.getElementsByTagName('header')[0].nextSibling;
////          site.embed_to['bottom'] = document.getElementsByTagName('footer')[0];
////        }
////        if (site.whereami==='thread' || site.whereami==='page') {
//////          site.postform = document.getElementsByTagName('form')['post'].getElementsByTagName('tbody')[0];
////          site.postform = document.getElementsByTagName('form')['post'];
////          site.postform_comment = document.getElementById('body');
////          this.postform_prep();
////
//////          var bar_bottom = document.getElementsByClassName('bottom')[0];
//////////          site.root_body2 = bar_bottom.insertBefore(document.createElement('span'),bar_bottom.childNodes[1]); // working code.
////////          site.root_body2 = document.getElementsByClassName('pages')[0];
////////          site.root_body2.setAttribute('style','width:auto');
//////////          site.root_body2 = document.getElementById('style-select');
////        }
////        pref.thread_reader.own_posts_tracker = true;
////        setTimeout(function(){this.postprocess_board(this.boards_json)}.bind(this),0);
        return true;
      } else {
        this.prep_own_posts(); // make own_posts structure WITH PROTOTYPE.
        return false;
      }
    },
    make_url4 : function(dbt){
      var url_prefix  = this.protocol + '//' + this.domain_url; // force to use https:
      if (dbt[3]==='page_json' && dbt[2]==0) dbt[3] = 'catalog_json'; // dbt[2] is string, === can't be used.
      if      (dbt[3]==='page_html')    return [url_prefix + ((dbt[2]!=0)? (parseInt(dbt[2],10)+1) :''), 'html'];
      else if (dbt[3]==='catalog_json') return [url_prefix + '/api/board'+ dbt[1].substr(0,dbt[1].length-1), 'json'];
      else if (dbt[3]==='thread_html')  return [url_prefix + dbt[1] + dbt[2], 'html'];
      else if (dbt[3]==='thread_json')  return [url_prefix + '/api/thread/' + dbt[2], 'json'];
    },
////    prep_own_posts_reg: /^4chan\-track\-[0-9A-z]+\-[0-9]+$/,



    catalog_get_native_area: function(){
      return (site.whereami==='catalog')? document.getElementById('catalog') :
                                          document.getElementsByName('postcontrols')[0];
    },
    catalog_native_prep: function(pn_filter,pn_tb,pn_hi){
//      var node_ref = document.getElementsByClassName('catalog_search')[0].nextSibling;  // FF doesn't work.
      var node_ref = (site.whereami==='catalog')? document.getElementsByTagName('threads')[0].getElementsByTagName('aside')[0] :
                                                  document.getElementsByName('postcontrols')[0];
      if (site.whereami==='catalog') {
////        var selector_native = document.getElementById('sort_by');
////        if (selector_native.selectedIndex!=0) {
////          selector_native.selectedIndex = 0;
////          var evt = document.createEvent('UIEvents');
////          evt.initUIEvent('change', false, true, window, 1);
////          selector_native.dispatchEvent(evt);
////        }
////        selector_native.style.display = 'none';
////        document.getElementById('image_size').addEventListener('change', site2['lain'].catalog_native_size_changed, false);
        var pn_tb_new = document.createElement('span');
        while (pn_tb.firstChild) pn_tb_new.appendChild(pn_tb.firstChild);
        pn_tb = pn_tb_new;
        pn_tb.appendChild(pn_tb.removeChild(pn_tb.childNodes[3]).firstChild);
        pn_tb.setAttribute('style','float:right');
      } else if (site.whereami==='page') {
        var pctrls = document.getElementsByName('postcontrols')[0];
//        for (var i=pctrls.childNodes.length-1;i>=0;i--) if (pctrls.childNodes[i].tagName==='HR') pctrls.removeChild(pctrls.childNodes[i]);
        pctrls.parentNode.insertBefore(document.createElement('hr'),pctrls.nextSibling);
      }
//      node_ref.parentNode.insertBefore(pn_hi,node_ref);
      if (site.whereami==='catalog') node_ref.parentNode.insertBefore(pn_tb,node_ref);
      else node_ref.parentNode.insertBefore(pn_tb,node_ref);
      node_ref.parentNode.insertBefore(pn_filter,document.getElementById('catalog'));
      var selector_catchan = pn_filter.getElementsByTagName('select')['catalog.order.ordering'];
      selector_catchan.setAttribute('style','float:left');
      if (site.whereami==='catalog') node_ref.parentNode.insertBefore(selector_catchan,node_ref);
      else pn_tb.childNodes[3].insertBefore(selector_catchan,pn_tb.childNodes[3].firstChild);
//      pn_tb.childNodes[0].setAttribute('style',pn_tb.childNodes[0].getAttribute('style')+';display:none');
//      pn_tb.childNodes[1].setAttribute('style',pn_tb.childNodes[1].getAttribute('style')+';display:none');

      node_ref.setAttribute('style','float:left');
      node_ref.parentNode.insertBefore(pn_tb.childNodes[3],node_ref.nextSibling);
//      if (site.whereami==='catalog') document.getElementById('catalog').innerHTML = ''; // clear all thread.
    },
    catalog_json2html3 : function(obj,board,thumb_url) {
      var th = document.createElement('article');
      var th_url = site2[obj.domain].make_url4([obj.domain,obj.board,obj.no,'thread_html'])[0];
      var tn_w = obj.tn_w || obj.posts[0].tn_w;
      var tn_h = obj.tn_h || obj.posts[0].tn_h;
      var tn_f = 150 / ((tn_w>tn_h)? tn_w : tn_h);
      var th_class_tn = (site2[obj.domain].historyAPI && pref.pref2.meguca.historyAPI)? 'history' : '';
      var th_class    = th_class_tn + ' ' + pref.cpfx+'link';
      var short_link = site2[obj.domain].short_link(obj.key, obj.nof_posts, 1, 'Last ', '');
      th.innerHTML = '<a target="_blank" rel="nofollow" href="' + th_url + '"' + ((th_class_tn)?' class="' + th_class_tn + '"' : '') +'>'+
                        '<img src="' + thumb_url + '" width="' + (tn_w * tn_f) + '" height="' + (tn_h * tn_f) + '" class="expanded">'+
                     '</a>'+
                     '<br>'+
                     '<small>'+
                       '<span title="Replies/Images"></span>'+ // (make_footer? (obj.nof_posts-1) + '/' + (obj.nof_files-1):'')+ '</span>'+
                       '<span class="act expansionLinks">'+
                         '<a href="' + th_url + '" class="' + th_class + '">Expand</a>'+
//                         '] [<a href="' + th_url + '?last=100" class="' + th_class + '">Last 100</a>'+
                         ((short_link)? '] [' + short_link : '')+
                       '</span>'+
                     '</small>'+
                     '<br>'+
                     ((obj.sub)? '<h3>' + obj.sub + ' </h3>' : '')+
                     ((obj.com)? obj.com : '');
      return th;
    },
//    mark_newer_posts: function(th,date,unmark, short_cut) {
//      return site2['DEFAULT'].mark_newer_posts('meguca',th.getElementsByTagName('article'),date,null,unmark, short_cut);
//    },
//    get_time_of_post_in_utc: function(post_pn){return this.parse_funcs.post_html.time_pn(post_pn);}, // TEMPORAL
////////    mark_newer_posts : function(nickname,posts,date,style_mark,style_unmark,class_or_tag,key,unmark){
//////////      var offset_top = 0;
////////      var marked_first_post = null;
////////      for (var i=posts.length-1;i>=0;i--) {
////////        var mark = date<site2[nickname].get_time_of_post_in_utc(posts[i]);
////////        var reply = posts[i];
//////////        posts[i] = posts[i].parentNode;
//////////        if (class_or_tag=='class') while (posts[i].className.search(key)==-1) posts[i] = posts[i].parentNode;
//////////        if (class_or_tag=='tag') while (posts[i].tagName.search(key)==-1) posts[i] = posts[i].parentNode;
////////        if (class_or_tag=='class') reply = posts[i].getElementsByClassName(key)[0];
////////        if (class_or_tag=='tag')  reply = posts[i].getElementsByTagName(key)[0];
////////        if (reply)
////////          if (mark) {
////////            reply.setAttribute('style',style_mark);
////////            if (unmark) reply.addEventListener('mouseover', site2[nickname].unmark_post_from_event, false);
//////////            offset_top = reply.offsetTop;
////////            marked_first_post = reply;
////////          } else reply.setAttribute('style',style_unmark);
////////      }
//////////      return offset_top;
////////      return marked_first_post;
////////    },
////////    unmark_post_from_event: function() {
////////      this.setAttribute('style','border: none');
////////      this.removeEventListener('mouseover', site2['common'].unmark_post_from_event, false);
////////    },


    prep_own_posts_event : function(e){
      if (e && e.key==='mine') site2['meguca'].prep_own_posts();
      if (window.name==='meguca') send_message('parent',['OWN_POSTS', window.name, site3[window.name].own_posts['ALL']]);
    },
    prep_own_posts_event_received : function(val){
      site3[this.nickname].own_posts['ALL'] = val;
//      for (var i in val) site3[this.nickname].own_posts['ALL'][i] = null;
    },
    prep_own_posts : function(bt){
      if (!site3[this.nickname].own_posts['ALL']) {
        site3[this.nickname].own_posts['ALL'] = {};
        var boards = ['/a/','/an/','/cr/','/g/'];
        for (var i=0;i<boards.length;i++) site3[this.nickname].own_posts[boards[i]] = Object.create(site3[this.nickname].own_posts['ALL']);
      }
      if (localStorage) {
        var nos = JSON.parse(localStorage['mine'] || '{}');
        for (var j in nos) site3[this.nickname].own_posts['ALL'][j] = null;
      }
    },
    wrap_to_parse: {
      get: site2['DEFAULT'].wrap_to_parse.get,
      posts: function(th){
        delete Object.getPrototypeOf(th.posts[0]).com; // PATCH
      },
    },
    short_link:function(name, nof_posts, num, kwd_head, kwd_tail){
      var url = this.make_url4(comf.name2dbt(name))[0];
      var th_class = pref.cpfx+'link';
      return ((!num || num>1)? '<a href="' + url + '?last=50' + '" class="' + th_class + '">' + kwd_head + '50' + kwd_tail + '</a>' : '') +
             '<a href="' + url + '?last=100' + '" class="' + th_class + '">' + kwd_head + '100' + kwd_tail + '</a>';
    },
    catalog_json2html3_src: function(post){return (post.image)? '/static/src/'+post.image.src : undefined}, // TEMPORAL
//    catalog_json2html3_thumbnail: function(post){return (post.image)? '/static/thumb/'+post.image.thumb : undefined}, // TEMPORAL
    catalog_json2html3_thumbnail: function(post){
      return (post.image)? '/static/'+ ((post.image.spoiler!==undefined && !pref[cataLog.embed_mode].open_spoiler_image)? 'spoil/spoilers'+post.image.spoiler+'.png' :
                                        (post.image.thumb!==undefined)? 'thumb/' + post.image.thumb :
                                                                        'src/' + post.image.src) : undefined;
    },
    post_json2html_fname_server: function(post){return (post.image)? post.image.src : undefined;},
    post_json2html_fname: function(post){return (post.image)? post.image.imgnm : undefined;},
    popups_href2dbtp: function(href){ //, src, th){
//      if (href[0]==='#' && th) {
//        href = th.board+href;
//        src.setAttribute('href',href);
//      }
      var hrefs = href.split(/[\/#]/);
      var p = hrefs[hrefs.length-1];
      var t = hrefs[hrefs.length-2];
      var b = '/'+hrefs[hrefs.length-3]+'/';
      return ['meguca',b,t,p]
    },
    link_dbtp2href: function(dbtp){
      return dbtp[1] + dbtp[2] + '#' + dbtp[3];
    },
    time_offset: -5,
    parse_funcs : { // meguca
      'post_json' : {
        get no()  {return this.num;},
        get sub() {return this.subject;},
//        get name() {return this.name;},
        name:'Anonymous', // default value
        get txt() {return this.body;},
        get com() {return (this.body)? this.body.replace(this.anchor_regex,this.anchor_func) : '';},
        get filename() {return (this.image)? this.image.imgnm : undefined;},
        get w() {return (this.image)? this.image.dims[0] : undefined;},
        get h() {return (this.image)? this.image.dims[1] : undefined;},
        get tn_w() {return (this.image)? ((this.image.spoiler!==undefined && !pref[cataLog.embed_mode].open_spoiler_image)? 250 : this.image.dims[2]) : undefined;},
        get tn_h() {return (this.image)? ((this.image.spoiler!==undefined && !pref[cataLog.embed_mode].open_spoiler_image)? 250 : this.image.dims[3]) : undefined;},
        get fsize() {return (this.image)? this.image.size : undefined;},
        get ext() {return (this.image)? this.image.ext : undefined;},
        domain:'meguca',
//        type_html: site.whereami,
        get domain_html() {return site.nickname}, // mimic always
        get parse_funcs_html() {return site2[site.nickname].parse_funcs[site.whereami+'_html'];},
        anchor_regex: /(>>)([0-9]+)/g,
//        pn: undefined, // stop prototype chain.
      },
      'thread_json' : {
        ths: function(obj, parse_obj){
          var proto_obj = {
            board: parse_obj.board,
            anchor_func: (function(){
              var htmls = ['<a href="' + parse_obj.board + parse_obj.thread + '#', '">&gt;&gt;', '</a>'];
              return function(str,ar,no){
                return htmls[0] + no + htmls[1] + no + htmls[2];
              };
            })(),
            get com() {return this.body;}, // PATCH
            __proto__:site2[parse_obj.domain].parse_funcs.post_json
          };
////          for (var i=0;i<obj.length;i++) obj[i].__proto__ = site2[parse_obj.domain].parse_funcs.post_json;
          var i=0;
          while (i<obj.length && obj[i].state===undefined) obj[i++].__proto__ = proto_obj; // 'state' shows that a post is being typed.
          if (i!==obj.length) obj.splice(i,obj.length-i);
          return [{posts: obj, // ext: obj[0].ext, tim:obj[0].time, obj:null,
                   nof_posts:obj.length,
                   key: parse_obj.domain + parse_obj.board + parse_obj.thread, no: parse_obj.thread,
                   __proto__:parse_obj}];
        },
        get_op_src: function(obj){return site2[obj.domain].protocol + '//' + site2[obj.domain].domain_url + '/static/'+ 'src/' + obj.posts[0].image.src;},
        op_img_url: function(obj){return site2[obj.domain].protocol + '//' + site2[obj.domain].domain_url + '/static/'+ site2['meguca'].catalog_json2html3_thumbnail(obj.posts[0]);},
//        op_img_url: function(obj){return site2[obj.domain].protocol + '//' + site2[obj.domain].domain_url + '/static/'+ // working code.
//                                         ((obj.posts[0].image.spoiler!==undefined)? 'spoil/spoilers'+obj.posts[0].image.spoiler+'.png' :
//                                          (obj.posts[0].image.thumb!==undefined)?   'thumb/' + obj.posts[0].image.thumb :
//                                                                                    'src/' + obj.posts[0].image.src);},
//        nof_posts: function(obj){return obj.posts.length;},
        nof_files: function(obj){return obj.posts[0].imgctr -1;}, // patch
////        nof_files: function(obj){
////          var num = 0;
////          for (var i=0;i<obj.posts.length;i++) if (obj.posts[i].image) num++;
////          return num;
////        },
        time: function(obj){return obj.posts[0].time;},
        time_posted: function(obj){return obj.posts[obj.posts.length-1].time;},
        time_created: function(obj){return obj.posts[0].time;},
        time_bumped: function(obj){
          var i=obj.posts.length-1;
          while (i>0) if (!obj.posts[i].email || obj.posts[i].email!=='sage') return obj.posts[i].time; else i++;
          return obj.posts[0].time;
        },
        sticky: function(th){return false;},
        com: function(obj){return obj.posts[0].body;},
        sub: function(obj){return obj.posts[0].sub;},
        name: function(obj){return obj.posts[0].name;},
        filename: function(obj){return obj.posts[0].image.imgnm;},
//        tn_w: function(obj){return obj.posts[0].tn_w;},
//        tn_h: function(obj){return obj.posts[0].tn_h;},

        type_com: 'txt',
//        type_com: 'html',
        time_unit: 1,

//        tn_as: function(th){return th.pn.getElementsByTagName('a');}, // the same as DEFAULT
        //        footer: function(th){return th.pn.getElementsByClassName('meta')[0];},

//        add_op_img_url: site2['DEFAULT'].parse_parts.add_op_img_url,
        add_op_img_url: function(posts,board,domain){
          for (var i=0;i<posts.length;i++)
            if (posts[i].image) posts[i].op_img_url = site2[domain].protocol + '//' + site2[domain].domain_url + '/static/thumb/' + posts[i].image.thumb;
        },

        footer: 'catalog_html',
        footer_prep: 'catalog_html',
        proto: 'DEFAULT.thread_json'
      },
      'catalog_html' : {
        footer: function(th){return th.pn.getElementsByClassName('counters')[0];}, // v3.
//        footer: function(th){return th.pn.getElementsByTagName('small')[0].getElementsByTagName('span')[0];}, // working code for v1, v2.
        footer_prep: function(th){th.footer.textContent = '';},
        ths: function(doc) {
          if (!pref.pref2.meguca.historyAPI) {
            var hist = doc.pn.getElementsByClassName('history');
            for (var i=hist.length-1;i>=0;i--) {
              if (hist[i].textContent==='Expand' || hist[i].textContent==='Last 100') hist[i].setAttribute('class',pref.cpfx+'link');
              else hist[i].removeAttribute('class');
            }
          }
          var ths = this.ths_array(doc,doc.pn.getElementsByTagName('article'));
          if (site.board==='/all/') for (var i=ths.length-1;i>=0;i--) ths[i].board = ths[i].pn.getElementsByTagName('a')[0].getAttribute('href').replace(/\d+$/,''); // for v3.
          return ths;
        },
        no : function(th){return th.pn.getElementsByTagName('a')[0].getAttribute('href').replace(/.*\//g,'');}, // 'replace' for ve.
//        no : function(th){return th.pn.getElementsByTagName('a')[0].getAttribute('href');},
        time_bumped: function(th){return undefined;},
        time_created : function(th){return undefined;},
        nof_posts: function(th){return parseInt(th.footer.textContent.split('/')[0],10);},
        nof_files: function(th){return parseInt(th.footer.textContent.split('/')[1],10);},
        sub: function(th){
          var sub = th.pn.getElementsByTagName('h3')[0];
          return (sub)? comf.escape(sub.textContent) : undefined;
        },
        name: function(th){return undefined;},
        com: function(th){return th.pn.childNodes[th.pn.childNodes.length-1].textContent;},
        sticky: function(th){return false;},
////////        tn_as: function(th){return th.pn.getElementsByTagName('a');},
////////        tn_imgs: function(th){return (th.tn_as[0])? [th.tn_as[0].getElementsByTagName('imgs')[0]] : [];},
////////        class_thread: 'thread',
////////        class_thumbnail: 'thumb',
//////////        op_img_url: function(th){ // working code.
//////////          var img = th.pn.getElementsByTagName('img')[0];
//////////          return (img)? img.getAttribute('src') : undefined; // patch.
//////////        },
        op_img_url: function(th){
          var img = th.pn.getElementsByTagName('img')[0];
          return img && img.getAttribute('src') || '';},
        get_op_src: function(th){return th.op_img_url.replace(/thumb/,'src');}, // patch
//        dynamic_image_hover: true,
        missing_info: 1,
        img2src: function(img, lth){
          if (!lth) lth = this.get_lth_from_node(img);
          if (!lth) return null;
          var src = img.src;
          if (lth.th && lth.th.posts[0].ext) src = src.replace(/thumb/,'src').replace(/\.\w+$/,lth.th.posts[0].ext);
          else if (lth.th.missing_info) scan.scan_ui('image_hover', {tgts: [lth.key], options:{callback:function(){cataLog.image_hover_reentry(img);}, priority:6}}); // NEVER ACTIVATED because catalog.json is read always at initial
          return src;
        },
      },
      'post_html' : {
        time_pn: function(post_pn){
          var time_node = post_pn.getElementsByTagName('time')[0];
          return Date.parse(time_node.title || time_node.textContent) + (pref.localtime_offset - site2['meguca'].time_offset) * 3600000;}, // timezone -5
        pn_name: function(post){return post.pn.getElementsByClassName('name')[0];}, // same as vichan.
      },
    },
    general_event_handler:{
      page:{
        mouseover: function(e){}, // patch for test_mode['98']
      },
      catalog:{
//        mouseover: function(e){ // working code
//          var et = e.target;
//          var et_tagName = et.tagName;
//          if (et_tagName==='IMG')
//            if (pref[cataLog.embed_mode].image_hover && et.parentNode.tagName==='A') cataLog.image_hover_add.call(et, e);
//        },
        image_hover_check_mode: function(img){
          return img.parentNode.parentNode.parentNode.parentNode.id==='catalog'? 'catalog' : 'page';
        },
        recSearch_thread: function(tgt,ecT, className){
          var func = site2['DEFAULT'].general_event_handler.common.recSearch_thread;
          return func(tgt,ecT,'op') || className && func(tgt,ecT,className) || null;
        },
        __proto__: site2['DEFAULT'].general_event_handler.catalog
      },
    },
    favicon : {
      __proto__: site2['DEFAULT'].favicon,
      none: '/static/favicon.ico',
      reply: '/static/css/ui/unreadFavicon.ico',
      reply_to_me: '/static/css/ui/replyFavicon.ico',
    },
    make_tack: function(){ // added pointere-events:none
      var tack = document.createElement('div');
      tack.innerHTML = '<svg class="sticky" xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8"><path style="pointer-events:none" d="M1.34 0a.5.5 0 0 0 .16 1h.5v2h-1c-.55 0-1 .45-1 1h3v3l.44 1 .56-1v-3h3c0-.55-.45-1-1-1h-1v-2h.5a.5.5 0 1 0 0-1h-4a.5.5 0 0 0-.09 0 .5.5 0 0 0-.06 0z"></path></svg>';
      return tack.childNodes[0];
    },
  };
  site2['meguca2'] = { // meguca.org v2
    home: 'https://meguca.org/assets/favicons/default.ico',
    components: {
      boardlist: '#banner', // '#board-navigation'
    },
    boards_json: undefined, // terminate prototype chain
    check_func: function(href, reentry){ // required to be a own property.
      if (href.indexOf('/meguca.org/')!=-1) {
        site.whereami = (href.search(/[A-z]+\/(\?page=\d+)*$/)!=-1)? 'page'
                      : (href.search(/[0-9]+(\?last=\d+)*$/)!=-1)? 'thread'
                      : (href.search(/catalog\/*$/)!=-1)? 'catalog' : 'other';
        site.config('meguca.org',this.nickname, reentry);
        site.no = (site.whereami==='thread')? parseInt(href.replace(/.*meguca\.org\/\w*\/([0-9]+)/,'$1'),10) : '';
        if (site.whereami==='page') {
          var idx = href.indexOf('?page=');
          site.page = idx!=-1 && parseInt(href.substr(idx+6),10) || 0;
        } else site.page = null;
        if (reentry) return;
        site.max_page = 3;
        site.header_height = function(){
          var header = document.getElementById('banner');
          return (header)? header.offsetHeight : 0;
        }
        return true;
      } else {
//        this.prep_own_posts(); // make own_posts structure WITH PROTOTYPE.
        return false;
      }
    },
    check_func_whereami: function(){
    },
////    url_boards_json : function(){return ['https://meguca.org/json/boardList', 'json'];}, // working code.
////    postprocess_board: function(val){
////      for (var i=0;i<val.length;i++) {
////        var bd = liveTag.mems.init({domain:this.nickname, board:'/'+val[i].id+'/'});
////        bd.o = i;
////        if (val[i].tags && val[i].tags.length!=0) liveTag.postprocess_board_add_btag(val[i].tags,bd);
////      }
////    },
    utilize_boards_json: true,
    url_boards_json : function(){return ['https://meguca.org/json/boardTimestamps', 'json'];}, // need to be debugged
    postprocess_board: function(val){
      var order = 0;
      for (var i in val) {
        var bd = liveTag.mems.init({domain:this.nickname, board:'/'+i+'/'});
        bd.o = order++;
        Object.defineProperty(bd,'max',{value:val[i], writable:true});
        if (bd.read_max===undefined) Object.defineProperty(bd,'read_max',{value:0, writable:true});
//        if (val[i].tags && val[i].tags.length!=0) liveTag.postprocess_board_add_btag(val[i].tags,bd);
      }
    },
    make_url4 : function(dbt){
      var url_prefix  = this.protocol + '//' + this.domain_url; // force to use https:
      if (dbt[3].substr(0,4)==='page' && dbt[2]==0) dbt[3] = 'catalog_json'; // dbt[2] is string, === can't be used.
//      if      (dbt[3]==='page_html')    return [url_prefix + ((dbt[2]!=0)? (parseInt(dbt[2],10)+1) :''), 'html'];
      return (dbt[3]==='catalog_json')? [url_prefix + '/json'+ dbt[1], 'json', 'meguca' + dbt[1] + 'j0'] :
             (dbt[3]==='thread_html')?  [url_prefix + dbt[1] + dbt[2], 'html'] :
             (dbt[3]==='thread_json')?  [url_prefix + '/json'+ dbt[1]+dbt[2], 'json'] : undefined;
    },
//    trim_list: 'force_init', // catalog_json is changed to have no posts. // this way is more optimized than fetching each thread when some threads are watched.
    catalog_get_native_area: function(){
      return document.getElementById((site.whereami==='catalog')? 'catalog'
                                   : (site.whereami==='page')?    'index-thread-container' : 'threads');
    },
    catalog_get_native_scroll_area: function(){
      return (site.whereami==='catalog')? document.getElementsByTagName('section')[1] : window;
    },
    catalog_native_prep_wait_loop_timeout: 100,
    catalog_native_prep_wait_loop: function(whereami, callback){
      if (--this.catalog_native_prep_wait_loop_timeout<0) return;
      if ((whereami==='catalog' && (!document.getElementsByName('sortMode')[0])) ||
          (whereami==='page' &&    (!document.getElementById('catalog-controls') || !document.getElementById('index-thread-container'))) ||
          (whereami==='thread' &&  (!document.getElementById('expand-images') || !document.getElementById('threads')))
         ) setTimeout(function(){this.catalog_native_prep_wait_loop(whereami,callback);}.bind(this),1000);
      else callback();
    },
    catalog_native_prep: function(pn_filter,pn_tb,pn_hi){
//      var node_ref = document.getElementsByClassName('catalog_search')[0].nextSibling;  // FF doesn't work.
//      var node_ref = (site.whereami==='catalog')? document.getElementById('threads').getElementsByTagName('aside')[0] :
      var node_ref = (site.whereami==='catalog')? document.getElementsByName('sortMode')[0]
                   : (site.whereami==='page')? document.getElementById('catalog-controls').getElementsByTagName('input')['search']
                   : document.getElementById('expand-images'); // .parentNode.nextSibling;
      if (site.whereami==='catalog' && node_ref.nextSibling===cataLog.components.ordering) return; // return if just focused, not entered from transition.
//      if (pn_tb.childNodes[3] && pn_tb.childNodes[3].ondblclick) cnst.tb_prep_for_embed(pn_tb);
//      if (site.whereami==='catalog' || site.whereami==='page') {
        if (!cataLog.components.initialized) {
//          var pn_tb_new = document.createElement('span');
//          while (pn_tb.firstChild) pn_tb_new.appendChild(pn_tb.firstChild);
//          pn_tb = pn_tb_new;
          pn_tb.setAttribute('style','float:right');
          cataLog.components.pn12_0 = pn_tb;
          cataLog.components.pn_hi = pn_tb.removeChild(pn_tb.childNodes[3]); //.firstChild;
          var redraw = function(){
////            console.log('redraw');
            cataLog.catalog_obj2.func_track_reset();
            cataLog.insert_myself(null,null,null,true);
            cataLog.show_catalog();
          }
//          var reentry_tap = function(e){
//////            console.log('reentry');
//            observer3.disconnect();
////            this.catalog_native_prep(null, pn_filter, pn_tb);
////            if (this.catalog_get_native_area()!=cataLog.components.initialized) { // doesn't work
////              console.log('found background refresh');
////              cataLog.components.initialized = this.catalog_get_native_area();
//          }; // .bind(this);
//          window.addEventListener('focus', reentry_tap, false);

          var updated_by_native = false;
          var observe_native_bg = function(e){
//            updated_by_native = true;
////            console.log('found an update in background');

            if (pref[site.whereami].embed) { // test
//              observer3.disconnect();
//              cataLog.catalog_obj2.masked = false;
              cataLog.general_event_handler.destroy();
//              cataLog.triage.off();
              cataLog.triage_parent_set();
              cataLog.general_event_handler = new cataLog.GEH(cataLog.parent);
              cataLog.general_event_handler.init();
              redraw();
              site2['meguca'].catalog_native_prep(pn_filter, pn_tb);
//              blur_tap();
            }

          };
          var observer3 = new MutationObserver(observe_native_bg);
//          var blur_tap = function(){
//////            console.log('start observe...');
////            observer3.observe(cataLog.parent, {childList: true}); // doesn't work
            observer3.observe(document.getElementById('threads'), {childList: true});
////            cataLog.catalog_obj2.masked = true;
//          };
//          window.addEventListener('blur', blur_tap, false);
          pref_func.settings.onchange_funcs['button_test'] = observe_native_bg; // test

          var loading_image = document.getElementById('loading-image');
          var skip_initial_call = loading_image.style.display!=='none';
          var observer_tap = function(e){
            if (e[0].target.style.display!=='none') {
              site2['meguca'].historyAPI_blocking = true;
              if (site.whereami==='thread') site2['meguca'].prep_own_posts(); // patch for IDB, but BUG, this has a racing condition.
            } else {
              site2['meguca'].historyAPI_blocking = false;
              if (!skip_initial_call) {
////                console.log('mascot off');
                cataLog.DIH.image_hover_remove();
                cataLog.general_event_handler.destroy();
//                cataLog.triage.off();
                for (var name in cataLog.threads) pClg.remove_thread(name, true);
//                for (var name in cataLog.threads) if (cataLog.threads[name][1]) if (cataLog.catalog_obj2.func_hide(name)) cataLog.threads[name][1] = false;

                site2['meguca'].check_func(window.location.href, true);
                if (pref[site.whereami].embed) { // using site.whereami instead of cataLog.embed_mode
//                  cataLog.catalog_obj2.masked = false;
                  cataLog.set_embed_xxxx(); // this changes embed_mode
                  cataLog.triage_parent_set();
                  cataLog.general_event_handler = new cataLog.GEH(cataLog.parent);
                  cataLog.general_event_handler.init();
//                  if (!pref.test_mode['90']) {
                  if ((site.whereami==='catalog' || site.whereami==='page') && !pref.test_mode['90']) redraw();
                  site2['meguca'].catalog_native_prep(pn_filter, pn_tb);
//                  reentry_tap(); // WILL call show_catalog here, from pref3.filter.kwd.clear()
                } // else cataLog.catalog_obj2.masked = true;
                recovery.reentry();
                receive_message_emu();
              }
              skip_initial_call = false;
            }
          };
          var observer = new MutationObserver(observer_tap);
//          observer.observe(document.getElementById('page-container'), {childList: true});
          observer.observe(loading_image, {attributes: true});
////          window.addEventListener('popstate', function(event) {console.log("onpopstate: location: " + document.location + ", state: " + JSON.stringify(event.state));}, false); // doesn't work
//////          setTimeout(function(){require('state').page.onChange('thread', function(e){
//////            console.log('state_changed: ',e);
//////          });},30000);
////          setTimeout(function(){ // working code, but spec was changed on 2017.06.12
////            var test_func = function(e){console.log('state changed');};
////            var div = document.createElement('div');
////            div.id = 'CatChan_csi';
////            div.setAttribute('data-csi',0);
//////            div.textContent = 0;
//////            div.onclick = test_func; // can't call
////            site.script_body.appendChild(div);
////            var scr = document.createElement('script');
////            scr.setAttribute('type', 'application/javascript');
//////            scr.textContent = 'console.log("Hello");';
//////            scr.textContent = 'console.log(require("state"));';
//////            scr.textContent = 'var tgt_func = document.getElementById("CatChan_csi").onclick;'+
//////                              'require("state").page.onChange("thread", tgt_func);';
////            scr.textContent = 'var tgt = document.getElementById("CatChan_csi");'+
//////                              'function mutate(){tgt.textContent+=1;}'+
////                              'function mutate(){tgt.setAttribute("data-csi",parseInt(tgt.getAttribute("data-csi"),10)+1);}'+
////                              'require("state").page.onChange("thread", mutate);'+
////                              'require("state").page.onChange("page", mutate);'+
////                              'require("state").page.onChange("catalog", mutate);';
////            site.script_body.appendChild(scr);
////            site.script_body.removeChild(scr);
////            var observer2 = new MutationObserver(test_func);
////            observer2.observe(div, {attributes: true});
//////            observer2.observe(div.childNodes[0], {attributes: true, characterData: true});
//////            console.log('unsafeWindow: ');
//////            unsafeWindow.require('state').page.onChange('thread', function(e){
//////              console.log('state_changed: ',e);
//////            });
////          },10000);
        }
//      } else if (site.whereami==='page') {
////        var pctrls = document.getElementsByName('postcontrols')[0];
//////        for (var i=pctrls.childNodes.length-1;i>=0;i--) if (pctrls.childNodes[i].tagName==='HR') pctrls.removeChild(pctrls.childNodes[i]);
////        pctrls.parentNode.insertBefore(document.createElement('hr'),pctrls.nextSibling);
//      }
//      node_ref.parentNode.insertBefore(pn_hi,node_ref);
      if (site.whereami==='catalog') {
        node_ref.parentNode.appendChild(pn_tb);
        node_ref.parentNode.parentNode.insertBefore(cataLog.components.pn_hi, node_ref.parentNode);
      } else {
        node_ref.parentNode.insertBefore(pn_tb,node_ref);
        node_ref.parentNode.insertBefore(cataLog.components.pn_hi, node_ref);
      }
      if (site.whereami==='catalog') {
        var ctlg = document.getElementById('catalog');
        ctlg.parentNode.insertBefore(pn_filter,ctlg);
      } else node_ref.parentNode.insertBefore(pn_filter,document.getElementById('catalog'));
      if (site.whereami==='catalog') site2['DEFAULT'].catalog_native_prep_swap_sortSel(node_ref);
      else {
//        var selector_catchan = pn_filter.getElementsByTagName('select')['catalog.indexing'];
//        selector_catchan.setAttribute('style','float:left');
//        if (!cataLog.components.initialized) pn_tb.childNodes[3].insertBefore(cataLog.components.catalog.indexing, pn_tb.childNodes[3].firstChild);
        node_ref.parentNode.insertBefore(cataLog.components.ordering, node_ref.nextSibling);
//        node_ref.setAttribute('style','float:left');
      }
//      pn_tb.childNodes[0].setAttribute('style',pn_tb.childNodes[0].getAttribute('style')+';display:none');
//      pn_tb.childNodes[1].setAttribute('style',pn_tb.childNodes[1].getAttribute('style')+';display:none');
//      if (site.whereami!=='catalog' && !cataLog.components.initialized) node_ref.parentNode.insertBefore(pn_tb.childNodes[3],node_ref.nextSibling);
      if (site.whereami=='catalog' || site.whereami=='page') {
        document.getElementById('banner').style="height:auto";
        pClg.kwd_init(document.getElementById('catalog-controls').getElementsByTagName('input')['search'], pn_filter);
      }
      cataLog.components.initialized = true; // this.catalog_get_native_area();
    },
//    catalog_native_prep: function(date,pn_filter,pn_tb,pn_hi){ // working code
//      //      var node_ref = document.getElementsByClassName('catalog_search')[0].nextSibling;  // FF doesn't work.
//      //      var node_ref = (site.whereami==='catalog')? document.getElementById('threads').getElementsByTagName('aside')[0] :
//      var node_ref = (site.whereami==='catalog')? document.getElementsByName('sortMode')[0] :
//            document.getElementById('expand-images').parentNode.nextSibling;
//      cnst.tb_prep_for_embed(pn_tb);
//      if (site.whereami==='catalog') {
//        var pn_tb_new = document.createElement('span');
//        while (pn_tb.firstChild) pn_tb_new.appendChild(pn_tb.firstChild);
//        pn_tb = pn_tb_new;
//        pn_tb.setAttribute('style','float:right');
//        pn_tb.setAttribute('style','float:right');
//      } else if (site.whereami==='page') {
//        var pctrls = document.getElementsByName('postcontrols')[0];
//        //        for (var i=pctrls.childNodes.length-1;i>=0;i--) if (pctrls.childNodes[i].tagName==='HR') pctrls.removeChild(pctrls.childNodes[i]);
//        pctrls.parentNode.insertBefore(document.createElement('hr'),pctrls.nextSibling);
//      }
//      //      node_ref.parentNode.insertBefore(pn_hi,node_ref);
//      if (site.whereami==='catalog') {
//        node_ref.parentNode.appendChild(pn_tb);
//        node_ref.parentNode.parentNode.insertBefore(pn_tb.removeChild(pn_tb.childNodes[3]).firstChild, node_ref.parentNode);
//      } else node_ref.parentNode.insertBefore(pn_tb,node_ref);
//      if (site.whereami==='catalog') {
//        var ctlg = document.getElementById('catalog');
//        ctlg.parentNode.insertBefore(pn_filter,ctlg);
//      } else node_ref.parentNode.insertBefore(pn_filter,document.getElementById('catalog'));
//      var selector_catchan = pn_filter.getElementsByTagName('select')['catalog.indexing'];
//      selector_catchan.setAttribute('style','float:left');
//      if (site.whereami==='catalog') node_ref.parentNode.insertBefore(selector_catchan,node_ref);
//      else pn_tb.childNodes[3].insertBefore(selector_catchan,pn_tb.childNodes[3].firstChild);
//      //      pn_tb.childNodes[0].setAttribute('style',pn_tb.childNodes[0].getAttribute('style')+';display:none');
//      //      pn_tb.childNodes[1].setAttribute('style',pn_tb.childNodes[1].getAttribute('style')+';display:none');
//
//      node_ref.setAttribute('style','float:left');
//      if (site.whereami!=='catalog') node_ref.parentNode.insertBefore(pn_tb.childNodes[3],node_ref.nextSibling);
//      if (site.whereami=='catalog') {
//        node_ref.style.display = 'none';
//        document.getElementById('banner').style="height:auto";
//        var search = document.getElementById('catalog-controls').getElementsByTagName('input')['search'];
//        var search2 = search.cloneNode(false);
//        search2.name = 'filter.kwd.str';
//        search2.oninput = cataLog.event_func;
//        search.parentNode.insertBefore(search2,search);
//        search.setAttribute('style','display:none');
//        pref3.filter.kwd.obj = [pn_filter.getElementsByTagName('input')['filter.kwd.str'], search2];
//        pref3.filter.kwd.clear();
//      }
//    },
    post_json2html: site2['4chan'].post_json2html,
    page_json2html3_skelton: site2['4chan'].page_json2html3_skelton,
    page_json2html3: site2['4chan'].page_json2html3,
    page_json2html3_replace_expander: site2['4chan'].page_json2html3_replace_expander,
    page_json2html3_prep_omitted_info: site2['4chan'].page_json2html3_prep_omitted_info,
    page_json2html3_set_omitted_info: site2['4chan'].page_json2html3_set_omitted_info,
    post_container: site2['4chan'].post_container,
    wrap_to_parse: site2['DEFAULT'].wrap_to_parse, // bypass meguca1
    catalog_json2html3_thumbnail: function(post){
      return (post.image)? this.protocol + '//' + this.domain_url_image + // fullpath is required for desktopNotification.
        ((post.image.spoiler!==undefined && !pref[cataLog.embed_mode].open_spoiler_image)? '/spoil/default.jpg' :
        (post.image.thumbType===14)? '/audio.png' :
        '/images/thumb/' + post.image.SHA1 + ((post.image.thumbType===0 || post.image.thumbType===undefined && post.image.fileType===0)? '.jpg' : '.png')) : undefined;
//        '/images/thumb/' + post.image.SHA1 + ((post.image.fileType===0)? '.jpg' : '.png')) : undefined;
    },
//    catalog_json2html3_src: (function(){
//      var fileType = ['jpg', 'png', 'gif', 'webm', 'pdf', 'svg', 'mp4', 'mp3', 'ogg'];
//      return function(post){return (post.image)? '/images/src/' + post.image.SHA1 + '.' + fileType[post.image.fileType] : undefined;};
//    })(),
    catalog_json2html3_src: function(post){
      return (post.image)? this.protocol + '//' + this.domain_url_image + '/images/src/' + post.image.SHA1 +
                             (post.ext || Object.getOwnPropertyDescriptor(this.parse_funcs.post_json_template, 'ext').get.call(post)) // for archive. It's before wrap. <- REALLY?
                         : undefined;},
//    catalog_json2html3_src: function(post){return (post.image)? '/images/src/' + post.image.SHA1 + (post.ext || Object.getOwnPropertyDescriptor(this.parse_funcs.post_json_template, 'ext').get.call(post)) :
//                                                                undefined;}, // for archive. It's before wrap.
//    catalog_json2html3_src: function(post){return (post.image)? '/images/src/' + post.image.SHA1 + post.ext : undefined;},
    post_json2html_fname_server: function(post){return (post.image)? post.image.SHA1 + ((post.image.fileType===0)? '.jpg' : '.png') : undefined;},
    post_json2html_fname: function(post){return (post.image)? post.filename + post.ext : undefined;},
    parse_funcs: {
      'page_html': { // for popup
        get_omitted_info: site2['4chan'].parse_funcs.page_html.get_omitted_info,
        set_omitted_info: site2['4chan'].parse_funcs.page_html.set_omitted_info,
        replace_omitted_info: site2['4chan'].parse_funcs.page_html.replace_omitted_info,
        replace_omitted_info2: site2['4chan'].parse_funcs.page_html.replace_omitted_info2,
      },
      'thread_json' : {
        ths: function(obj, parse_obj) {
          var th = this.prep_th_posts(obj);
          th.nof_posts = th.posts.length; // for not being reduced if posts are sliced.
          obj.__proto__ = parse_obj;
//          if (pref.filter.kwd.post) site2['meguca'].wrap_to_parse.posts(th); // patch for each posts' "com". // v3 // patched in catalog_filter_query_keyword.query_11
//          if (pref.filter.kwd.post) site2['DEFAULT'].wrap_to_parse.posts(th); // patch for each posts' "com". // v2
          return [th];
        },
        prep_th_posts: function(obj){ // API was changed on 2017.02.19.
          var th = Object.create(obj); // keep original as it is for archiving.
          if (th.sticky) th.sticky = true; // overwrite property of the same name before setting prototype to use polarity.
          th.board = '/' + obj.board +'/';
          obj.no = obj.id || obj.no; // '|| obj.no' for restoring from archive.
          delete obj.id;
          var posts = [obj];
////          var flag = false; // working code -2016.10.06
////          var max_no = 0;
////          for (var i in th.posts) {
          if (obj.posts) for (var i=0;i<obj.posts.length;i++) {
            var post = obj.posts[i];
//            if (pref.test_mode['72'] && post.editing) break; // static for live tagging and archiving, extraction will be done before finishing the posts.
            if (post.id) {
              post.no = post.id;
              delete post.id;
            }
            posts[posts.length] = post;
////            if (max_no>post.no) flag = true;
////            max_no = post.no;
          }
////          if (flag) posts.sort(function(a,b){return a.no - b.no;});
////          th.posts_obj = th.posts;
          th.posts = posts;
          return th;
        },
        get_op_src: function(obj){return site2[obj.domain].catalog_json2html3_src(obj.posts[0]);},
        op_img_url: 'DEFAULT.common', // function(th) {return site2['meguca'].catalog_json2html3_thumbnail(th, th.board);},
//        op_img_url: function(obj){return site2[obj.domain].protocol + '//' + site2[obj.domain].domain_url + site2['meguca'].catalog_json2html3_thumbnail(obj.posts[0]);},
//        add_op_img_url: site2['DEFAULT'].parse_parts.add_op_img_url,
        time_unit: 1000,
        type_com: 'txt',
//        posts_full: 'DEFAULT.catalog_json',
        posts_full: null,
        has_editing: true,
        posts_obj: function(th,no_in){
          var posts_obj = {};
          var i=th.posts.length;
          while (--i>=0) {
            var no = th.posts[i].no;
            posts_obj[no] = th.posts[i];
            if (no===no_in) break;
          }
          return posts_obj;
//          th.posts_obj = posts_obj;
//          return posts_obj[no_in];
        },
        key: 'DEFAULT.common',
      },
      'catalog_json': {
        ths: function(obj, parse_obj) {
          var ths = [];
          var page_base = parseInt(parse_obj.page || 0, 10);
          if (obj.threads) obj = obj.threads; // for v2
          for (var j=0;j<obj.length;j++) {
            var th = this.prep_th_posts(obj[j]);
            th.page = (page_base + Math.floor(j/15)) + '.' + j%15;
//            if (th.sticky) th.sticky = true; // overwrite property of the same name before setting prototype to use polarity.
//            th.board = '/' + th.board +'/';
//            obj[j].no = th.id;
//            delete obj[j].id;
//            th.posts = [th.__proto__];
//            th.posts_obj = {};
//            th.posts_obj[th.no] = th.posts[0];
            obj[j].__proto__ = parse_obj;
            ths[ths.length] = th;
          }
          return ths;
        },
//        footer: function(th){return th.pn.getElementsByClassName('meta')[0];},
//        posts: 'DEFAULT.catalog_json',
//        posts: function(th){
//          var posts = site2['DEFAULT'].parse_funcs.catalog_json.posts(th);
//          if (th.last_replies) posts = posts.concat(th.last_replies);
//          return posts;
//        },
//        has_editing: false,
        has_posts: true, // API was changed on 2017.02.19. // false,
//        missing_info: 1, // API was changed on 2017.02.19. // com is missing.
        proto: 'thread_json',
      },
      'shortCatalog_json_template': {
        proto: 'catalog_json_template'
      },
      'page_json_template': {
        proto: 'catalog_json_template'
      },
      'catalog_json_template': {
        dont_have_com: true,
        get nof_posts(){return this.postCtr;}, // v3
//        get nof_posts(){return this.postCtr+1;}, // v2
//        com: '', // doesn't have this. // changed.
//        get max(){return this.lastUpdated;},
        proto: 'thread_json_template'
      },
      'thread_json_template': {
        get time_posted(){return this.replyTime*1000;},
        get time_bumped(){return this.bumpTime*1000;},
        get time_created(){return this.time*1000;},
//        get nof_posts(){return this.posts.length;}, // remove editing posts. // BUG. reduced if posts are sliced.
        get nof_files(){return this.imageCtr;},
        get com(){return this.body;},
        proto: 'post_json_template'
      },
      'post_json': {
        anchor: function(board,op,no, cross, href){
          if (!href) href = ((cross)? '/all/' : board) + '#p' + no;
          return '<em>'+
                   '<a class="history post-link" data-id="' + no + '" href="' + href + '">&gt;&gt;' + no + '</a>'+
                   '<a class="hash-link history" href="' + href + '"> #</a> '+
                 '</em>';
        },
      },
      'post_json_template': (function(){
        var fileType = ['jpg', 'png', 'gif', 'webm', 'pdf', 'svg', 'mp4', 'mp3', 'ogg', 'zip', '7z', 'tar.gz', 'tar.xz', 'flac', 'noFile', 'txt'];
        return {
          get filename() {return (this.image)? this.image.name : undefined;},
          get w(){return (this.image)? this.image.dims[0] : undefined;},
          get h(){return (this.image)? this.image.dims[1] : undefined;},
          get tn_w(){return (this.image)? ((this.image.spoiler!==undefined && !pref[cataLog.embed_mode].open_spoiler_image)? 150 : this.image.dims[2] || 125) : undefined;},
          get tn_h(){return (this.image)? ((this.image.spoiler!==undefined && !pref[cataLog.embed_mode].open_spoiler_image)? 150 : this.image.dims[3] || 125) : undefined;},
          get fsize(){return (this.image)? this.image.size : undefined;},
          get ext(){return (this.image)? '.'+fileType[this.image.fileType] : undefined;},
          get sub(){return (this.subject)? comf.escape(this.subject) : '';},
          get capcode(){return this.auth;},
          get md5(){return (this.image)? this.image.MD5 : undefined;},
          get sha1(){return (this.image)? this.image.SHA1 : undefined;},
          get country(){return (this.flag)? this.flag.toUpperCase() : undefined;},
          name: 'Anonymous',
          get txt(){return this.body;},
          get com(){
//            var txt2com = site2[site.nickname].parse_funcs.post_html.txt2com;
            var anchor_class = (site2[site.nickname].parse_funcs.post_html.txt2com_anchor_class)? '" class="' + site2[site.nickname].parse_funcs.post_html.txt2com_anchor_class : '';
            var anchor_regex = /(>>)([0-9]+)/g;
            var anchor_func_tgt = null;
            var anchor_func_1 = (site.nickname==='meguca')? site2['meguca'].parse_funcs.post_json.anchor
                                                          : function(board,op,no,cross){ // for v3
              return '<a href="' + ((cross)? '/cross/' : board) + op + '#p' + no + anchor_class + '">&gt;&gt;' + no + '</a>'; // URL is changed on 2017/05/30
            };
            function anchor_func(str,arrow,no){
              var links = anchor_func_tgt.links;
              var i=0;
              while (i<links.length && links[i].id!=no) i++; // link.no is string, != is used intentionally.
              return (i<links.length)? anchor_func_1(anchor_func_tgt.board, links[i].op, no, anchor_func_tgt.op!=links[i].op, null) // editing post isn't included in links.
                : str;
//              while (i<links.length && links[i][0]!=no) i++; // working code -2018.10.31
//              return (i>=links.length)? str
//                   : anchor_func_1(anchor_func_tgt.board, links[i][1], no, anchor_func_tgt.op!=links[i][1], null); // editing post isn't included in links.
            }
//            function anchor_func(board, str,arrow,no){ // v2
//              var link = this[no];
//              return (link)? '<a href="/' + link.board + '/' + link.op + '#' + no + anchor_class +
//                '">&gt;&gt;' + ((link.board!=board)? '&gt;/'+ link.board +'/' : '') + no + '</a>' : str; // links isn't provided while editing
//            }
            var spoiler_replace_txt = site2[site.nickname].parse_funcs.post_html.txt2com_spoiler_replace_txt || '<spoiler>$1</spoiler>';
            var quote_replace_txt = (site.nickname==='meguca')? '<em>$1</em>' : '<span class="quote">$1</span>';
            var c2t_count;
            function command2txt(str){
              return '<strong>' + str + ' (' + ((this[c2t_count])? this[c2t_count++].val : '') + ')</strong>';
            }
            Object.defineProperty(site2['meguca'].parse_funcs.post_json_template,'com',{get:function(){ // ERROR in test_mode['182'], because site2['meguca'].parse_funcs is hard-corded.
                if (!this.body) return '';
                var com = comf.escape_text(this.body);
                if (this.links) { // v3
                  anchor_func_tgt = this;
                  com = com.replace(anchor_regex,anchor_func);
                }
//                if (this.links) com = com.replace(anchor_regex,anchor_func.bind(this.links, this.board.slice(1,-1))); // v2
                com = com.replace(/(^>[^>].*$)/mg,quote_replace_txt);
                com = com.replace(/\*\*(.*?)((\*\*)|$)/mg,spoiler_replace_txt);
                if (this.commands) {
                  c2t_count = 0;
                  com = com.replace(/^#(pyu|flip|\d*d\d+|8ball)$/mg,command2txt.bind(this.commands));
                }
                com = com.replace(/(https*:\/\/[^\s]*)(\s|$)/mg,'<a href="$1" target="_blank">$1</a>$2');
                return com.replace(/\n/g,'<br>');
              }, enumerable:true, configurable:true});
            return this.com;}, // return Object.defineProperty(...).com doesn't work, because 'this' refers site2['meguca'].parse_funcs.post_json_template.
//          get com(){return (this.body)? site2[site.nickname].parse_funcs.post_html.txt2com((this.links)? this.body.replace(anchor_regex,anchor_func(this.links)) : this.body) : '';}, // BUG, /a/1179831(test thread) doesn't have links and cause error. // links isn't provided while editing.
//          get posts_editing(){
//            var posts_editing = {};
//            for (var i=0;i<this.posts.length;i++) if (this.posts[i].editing) posts_editing[this.posts[i].no] = this.posts[i];
//            return Object.defineProperty(this,'posts_editing',{value:posts_editing, writable:true, enumerable:true, configurable:true}).posts_editing;
//          },
          sticky: false,
//          pn: undefined, // block getter
          get no(){return this.id;},
          time: undefined, // patch
          flag: undefined,
          flags: undefined,
          get parse_funcs_html(){
            Object.defineProperty(site2['meguca'].parse_funcs.post_json_template,'parse_funcs_html',
              {value:site2[site.nickname].parse_funcs[site.whereami+'_html'], writable:true, enumerable:true, configurable:true});
            return this.parse_funcs_html;},
          proto: 'DEFAULT.common'
        };
      })(),
    },
    mimic_always: true,
    post_com2txt: function(post){
      return (post.body)? post.body.replace(/\*\*([^(\*\*)\n]*)((\*\*)|$)/mg,' $1 ') : '';
    },
    proto: 'meguca1'
  };
  comf.Object_modifyDescriptor(site2['meguca2'],'check_func',{writable:false}); // for reentry.

  site2['meguca'] = { // meguca.org v3
    domain_url_image: 'meguca.org/assets', // changed around 2018.04.01
//    domain_url_image: 'images.meguca.org/assets',
    check_func: site2['meguca2'].check_func, // needed as a own property.
//    url_boards_json: function(){return ['https://meguca.org/json/' + ((pref.pref2['meguca'].utilize_boards_json)? 'boardTimestamps' : 'boardList'), 'json'];},
    url_boards_json : function(){return ['https://meguca.org/json/board-list', 'json'];},
//    url_boards_json : function(){return ['https://meguca.org/json/boardList', 'json'];}, // working code before 2017.06.19
    postprocess_board: function(val){
      for (var i=0;i<val.length;i++) {
        var bd = liveTag.mems.init({domain:this.nickname, board:'/'+val[i].id+'/'});
        bd.o = i;
        if (val[i].tags && val[i].tags.length!=0) liveTag.postprocess_board_add_btag(val[i].tags,bd);
      }
    },
    utilize_boards_json: false,
    catalog_get_native_scroll_area: undefined, // always window.
//    spoiler_text: {
//      open_rule: 'del, del em {color: #fff!important;}',
//    },
    make_url4 : function(dbt){
      var url_prefix  = this.protocol + '//' + this.domain_url; // force to use https:
      var json_prefix = '/json/boards'; // '/json'; // working code before  2017.06.19
      return (dbt[3]==='catalog_json')? [url_prefix + json_prefix + dbt[1]+ 'catalog', 'json']
           : (dbt[3]==='page_json')?    [url_prefix + json_prefix + dbt[1]+ '?page='+dbt[2], 'json']
           : (dbt[3]==='thread_html')?  [url_prefix + dbt[1] + dbt[2], 'html']
           : (dbt[3]==='thread_json')?  [url_prefix + json_prefix + dbt[1]+dbt[2], 'json']
           : (dbt[3]==='page_html')?    [url_prefix + dbt[1], 'html'] : undefined;
    },
    differentAPI: true,
    historyAPI: false, // true, // changed on 2017.06.12 
    historyAPI_blocking: false,
    short_link:function(name, nof_posts, num, kwd_head, kwd_tail){
      var url = this.make_url4(comf.name2dbt(name))[0];
      var th_class = 'history lastN-link ' + pref.cpfx+'link';
      return '<a href="' + url + '?last=100' + '" class="' + th_class + '">' + kwd_head + '100' + kwd_tail + '</a>';
    },
    wrap_to_parse: {
      get: site2['DEFAULT'].wrap_to_parse.get,
      posts: function(th, start){site2['DEFAULT'].wrap_to_parse.posts(th, start || th.localArchive && 1 || undefined, {op:th.no || th.id});},
//      posts: function(th, start){site2['DEFAULT'].wrap_to_parse.posts(th, start, {op:th.no || th.id});},
    },
    lth_init: function(th){
      liveTag.mems.init({board:'/cross/', domain:th.domain});
      Object.defineProperty(liveTag.mems['meguca'],'/cross/',{value:liveTag.mems['meguca']['/cross/'], configurable:true, enumerable:false, writable:true});
    },
    prep_own_posts: function(bt){
      if (window.opener && window.name==='meguca') setTimeout(this.prep_own_posts_IDB.bind(this),0); // wait to configure IDB module.
      else this.prep_own_posts_IDB();
    },
    prep_own_posts_IDB: function(bt){
      IDB.req_raw('meguca', 'meguca', 'mine', null, this.prep_own_posts_IDB_callback.bind(this), 'get_all'); // This has a racing condition, http request in subframe is faster than IDB usually.
    },
    prep_own_posts_IDB_callback: function(domain, board, no, result){
      var all = {};
      for (var i in result) all[result[i].id] = null;
      site3[this.nickname].own_posts = {ALL:all};
//      var boards = ['/a/','/an/','/cr/','/g/'];
//      for (var i=0;i<boards.length;i++) site3[this.nickname].own_posts[boards[i]] = all;
      if (window.opener && window.name==='meguca') this.prep_own_posts_event();
    },

    popups_post_pnode: function(pnode){
      while (pnode.tagName!=='ARTICLE' && pnode.parentNode) pnode = pnode.parentNode;
      return pnode;
    },
//    backlink_class: 'backlinks', // for popups_add_1
    add_backlinks_pn: function(pn, target){
      var bks = pn.getElementsByClassName('backlinks')[0];
      if (!bks) {
        bks = document.createElement('span');
        bks.setAttribute('class','spaced backlinks');
        pn.appendChild(bks);
        return {pn:bks, hrefs:{}};
      } else return {pn:bks, hrefs:this.add_backlinks_hrefs(bks, target)};
    },
    add_backlinks_hrefs: function(bks, target){
      var hrefs = {};
      if (!target) bks.innerHTML = '';
      else {
        var as = bks.getElementsByTagName('a');
        for (var j=0;j<as.length;j++) hrefs[as[j].getAttribute('href')] = null;
      }
      return hrefs;
    },
    add_backlinks_add_1: function(bks, dbtp, href){
      var blk = document.createElement('span');
      blk.innerHTML = site2['meguca'].parse_funcs.post_json.anchor(dbtp[1], dbtp[2], dbtp[3], null, href);
      blk = blk.childNodes[0];
//      blk.onclick = this.backlink_onclick;
//      blk.onmouseover = this.popups_post_entry;
      if (dbtp[0]!=='meguca' || pref.test_mode['91']) {
        blk.childNodes[0].onclick = this.backlink_onclick;
        blk.childNodes[1].onclick = this.backlink_onclick;
//        blk.childNodes[0].onmouseover = this.popups_post_entry;
//        blk.childNodes[1].onmouseover = this.popups_post_entry;
      }
      bks.pn.appendChild(blk);
    },
    add_backlinks: function(pn,backlinks,target, th){ // MUST CHANGE TO USE site2['DEFAULT'].add_backlinks, but meguca is down and I can't debugg it now, so I leave this.
      var bks = this.add_backlinks_pn(pn, target);
      for (var i=(target || 0);i<backlinks.length;i++) {
        var dbtp = this.popups_backlink2dbtpth(backlinks[i], th);
        var domain = dbtp[0];
        var href = site2[domain].link_dbtp2href_abs(dbtp);
//        if (domain!==site.nickname) href = site2[domain].absolute_link_1(href);
        if (bks.hrefs[href]!==null) this.add_backlinks_add_1(bks, dbtp, href);
        if (target) break;
      }
    },
    popups_href2dbtp: function(href){ //, src, th){
//      if (href[0]==='#' && th) {
//        href = th.board+th.no+href; // this.link_dbtp2href([th.domain, th.board, th.no, href.substr(2)]);
//        src.setAttribute('href',href);
//      }
      var hrefs = href.split(/[\/#]/);
      var p = hrefs[hrefs.length-1].substr(1); // remove 'p'
      var b = '/'+hrefs[hrefs.length-3]+'/';
      var t = hrefs[hrefs.length-2];
      if (!t) t = -1;
      return ['meguca',b,t,p]
    },
    toplevel_anchor_pos:2,
    link_dbtp2href: function(dbtp){
      return dbtp[1] + dbtp[2] + '#p' + dbtp[3];
    },

    popups_post_fetch: function(th, dbt){
      httpd.req({initiator:'popup',
                 tgts:[{url:'https://meguca.org/json/post/'+dbt[3], responseType:'json', tgt:dbt[0]+'/*/'+dbt[2]+'#'+dbt[3], data_type:'json', domain:th.domain}],
                 callback_1:site2[th.domain].popups_post_fetch_callback.bind(site2[th.domain]),
//                 INDICATOR: {shift:function(){}, report:function(){}},
                 IDX:0, RUNNING:0, SUC:0, max:1,
                 dbt:dbt,
                },8);
    },
    popups_post_fetch_callback: function(req,val, REQ){
      var post_0 = {domain:REQ.dbt[0], board:REQ.dbt[1], no:REQ.dbt[2], type_data:'json', domain_html:site.nickname, key:REQ.dbt[0]+REQ.dbt[1]+REQ.dbt[2]};
//      var post_0 = {domain:REQ.dbt[0], board:'/'+val.response.board+'/', no:REQ.dbt[2], type_data:'json', domain_html:site.nickname};
      delete val.response.board;
      var th = {posts:[post_0, val.response], __proto__:post_0};
      site2[th.domain].wrap_to_parse.posts(th);
      var lth = liveTag.mems.init(th);
//      var lth = liveTag.prep_tags(th);
      this.popups_fetched(th,lth, 1);
//      cataLog.format_html.prepare_html_post(th, th.posts[1]);
//      this.popups_add_1(th, th.posts[1], pref[cataLog.embed_mode].popup, lth.q, th.no===th.posts[1].no, false); // cut quote link.
//      if (this.popup_info && this.popup_info.key===REQ.dbt[0]+REQ.dbt[1]+REQ.dbt[2]+'#'+th.posts[1].no) this.popups_post_entry({target:this.popup_info.node, __proto__:this.popup_info});
    },
    post_json2html_imagesearch_href: {
      google: function(url){return 'https://www.google.com/searchbyimage?image_url='+url;},
      iqdb: function(url){return 'http://iqdb.org/?url='+url;},
      saucenao: function(url){return 'http://saucenao.com/search.php?db=999&amp;url='+url;},
      whatAnime: function(url){return 'https://whatanime.ga/?url='+url;},
      desustorage: function(md5){return 'https://desuarchive.org/_/search/image/'+md5;},
      exhentai: function(sha1){return 'http://exhentai.org/?fs_similar=1&amp;fs_exp=1&amp;f_shash='+sha1;},
    },
    post_json2html_imagesearch_1: function(provider,src_x,label){
      var href = this.post_json2html_imagesearch_href[provider](src_x);
      return '<a class="image-search ' + provider + '" target="_blank" rel="nofollow" href="' + href + '">' + label + '</a>';
    },
    post_json2html_imagesearch: function(pref_imgsearch,furl,md5,sha1){
      return ((pref_imgsearch.google)?             this.post_json2html_imagesearch_1('google'     ,furl,'G' ) : '')+
             ((pref_imgsearch.iqdb)?               this.post_json2html_imagesearch_1('iqdb'       ,furl,'Iq') : '')+
             ((pref_imgsearch.saucenao)?           this.post_json2html_imagesearch_1('saucenao'   ,furl,'Sn') : '')+
             ((pref_imgsearch.whatAnime)?          this.post_json2html_imagesearch_1('whatAnime'  ,furl,'Wa') : '')+
             ((pref_imgsearch.desustorage && md5)? this.post_json2html_imagesearch_1('desustorage',md5, 'Ds') : '')+
             ((pref_imgsearch.exhentai && sha1  )? this.post_json2html_imagesearch_1('exhentai'   ,sha1,'Ex') : '');
    },
    post_json2html: function(post, op, short_link, op_no) {
      var board = post.board;
      var pn = document.createElement('div');
      var time_unit = (post.parse_funcs && post.parse_funcs.time_unit) || 1;
      var date = new Date((post.time || 0) * time_unit);
      var name = post.name || 'Anonymous';
      var file_header_html= '';
      var file_html= '';
      if (post.ext) {
        var fsize_str = (((post.fsize>1048576)? post.fsize/1048576 : post.fsize/1024)+0.005).toString();
        fsize_str = fsize_str.substr(0,fsize_str.indexOf('.')+3) + ((post.fsize>1048576)? ' MB' : ' KB');
//        var fname_server = site2[post.domain].post_json2html_fname_server(post);
//        var fname = site2[post.domain].post_json2html_fname(post);
        var furl = site2[post.domain].catalog_json2html3_src(post,board);
        var turl = site2[post.domain].catalog_json2html3_thumbnail(post,board);
        var tn_f = (op)? 1 : ((post.tn_w>post.tn_h)? post.tn_w : post.tn_h) / 150;
        file_header_html = '<figcaption class="spaced">'+
            '<a class="image-toggle act" hidden=""></a>'+
            ((pref[cataLog.embed_mode].imagesearch.use)? '<span class="spaced image-search-container">'+
              this.post_json2html_imagesearch(pref[cataLog.embed_mode].imagesearch, furl, post.md, post.sha1) + '</span>' : '')+
            '<span class="fileinfo">'+
              '<span class="filesize">' + fsize_str + '</span>'+
              '<span class="dims">' + post.w + 'x' + post.h + '</span>'+
            '</span>'+
            '<a href="' + ((post.domain==='meguca')? furl.replace(/images\.meguca\.org/,'meguca.org') : furl) + '" download="' + post.filename + post.ext + '">' + post.filename + post.ext + '</a>'+
//            '<a href="' + furl + '" download="' + post.filename + post.ext + '">' + post.filename + post.ext + '</a>'+
          '</figcaption>';
        file_html = '<figure>'+
            '<a target="_blank" href="' + furl + '">'+
              '<img src="' + turl + '" width="' + post.tn_w + '" height="' + post.tn_h + '">'+
            '</a>'+
          '</figure>';
      }
      pn.innerHTML = '<article id="p' + post.no + '" class="glass' + ((post.ext)? ' media' : '') + '">'+
          '<input type="checkbox" class="deleted-toggle">'+
          '<header class="spaced"><input type="checkbox" class="mod-checkbox hidden">'+
            '<b class="name">'+post.name+'</b>'+
            ((post.country)? '<img class="flag" src="/assets/flags/' + post.country.toLowerCase() + '.svg" title="' + post.country + '">' : '')+
            '<time>' + site2['common'].change_utc_to_local(date) + '</time>'+
            '<nav>'+
//              '<a class="history" href="' + board + op_no + ((post.domain==='meguca')? '?last=100' : '')) + '#p'+post.no + '">No.</a>'+
//              '<a class="history quote" href="/all/2025573?last=100#p2032325">2032325</a>'+
              '<a class="history" href="' + '#p'+post.no + '">No.</a>'+
              '<a class="history quote" href="#p' + post.no + '">' + post.no + '</a>'+
            '</nav>'+
            ((op)? '<span>'+
              '<span class="act">'+
                 '<a class="history expand-link" href="' + board + post.no + '">Expand</a>'+
              '</span>'+
              ((short_link)? '<span class="act">'+ short_link + 
//                '<a class="history lastN-link" href="/all/2114139?last=100">Last 100</a>'+
              '</span>' : '' )+
            '</span>' : '' )+
            '<a class="control">'+
              '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8">'+
                '<path d="M1.5 0l-1.5 1.5 4 4 4-4-1.5-1.5-2.5 2.5-2.5-2.5z" transform="translate(0 1)"></path>'+
              '</svg>'+
            '</a>'+
          '</header>'+
          file_header_html+
          '<div class="post-container">'+
            file_html+
            '<blockquote>'+
//              '<em>'+
//                '<a class="history post-link" data-id="2032314" href="/all/2025573#p2032314">&gt;&gt;2032314</a>'+
//                '<a class="hash-link history" href="/all/2025573#p2032314"> #</a> '+ // there is a blank here.
//              '</em>'+
//              '<br>Well the grave has already been dug<br>Might as well hop in'+
              post.com+
            '</blockquote>'+
          '</div>'+
        '</article>';
      return pn.removeChild(pn.childNodes[0]);
    },

    
    post_container: function(pn){return pn},
    update_posts_remove: function(th_old,i,pnode){
      pnode.removeChild(th_old.posts[i].pn);
    },
    update_posts_insert: function(src,dst,i,j,pnode){
      var ref = (j<dst.length)? dst[j].pn : pnode.getElementsByTagName('aside')[0];// dst[dst.length-1].pn.nextSibling; // dst[dst.length-1].pn.nextSibling;
      if (!src[i].pn) src[i].pn = this.post_json2html(src[i]);
      var tgt = src[i].pn;
      pnode.insertBefore(tgt, ref);
    },
    page_json2html3_skelton: function(obj, both) {
      var th = document.createElement('section');
      th.setAttribute('class','index-thread');
      th.setAttribute('data-id',obj.no);
      th.innerHTML = '<input type="checkbox" class="deleted-toggle">'+
                     '<aside class="act posting glass"><a href="'+ obj.board + obj.no + '?last=100#bottom">Reply</a></aside>';
      return (both)? [th,th.childNodes[1]] : th;
    },
    page_json2html3: function(obj, clone, dst, force_expander, no_expander){
      var doms = this.page_json2html3_skelton(obj, true);
      var th = doms[0];
      var ref = doms[1];
//      if (!obj.posts) obj.posts = {posts:[{}], _proto__:obj}; // obj.posts = [obj]; // test patch for catalog->page transition.
      var op = this.post_json2html((obj.posts && obj.posts[0])? obj.posts[0] : obj, true, site2[obj.domain].short_link(obj.key, obj.nof_posts, 2, 'Last ',''), obj.no);
//      if (!obj.posts) obj.posts = cataLog.threads[obj.key][4].slice(0,1); // test patch for catalog->page transition.
//      op.setAttribute('class','post op');
      th.insertBefore(op,ref);
      obj.posts[0].pn = op;
      if (obj.posts) {
        for (var i=1;i<obj.posts.length;i++) {
          var pn = this.post_json2html(obj.posts[i]);
          th.insertBefore(pn,ref);
          obj.posts[i].pn = pn;
        }
        if (!no_expander) this.page_json2html3_add_omitted_info(obj,obj.posts,obj.posts);
      }
      return th;
    },
    page_json2html3_replace_expander : function(posts_old, idx, key) {
      var omit_info = this.parse_funcs['page_html'].get_omitted_info(posts_old[0]);
      if (omit_info) omit_info.appendChild(cnst.config_expander(key, idx));
    },
    page_json2html3_add_omitted_info : function(th,posts_old,posts) {
      var nof_files = 0;
      for (var i=0;i<posts.length;i++) nof_files += (posts[i].type_data==='html')? posts[i].pn.getElementsByClassName('figure').length :
                                                    (posts[i].filename)? 1 : 0;
      var nof_files_omitted = th.nof_files - nof_files;
      var nof_posts_omitted = th.nof_posts - posts.length;

      var omit_info = (posts_old)? this.parse_funcs['page_html'].get_omitted_info(posts_old[0]) : null;
      if (nof_posts_omitted!=0) {
        if (!omit_info) {
          omit_info = document.createElement('span');
          omit_info.setAttribute('class','omit');
          omit_info.setAttribute('data-omit',nof_posts_omitted);
          omit_info.setAttribute('data-image-omit',nof_files_omitted);
          omit_info.innerHTML = ' <span class="act"><a href="' + posts[0].no + '" class="history">See All</a></span>';
          if (th.domain!=='meguca' || pref[cataLog.embed_mode].use_expander_always) omit_info.appendChild(cnst.config_expander(th.key));
          if (posts_old) this.parse_funcs['page_html'].set_omitted_info(posts_old[0],omit_info);
        }
        omit_info.childNodes[0].textContent = nof_posts_omitted + ((nof_posts_omitted==1)? ' post' : ' posts') +
                                ((nof_files_omitted)? ' and ' + nof_files_omitted + ' image' + ((nof_posts_omitted!==1)? 's' : '') : '') + ' omitted ';
//        if (th.domain!==site.nickname) omit_info.childNodes[0].style.display = 'none';
      } else if (omit_info) omit_info.childNodes[0].textContent = 'Showing all posts.'; // if (omit_info && posts_old) posts_old[0].pn.removeChild(omit_info);
      return omit_info;
    },


    catalog_json2html3 : function(obj,board,thumb_url) {
      var th = document.createElement('article');
      th.id = 'p'+obj.no;
      th.setAttribute('class','glass media op' + ((obj.deleted)? ' deleted' : ''));
      th.setAttribute('data-id',obj.no);
      var th_url = site2[obj.domain].make_url4([obj.domain,obj.board,obj.no,'thread_html'])[0];
      if (thumb_url) {
        var tn_w = obj.tn_w || obj.posts[0].tn_w;
        var tn_h = obj.tn_h || obj.posts[0].tn_h;
        var tn_f = 150 / ((tn_w>tn_h)? tn_w : tn_h);
      }
      var th_class_tn = (site2[obj.domain].historyAPI && pref.pref2.meguca.historyAPI)? 'history' : '';
      var th_class    = th_class_tn + ((th_class_tn)? ' ':'') + pref.cpfx+'link';
      var short_link = site2[obj.domain].short_link(obj.key, obj.nof_posts, 1, 'Last ', '');
//      if (!thumb_url) thumb_url = site2[obj.domain].catalog_json2html3_thumbnail(obj, obj.board) || site2[obj.domain].catalog_json2html3_thumbnail(obj.posts[0], obj.board); // for reentry
      th.innerHTML = 
        '<input type="checkbox" class="deleted-toggle">'+
        ((thumb_url)? '<figure>'+
          '<a class="' + th_class_tn + '" href="' + board + obj.no + '">'+
          '<img width="' + (tn_w * tn_f) + '" height="' + (tn_h * tn_f) + '" class="catalog" src="'+ thumb_url + '"></a>'+
        '</figure>' : '' )+
        '<span class="spaced thread-links hide-empty">'+
          '<b class="board">' + board + '</b>'+
          '<span class="counters" title="Posts/Images"></span>'+ // (make_footer? (obj.nof_posts-1) + '/' + (obj.nof_files-1):'')+'</span>'+
          '<span class="act">'+
            ((obj.domain==='meguca')? '<a class="' + th_class_tn + ' lastN-link" href="' + board + obj.no + '?last=100">Last 100</a></span>' :
                                      '<a href="' + th_url + '" class="'+ th_class + ' lastN-link">Expand</a>'+
                                      ((short_link)? '] [' + short_link : ''))+
          '</span>'+
        '</span><br>'+
        ((obj.sub)? '<h3>' + obj.sub + ' </h3>' : '')+
        '<blockquote>' + ((obj.com)? obj.com : '') + '</blockquote>';
      return th;
    },
//    mark_newer_posts: function(th_pn,date,unmark, short_cut, th) {
//      var ths = (th)? [th] : site2['meguca'].wrap_to_parse.get(document, 'meguca', site.board, site.whereami+'_html', (site.whereami==='thread')? {thread:site.no} : null);
////      if (!th && th_pn!==document) return site2['meguca1'].mark_newer_posts(th_pn,date,unmark, short_cut); // patch.
////      if (!th) {
////        th = JSON.parse(document.getElementById('post-data').innerHTML); // JSON.parse(th_pn.getElementById('post-data').innerHTML);
////        th.posts = [th].concat(th.posts);
////        var pns = th_pn.getElementsByTagName('article');
////        for (var i=0;i<pns;i++) th.posts[i].pn = pns[i];
////      }
//      var marked_first_post = null;
//      for (var i=ths.length-1;i>=0;i--)
//        marked_first_post = ths[i].posts[0].pn && this.mark_newer_posts2(ths[i].posts, date, unmark, short_cut) || marked_first_post; // ths[i].posts[0].pn for reentry.
//      return marked_first_post;
//    },
    parse_funcs: {
      'shortCatalog_json': {
        has_posts: false,
        proto: 'meguca2.catalog_json'
      },
      'catalog_json': {
        has_posts: false, // API was changed on 2017/5/24.
      },
      'catalog_html': {
        ths_json: 'thread_html',
        ths: function(doc) {
          var ths = site2['meguca2'].parse_funcs.catalog_html.ths(doc);
          var obj = this.ths_json(doc, ths.map(function(v){return v.pn;}), 'shortCatalog_json', 'catalog');
          for (var i=0;i<obj.length;i++) obj[i].page = Math.floor(i/15) + '.' + (i%15);
          return obj; //site2['meguca'].wrap_to_parse.get(obj, 'meguca', doc.board, 'shortCatalog_json');
//          setTimeout(function(){
//          cataLog.scan_boards_keyword_callback2('meguca,'+doc.board+',j0,shortCatalog_json', {date:Date.now(), status:200, response:obj},
//            ['reentry_data',Object.create(cataLog.scan_boards_keyword_callback2_default_args)]);
//          }, 0);
//          return ths; // REDUNDANT, pass twice.
//          return site2['meguca2'].parse_funcs.catalog_html.ths(doc);
        },
        get_omitted_info: function(){},
        set_omitted_info: function(){},
        pn_name: 'thread_html',
      },
      'page_json': {
        proto: 'meguca2.catalog_json',
      },
      'post_json': {
        pn: 'DEFAULT.post_json',
      },
      'page_html': {
        ths: function(doc) {
          var obj = this.ths_json(doc, document.getElementsByClassName('index-thread'), 'page_json', 'page');
//          var href = window.location.href;
//          var idx  = href.indexOf('?page=');
//          var page = (idx!=-1)? href.substr(idx+6) : 0; // SHOULD BE site.page;
          for (var i=0;i<obj.length;i++) obj[i].page = site.page + '.' + i;
          return obj;
        },
//        footer: function(th){return this.insert_footer4(th.pn.getElementsByClassName('deleted-toggle')[0]);},
        get_max_page: function(doc){
          var len;
          var pgn = doc.getElementsByClassName('pagination')[0];
          return pgn? (len = pgn.childNodes.length, (len<=1)? len : len-1) : 1;
        },
        proto: 'thread_html',
      },
      'thread_html': {
        ths_json: function(doc, pns, type_parse, type_html) {
          var obj = JSON.parse(doc.pn.getElementById('post-data').innerHTML);
          obj = site2['meguca'].wrap_to_parse.get(obj, 'meguca', doc.board, type_parse);
          var dic = {};
          for (var i=0;i<pns.length;i++) dic[pns[i].dataset.id] = pns[i];
          for (var i=0;i<obj.length;i++) {
            obj[i].pn = dic[obj[i].no];
            var posts_pns = obj[i].pn && obj[i].pn.getElementsByTagName('article');
            if (posts_pns) {
              for (var j=0;j<obj[i].posts.length;j++) obj[i].posts[j].pn = posts_pns[j];
              Object.defineProperty(obj[i], 'footer', {value:this.footer(obj[i]), configurable:true, enumerable:true, writable:true});
              Object.defineProperty(obj[i], 'type_mimic', {value:type_html, configurable:true, enumerable:true, writable:true});
            }
          }
//          Object.defineProperty(obj[0].__proto__.__proto__.__proto__, 'type_mimic', {value:type_html, configurable:true, enumerable:true, writable:true}); // BUG. this also effects *_json at next fetch.
          obj[0].__proto__.__proto__.__proto__.parse_funcs_html = site2['meguca'].parse_funcs[type_html+'_html'];
          return obj;
        },
        ths: function(doc) {return this.ths_json(doc, [document.getElementById('thread-container')], 'thread_json', 'thread');},
        footer: function(th){return site2['DEFAULT'].parse_funcs.page_html.insert_footer4(th.pn.getElementsByClassName('deleted-toggle')[0]);},
        get_thread_links: function(th){return th.pn.querySelectorAll('.expand-link,.lastN-link,.'+pref.cpfx+'link');},
        get_omitted_info: function(post){
          return post.pn && post.pn.getElementsByClassName('omit')[0] || undefined;
//          var omit_info = post.pn && post.pn.lastChild; // post.pn for transition
//          return (omit_info && omit_info.classList.contains('omit'))? omit_info : undefined; // BUG, this refers backlinks
        },
        set_omitted_info : function(post, info){if (info) post.pn.appendChild(info);},
        replace_omitted_info : function(dst, src){dst.childNodes[0].textContent = src.childNodes[0].textContent;},
        replace_omitted_info2 : function(dst, src, th){
          if (!src) {
            var span = document.createElement('span');
            span.setAttribute('style','display:none');
            dst.insertBefore(span,dst.childNodes[1]);
            span.appendChild(dst.childNodes[0]);
            dst.childNodes[1].setAttribute('style','display:none');
          } else {
            if (dst.childNodes[1].style.display==='none') {
              dst.insertBefore(dst.childNodes[0].childNodes[0], dst.firstChild);
              dst.removeChild(dst.childNodes[1]);
              dst.childNodes[1].removeAttribute('style');
            }
            this.replace_omitted_info(dst,src);
          }
        },
//        replace_omitted_info2: site2['4chan'].parse_funcs.page_html.replace_omitted_info2,
        pn_name: function (post){return post.pn.getElementsByClassName('name')[0].childNodes[0];},
      },
      'thread_json': {
        prep_to_archive: function(obj){
          obj[0].posts = obj.slice(1);
          return obj[0];
        },
        proto: 'meguca2.thread_json',
      },
    },
    favicon : {
      __proto__: site2['DEFAULT'].favicon,
      none: '/assets/favicons/default.ico',
      reply: '/assets/favicons/unread.ico',
      reply_to_me: '/assets/favicons/reply.ico',
    },
    proto: 'meguca2'
  };
  comf.Object_modifyDescriptor(site2['meguca'],'check_func',{writable:false}); // for reentry.

  site2['imeguca'] = {
    nickname: 'imeguca',
    domain_url: 'images.meguca.org',
    home: 'https://images.meguca.org/assets/favicons/default.ico',
    check_func: site2['DEFAULT'].check_func,
  };
}
if (pref.features.domains['lain'] || pref.features.domains['lainjp']) {
  site2['lain'] = { //lainchan.org // 5.1.3
    nickname : 'lain',
    home : site.protocol + '//lainchan.org/q/index.html',
//    home : site.protocol + '//lainchan.org',
    X_FRAME_OPTIONS: true,
    CONTENT_SECURITY_POLICY_FRAME: true,
    protocol: 'https:',
    domain_url: 'lainchan.org',
    postform_rules: null,
    features : {page: false, graph: true, setting2: false},
    boards_json:site2['DEFAULT'].generate_boards_json([['\u03bb',5],['diy',2],['sec',6],['tech',6],['inter',2],['lit',3],['music',3],['vis',3],['hum',3],['drg',3],['zzz',2],['layer',1],['q',7],['r',7]],3),
//    boards_json:{boards:[{board:'cyb', pages:7}, {board:'sci', pages:2}, {board:'tech', pages:7}, {board:'\u03bb', pages:7}, {board:'layer', pages:5}, {board:'zzz', pages:17}, {board:'w', pages:4}, {board:'feels', pages:7}, {board:'drg', pages:4}, {board:'lit', pages:7}, {board:'civ', pages:1}, {board:'diy', pages:7}, {board:'art', pages:7}, {board:'r', pages:9}, {board:'q', pages:3}, {board:'f', pages:2}, {board:'sec', pages:3}, {board:'cult', pages:4}]},
    check_func : function(){
      var href = window.location.href;
      if (href.indexOf(this.domain_url)===-1 || href.search(new RegExp(this.domain_url+'\/*$'))!=-1) return false;
//      if (href.indexOf(new RegExp(this.domain_url))!=-1) {
        site.whereami = (href.indexOf('/catalog')!=-1)? 'catalog'
                      : (href.indexOf('/res/')!=-1)? 'thread'
                      : (document.title==='404')? '404' 
//                      : (site.board==='/all/' || site.board==='/popular/')? 'other'
//                      : (!pref.test_mode['29'] && (site.board==='/all/' || site.board==='/popular/'))? 'other'
                      : 'page';
//                      : (href.search(/\/$|(index|[0-9]+)\.html|\/all$|\/popular$/)!=-1)? 'page'
//                      : 'other';
        if (site.whereami==='thread') site.no = parseInt(href.replace(/.*res\//,'').replace(/\.html/,''),10);
        site.config(this.domain_url, this.nickname);
        site.max_page = 7;
        site.header_height = function(){
          var header = document.getElementsByClassName('boardlist')[0];
          return (header)? header.offsetHeight : 0;
        }
//        site.myself = site.no || 0;
        site.embed_to = (site.whereami==='thread' || site.whereami==='page')? {
          top: function(){return document.getElementsByName('postcontrols')[0];},
          bottom: function(){return document.getElementsByTagName('footer')[0];}
        } : (site.whereami==='catalog')? {
          top: function(){return document.getElementsByTagName('header')[0].nextSibling;},
          bottom: function(){return document.getElementsByTagName('footer')[0];}
        } : {};
        if (site.whereami==='thread' || site.whereami==='page') {
//          site.postform = document.getElementsByTagName('form')['post'].getElementsByTagName('tbody')[0];
          site.postform = document.getElementsByTagName('form')['post'];
          if (site.postform) {
//            site.postform_comment = document.getElementById('body');
            this.postform_prep();

//          var bar_bottom = document.getElementsByClassName('bottom')[0];
//////          site.root_body2 = bar_bottom.insertBefore(document.createElement('span'),bar_bottom.childNodes[1]); // working code.
////          site.root_body2 = document.getElementsByClassName('pages')[0];
////          site.root_body2.setAttribute('style','width:auto');
//////          site.root_body2 = document.getElementById('style-select');
          }
        }
//        pref.thread_reader.own_posts_tracker = true;
////////        setTimeout(function(){this.postprocess_board(this.boards_json)}.bind(this),0);
//        var ss = document.querySelector('link[href^="/stylesheets/"]');
        // http://stackoverflow.com/questions/2635814/javascript-capturing-load-event-on-link
        // But https://pie.gd/test/script-link-events/
//        if (this.nickname==='lain') {
//          styleSheet.add_to_watch(document.getElementById('stylesheet'));
//          styleSheet.add_to_watch(document.getElementById('code_stylesheet'));
//        }
//        var ss = document.getElementById('stylesheet'); // doesn't work because chrome and FF don't support onload event from link tag.
//        ss.addEventListener('load',styleSheet.styles_changed,false);
        return true;
//      } else return false;
    },
    styles: 'a.dotted{border-bottom:1px dashed;}\n'+
            '.CatChan_window select{float:none}', // patch for odd stylesheet, where select{float:right}
//    catalog_threads_in_page : function(doc){return doc.getElementsByClassName('mix');},
    catalog_posts_in_thread : function(doc){return doc.getElementsByClassName('replyContainer');},
    max_page : function(){return 10;},
    all_boards: ['/popular/','/all/','/mega/','/random/', '/culture/','/psy/'],
    make_url4: function(dbt){
      return (site2['lain'].all_boards.indexOf(dbt[1])!=-1 && dbt[3]!=='catalog_html')? undefined : site2['vichan'].make_url4.call(this,dbt);
//      return (site2['lain'].all_boards.indexOf(dbt[1])!=-1 && dbt[3]!=='catalog_html')? undefined : this.__proto__.make_url4.call(this,dbt); // cause error from lainjp
    },

    catalog_native_prep: function(pn_filter,pn_tb,pn_hi){
        if (this.nickname==='lain') {
          styleSheet.add_to_watch(document.getElementById('stylesheet'));
          styleSheet.add_to_watch(document.getElementById('code_stylesheet'));
        }
//      var node_ref = document.getElementsByClassName('catalog_search')[0].nextSibling;  // FF doesn't work.
      var node_ref = (site.whereami==='catalog')? document.getElementsByClassName('threads')[0]
                                                : document.getElementsByName('postcontrols')[0];
      site2['DEFAULT'].catalog_native_prep_sel(pn_filter,pn_tb,site.whereami==='catalog'? document.getElementById('sort_by') : null);
      if (site.whereami==='catalog') {
        document.getElementById('image_size').addEventListener('change', site2['lain'].catalog_native_size_changed, false);
        pn_tb.style.display='inline';
        pn_tb.childNodes[3].style.display='inline';
//        var pn_tb_new = document.createElement('span'); // rip from div to span
//        while (pn_tb.firstChild) pn_tb_new.appendChild(pn_tb.firstChild);
//        pn_tb = pn_tb_new;
//        var pn3 = pn_tb.removeChild(pn_tb.childNodes[3]);
//        while (pn3.firstChild) pn_tb.appendChild(pn3.firstChild); // rip from div to span
////        pn_tb.appendChild(pn_tb.removeChild(pn_tb.childNodes[3]).firstChild);
        if (this.nickname==='lain') {
          document.getElementsByClassName('controls')[0].appendChild(pn_tb);
        } else {
          var threads0 = document.getElementsByClassName('threads')[0];
          threads0.parentNode.insertBefore(pn_tb,threads0);
        }
        node_ref.parentNode.insertBefore(pn_filter,node_ref);
      } else { // if (site.whereami==='page') {
        var pctrls = document.getElementsByName('postcontrols')[0];
//        for (var i=pctrls.childNodes.length-1;i>=0;i--) if (pctrls.childNodes[i].tagName==='HR') pctrls.removeChild(pctrls.childNodes[i]);
//        pctrls.parentNode.insertBefore(document.createElement('hr'),pctrls.nextSibling);
        if (this.nickname==='lain') {
          var blotter = document.getElementsByClassName('blotter')[0];
          var pn = document.createElement('div');
          pn.setAttribute('style','float:left');
          pn.appendChild(blotter.nextSibling.nextSibling);
          pn.appendChild(blotter.nextSibling.nextSibling);
          if (site.whereami==='page') pn.appendChild(document.createTextNode(' | '));
          blotter.parentNode.insertBefore(pn,blotter.nextSibling.nextSibling);
          pn.parentNode.insertBefore(pn_filter, pn.nextSibling);
          pn.parentNode.insertBefore(pn_tb, pn.nextSibling);
        } else {
          pctrls.parentNode.insertBefore(pn_filter,pctrls);
          pctrls.parentNode.insertBefore(pn_tb,pctrls);
        }
      }
//      node_ref.parentNode.insertBefore(pn_hi,node_ref);
//      if (site.whereami==='catalog') node_ref.previousElementSibling.appendChild(pn_tb);
//      if (site.whereami==='catalog') node_ref.previousSibling.appendChild(pn_tb);
//      else node_ref.parentNode.insertBefore(pn_tb,node_ref);
//      node_ref.parentNode.insertBefore(pn_filter,node_ref);
//      pn_tb.childNodes[0].setAttribute('style',pn_tb.childNodes[0].getAttribute('style')+';display:none');
//      pn_tb.childNodes[1].setAttribute('style',pn_tb.childNodes[1].getAttribute('style')+';display:none');
////////      return site2['lain'].catalog_from_native(date,document,site.board,site.whereami+'_html');
    },
////    catalog_get_native_area: function(){
////      if (site.whereami==='catalog') return document.getElementById('Grid');
////      else {
////        var pc = document.getElementsByName('postcontrols')[0];
////        return pc.insertBefore(document.createElement('div'),pc.firstChild);
////      }
////    },

////////    catalog_from_native : function(date,doc,board,type) { // working code.
////////      return site2[this.nickname].wrap_to_parse.get(doc, this.nickname, board, type);
////////////      var parse_obj = {domain:this.nickname, board:board, parse_funcs:site2[this.nickname].parse_funcs[type], __proto__:site4.parse_funcs_on_demand};
////////////      var ths = {pn:doc, __proto__:parse_obj};
////////////      return ths.ths;
////////    },
//    catalog_get_native_area: function(){return document.getElementById('Grid');},
    catalog_native_size: (document.getElementById('image_size'))? document.getElementById('image_size').value : 'small',
    catalog_native_size_changed: function(){site2[site.nickname].catalog_native_size = this.value;},
    parse_funcs : { // lainchan
      'catalog_html' : {
        sticky: function(th){return th.footer.textContent.indexOf('(sticky)')!=-1;},
//        sticky: function(th){return false;}, // patch
        ths: function(doc){
          var ths = this.__proto__.ths(doc);
          if (site2['lain'].all_boards.indexOf(site.board)!=-1)
            for (var i=ths.length-1;i>=0;i--) ths[i].board = '/' + ths[i].pn.getAttribute('data-board') + '/';
          for (var i=0;i<ths.length;i++) ths[i].page = Math.floor(i/10) + '.' + i%10;
          return ths;
        },
      },
      'page_html':{
//        tn_as: function (th){
//          var as = [];
////          var files = th.pn.getElementsByClassName('op')[0].getElementsByClassName('file');
//          var files = th.pn.getElementsByClassName('files')[0].getElementsByClassName('file');
//          for (var i=0;i<files.length;i++) {
//            var as_tmp = files[i].getElementsByTagName('a');
//            if (as_tmp) for (var j=0;j<as_tmp.length;j++) if (as_tmp[j].getElementsByTagName('img')[0]) as[as.length] = as_tmp[j];
//          }
//          return as;
//        },
        ths: function(doc){
//          var ths = site2['vichan'].parse_funcs.page_html.ths(doc);
          var ths = this.__proto__.ths(doc);
          if (site2['lain'].all_boards.indexOf(site.board)!=-1) {
            for (var i=ths.length-1;i>=0;i--) {
              var key = site.nickname + '/' + ths[i].pn.getAttribute('data-board') + '/' + ths[i].no;
              if (cataLog.threads[key]) {ths.splice(i,1);continue;}
              ths[i].board = '/' + ths[i].pn.getAttribute('data-board') + '/';
            }
//            for (var i=0;i<ths.length;i++) {
////            for (var i=ths.length-1;i>=0;i--) {
//              var prev = ths[i].pn.previousSibling;
//              if (!prev || prev.tagName!=='H2') {ths.splice(i,ths.length-i);break;}
////              if (!prev || prev.tagName!=='H2') {ths.splice(i,1);continue;}
////              if (!prev || !prev.getElementsByTagName('a')[0]) {ths.splice(i,1);continue;}
//              ths[i].board = prev.getElementsByTagName('a')[0].textContent;
//              prev.classList.add(pref.cpfx+'hs');
////              prev.setAttribute('style','display:none');
////              ths[i].pn.insertBefore(prev,ths[i].pn.firstChild);
//            }
            if (ths.length===0) cataLog.show_catalog();
          }
          return ths;
        },
      },
      'catalog_json' : {
        time_unit: 1000,
      },
      'post_html' : {
        img2src: function(img){
          var src = img.parentNode.href;
          if (src.search(/.webm/)!=-1) src = src.replace(/.*player.php\?v=/,'').replace(/&t=.*/,'');
//              https://lainchan.org/player.php?v=/test/src/1449566174628.webm&t=1449422283293.webm&loop=1
          return src;
        }
      },

    },
    colorID: function(pn) {
      var id = pn.getElementsByClassName('poster_id')[0];
      if (id && id.style && !id.style.backgroundColor) {
        site2['vichan'].colorID(pn);
        var prev = id.previousSibling;
        id.textContent = 'ID: '+id.textContent;
        if (prev.textContent===' ID: ') prev.textContent = '';
      }
    },
    catalog_json2html3_thumbnail: function(obj, board) {
      return (obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png' || obj.ext==='.pdf')? 'https://' + this.domain_url + board + 'thumb/' + obj.tim + '.png'
           : (obj.ext==='.epub')? 'https://' + this.domain_url + '/static/lain_epub.jpg' : '';
    },
////////    catalog_json2html3 : function(obj,board,thumb_url) {
////////      var th = document.createElement('div');
////////      th.setAttribute('class','mix');
////////      th.setAttribute('style','display: inline-block;');
//////////      if (obj.ext==='.gif' || obj.ext==='.png') obj.ext='.jpg';
////////      if (obj.ext==='.gif' || obj.ext==='.jpeg') obj.ext='.jpg';
////////      th.innerHTML = '<div class="thread grid-li grid-size-' + this.catalog_native_size + '">' +
////////                     '<a href="' + thumb_url + '">' +
////////                       '<img src="' + thumb_url +
////////                       '" id="img-' + obj.no +
////////                       '" data-subject="' + obj.sub +
////////                       '" data-name="' + obj.name +
////////                       '" data-muhdifference="" data-last-reply="" data-last-subject="" data-last-name="" data-last-difference=""' +
////////                       'class="' + board + ' thread-image" title="' + new Date(obj.last_modified*1000).toLocaleString() + ' '+obj.ext + '"></a>' +
////////                     '<div class="replies"><strong>R: ' + (obj.nof_posts-1) +' / I: ' + obj.nof_files +
//////////                     ((pref.catalog_footer_show_board_name)? ' '+board : '')
////////                     '</strong>' + 
////////                     ((obj.sub)? '<p class="intro"><span class="subject">' + obj.sub + '</span></p>' : '<br>') +
////////                     obj.com + '</div></div>';
////////      return th;
////////    },
    catalog_from_json3 : function(obj,board) {
      return site2[this.nickname].wrap_to_parse.get(obj, this.nickname, board, 'catalog_json');
////      var parse_obj = {domain:this.nickname, board:board, parse_funcs:this.parse_funcs['catalog_json'], __proto__:site4.parse_funcs_on_demand};
////      var ths = {obj:obj, __proto__:parse_obj};
////      return ths.ths;
    },
    favicon : {
      __proto__: site2['DEFAULT'].favicon,
      none: '/favicon.png',
      reply: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH3wUcDCkWRBuw0wAAIABJREFUeNq8vXewZdl13vfb+8Sb08upX+ecZzABGASRAAkBAkGAGJCURMlVUsk0JZctV0muUpU4Fs2SXKLKLtM2y4ogZZkiAAJiFAGYCDMYzCBM6DDd09O5X84335P23v7j3Hf7dU/3YAYYale9evGec+5ee6/1rW99az9hjOG/5BBCSMACDKCMMUYIYcE+G65ZgEWpZBPZWSyKhD0fsImNJOOAEDFJEkMcI2VAsdgjikKk1GxamnFbs7SkALXj+hKQgNh53/ueS/S/lP3PGo44sOZCN4cTjWHiYSQ5FAphN7CyS2TkOvV6CJ+J4Aumfw9t3ubEir9oA/Tf2PYH/Qkwg58NH83QXJlA9WYw0TgqHkJSllAEshY4loUlQCrdnzxFgkRriA30gK7WdJCyhbDrCLGOtFeIxBK0631jwDPPwDPPmPsm+t7nSb+GajVPMz6J7hy30bNSMOxaFABHgNGGIFJsxZoFLPcNnOz3COpzqeGQ29f9YYb4kQywY1K5b2LZedP7/g5jjBZCSAqTFYKtXcThhMRMgd7lWcxIwdDs7l3HC7nceBJHKKUQGKQAYwzGwD32FAJhWdi2jRAyqjcb83fm597Qmi0MawoWVcKCwtrEcTewSitUJlfN4g+628/3zDOIZ5655/kKOM5h4visK3mPZ3Fw3749j6ITW27byoBGgLTYqjfmlte3LkQJL2ns58F/BdrrO9/zu2aA+yf0LYbZaZi+G/AhU8NJJon1QVuqU5bgQMa2pmZ3TR9xLFxbChxb0j11kB987p+8rWeqvHyZiS9/ndqffxfdCUiUQRkDWDTa7bWlpZVriWY1MdxRcE1r3sB1F5G5Otlanf/2r7XNr/6q7r+3Kpb1uFTqL2ccntg3M3m8mPPs5859/qH3/8CZn6cbxFy5dvuNIOF8ZKw/JON+nV5vub8beKtd8LYNcN/kp8uAD1mwKjkCXLqU9FfQfUY6YsOlAo6zD6Xe4wh9xhXsnpkaP1rKZ4ZsATKf4dnv/Pa76vqe/OivYC+toY1ACAvpuFy4ePHFKGE1NMzFSl7Hdi6TiCuU/S3qyobuB12pns454tTBvTP7l//h3+TWL/2Vt3W/p048zdJGvXFnafPFyDifB/dPoLN2v1f4kQywY1LTlX3kiM3iYpaeHiJUWVzXYAV1er0NINy2PIxmYWs3qMc9qR51LY4c3L/3vULHwndt3mplvZvjA6c/S73VIYhipHTIF4pcvfLGt2OYDxVXEmFdRXp5N+n+5Egl+9S+6bHRb75699meOvVZokihDSAMAvA9h2df+Y/33OeDZz7L4lqjd3tp/blQWf83qK8Dje25e5ARfqgB7pt8CQyDvQfMbomaBgpINJoNATeV614him4BGeCkLfmQZ/H+3VPjj2U9J6MfO/a23cu7Nc4c/DStXg9tNBiDJQSuZWE7DkGcrLi5UrPVDbwk7I5XM5Zz7vofD177+PGnSTT4GR/P8zEYgiAk6PVwJHz34hfuNfZ7f4k3Ll5f22iHX4u0/X/ywfd9j29+Uz9sJ7ylAXZAMwAPtzBL1HrSlbzfluydHB/d33rmvx5FCPR/98+udLrB7VDznUTa38CYUQf1Cd/ikf2zU4cKGZdvvfp5/kuPw7s/QaMTIi0bKQVaK7RKcC0oZlzyWZ9EGzYaHYIooZTPUinl0UYTRppqrcLM9ASjw1Vy2SwGaLa73J5b5NateeKgyytXvnSfO/oMr75+82ZH8a+0V/339DYXHwZPH2qA7cnvB9AMjnOCOP7LGYsPTYxUHrWF8V1HIqUEY0iShMRYzK1uvtaNOYcQwznfeSLnyPz0WI2XrvzBjzSBu37njxn++vdxVzaxm60B8EpKeRqPHOG1f/LLb/n6sdpPEiaGYiGHlDJ9Tq0QxlDMugyXszz3yn9k12//IRO/8R/ohjGtbo8gShiqVfngex/h0dNHqBTzCCHQCKIoZnVji3OvXeXlVy8i4pDvvnbvTji++2O8MbfyZyHWb6LUV3bA3XuC8gMNcK/bmfVg4bhE/XXf0h+fGC7tvrb05/f8fXZxle7ECAC14lO0Iw0Ysq5DIZtFSMNQOcdLl37/LSd69n/7DyhtUMqgjUmh5zbSFf3PZoBABxhYIpASLClJxmt8509/c4CQqp/4H+jGmlwmg+vYCCEwBjq9gFa7QzHncmvhP9/zLKcPfoq1epvRkWE+8N7TnDl+iEq5lF4/UQghyGRcNraa/PlzP+D5F1+iknV49pXfvQed6Sf/+q1WzL/Tuvy/Q307FtwDTd9kgDclTq57lCj5W77QP3tw99jUTv94/zi062PcWdlCIyjksuRzWbQytDodaiWfq7f/5J6/P/qPf4vSl79JEMVESoOQOK6N5/lkfY+M7+P5Hq7rYlsSKQUCgTYGpTRRHNELQnq9gF4vJAgC4jgCo/Fdm5zv0g0iljbbBJEi6/t4noOU6c6tN9ooFXNwZpjndkze9jh7+OcIlWHvnhmeeOQkB/ZO49g2qm8Ez3NZ3dji//3SV1hbXuKlS1+85/XH9nyMa/MrXwjI/Avi3svbCeFOA9hvjeX9KRlFf8WT+q/MfOpDU+e+8M9Tvzr7caIkAQSuY+HYFq1OwHy9jeu5lAsFstkMBuh0AixLUs5n+pP+f+H87lfoRQnCcRGlAtMT4wwNVRmqlKiUChTyOTIZD891sS0bKSVaK5RKkUhqBI1SiihJCMPUEK1Wh81Gg7WNBmvrm9TrDXrdEHSCNJooComTGK3TRRclipxnc+e//6sPnICXLn+RJ098lmtXb7K6tsUjp4/y5CPHKJfy9IKYOEmolkscPrCHjfV13nfys3z73O8NXp/3XRzJRJAkh2D8Aiwl9+dR9gNX/zPPwOc+57K4+H5b64/vnhiavfSFf87Yv/4ypV//NxRKeUZqZXIZn6XVTW7Nr7K22UQjKBazeK6HVpoojlEqwreh1ws4ffBnQdoUKjUOjY8wOlKlWi6Rz2fJ+B62bSGRqe/ThjCK0LYh63uUSwVcz0FpTRLru/7HpO5KaU2cJPR6Ac12l/XNJiur66ysrrO0ssri0hqNRhvLlhSyfroLhKBcyPLiL338oavwO+d/jzN/81dpf/scL3zvFdqdDk8+coLJ8WEEgkQpZibHyGazdBpb97zWqxSYmZo6fPn28rRmyerH04cbYGCdZ54RuO5+mUQ/OVTOnrh0+88ofO81yr/+b5mcGOXk4VlmxoYA+MZ3L7KwvEG5kCXRKWUQRimN0At6GJVQLmaxPZ9SucT05AT7dk9Tq5RSSBeGNFtt5uaXabQ6tDo9giAkUSl94zoOxXyW8ZEhdu+aYGykNvDjYPqxQCCEwLIkxUKOSqnArskxonCWjXqTWwtLvHHtDjdvzbO1tYUjoVzI3rNa32q8/Ln/qQ9nf5bvfv883W7IU4+fYs+uCRzbplTI4fs+zfq97vxbz/8Ojxz/+SpJUoKytc1HCSHEdiC2HwA5DUNDOdY3P5ixOTE8PpxdBMY+/Q/Yv3+GR0/uZ2q0htaay9fnmV9ep5z3WXjh37H/g3+HeruHSkKSJAGjKBTyzMxMcfjAbvbvnWZiZIggjHnp/Ou8fv026xt1ut0eYRSRJAqlU84H0U+2EUgBnuty5OAefuJ9Z9kzO4lBoJW6JzlXSqM0JEYjhEBIyVC1RKVSZN/sNLfuLPHyhde5eu0mnSB6x4js5Stf5skTn+X8xct0ej0+8sHHOLR3Fznfw3Fs9AMApdEaKXG1NmJACO4g/ew3rX4QtFrDjtSP7pudOnXu4u9zcv8nOTf3J3z6l/8pU2NDCCFZXt3k++evkUQR5658GYAL177M+87+AisrW/SMYGpyipPHDnFw7wyjIzWGqmXqjRbf+cEFnn3xFbrdXgoplQJjEFJgWVY6eYLUECY1RKvT5fIbNxiulZiZGsNzXSKtuTdPNBi9jfU0CLCExLEsKqUC2QMetWqR8dEhXjl3iWP7fob5z/8z6mcOv20jfOf87/Hek5/l1q07fO2bYFsWtT5CehCitKQEiKDxQLx/vwE0kCHhkGOxR40WxXaw+quf+PuMnzqA59gsrW3xyqWbrK3XKU+P3HPBsBuSyeU4MD3JyaMH2L9nhmq5iGNbOLbNwtIa33/1EiurGxRyWXzfTR9cpEynQNxd/UJgSYkUknAtotXp0ukF21s2hWri7rpJ2VJ2fG/QGHQftfiuy67JMQq5HBnf4/svX4Cn/0fq195ZjvL8ud/jyRNPMzc3zzee/wH7ds/Q6XRxLeteauL008xtNOYTLTdAKyGE+AyILwyomruc+D2wHhUe9ixqF7/9O6kVLcnYSJV8JkM3CLh+Z4k3bi5QyLq88Oy/upu2H/sMMRaHDu3jg+99lEdOHGakVkFKgVIKlSha7Q5b9Sa+56WB17KwLDmYaCEElpBYloUlJVprOr2ARClGhqrMTk9gWRZaax4Aod/00Yd9aK2JlUJrQ61S5LEzR3niPScpViqc3P+z79gdfef85yl4Dlev3uQbz7/E+sYWuYx7z9+0uwEr6xvXseybMJWQTv49idg9BjDGGDKZjJTMjvzMhw8NtpFjk8/62LbkztI6r1+fJ4kivrcj+3v82NN0Yjh0aD9PPXaag3tnyGR8lFbEcUKcKMDgeS7ZjI8l+9lU39WkHwbTT8RUogmDiFa7S6vdoVYr8+ipIxw9uBspBHrgft6Sx0IIgZSyH7gNcZKQKEWpkOPsiUM8duY4luvx+LGn37ERnnv1d8nYgrm5BRqNJmT9e37fjRSR5haK12A+vqfg8wADpL/Q2rEFQ87iKgCF778G/a3d7PR4/foCSysblKdHBy9874nP0ksMBw/u4QNPnmH/nmls26IXBCSJRgg5yF5r5SKT48P0wohur4fWeucCQGlNGMd0ugHdXoAQgsnxYd77nhM8cfYYQ9VyP0smpUHefi1jkIAliaYXRJSLBR45eZgjh/fTjhIe/+m/+46N8MKFz1MrZNBa091qDX7+vhOf4c7CysVI8TJEt/vu3fxwA4ShADyr3gSg9ehRlFJs1dtcujrH7fkVPEvywrP/GgB/fpVGL2Zm1xQffv97mJ0aI4kVYRj133AaUDGQKEOtWubY4X2USwXCMKbbDQjCkF4QEoQRQRgSRTFCQKlU4PDB3fz0h57gw089yvTkKEmi7vP177ia1yflDEEYUykXee97TlIbqhHdXvqRrnn+2h9QzGfZanb54CNpUnftztJ6N+ZFnMyL8JnuwwpZ9wdhA2hlCHfaxijNhat3aLZ76Djm3I6gtfepv83o1Djvf+I0u/oTFMXqHh8M6WqNkwTf9zh1dD/1RpPvv3qZzc0GJkk5H8uS+J5LuVRkanyEA3um2bt7itGhKq5jEycKbcwPdT1vzwgQJwmWJZkaH+HUsUM8/+IPePLE03zn/DtnbS9c+08c3fsz3F5c5/i+n+H1ZnBZW+6fUc5fYu0L5mF09IOoiNjAWtzqBYAPIKZHeP3ybVzXofjJD9zj9+NcjrMnj3Bk3yyJUkRxghDpyr//TWut0VpTLRX4wBNnmBgdZmFplW43wGDIZNKMd7haZWS4wlClSDaTAWMI4xilTerP3x2xAFIIVKKwbZtjh/Zy5dpNFufmf+Rrek+dZvOPvsVavbMRG/nHqOh5s7raTlUfD1ZK2PcxEYJSqaPb3Qt35hevnZz96WPnbv0Z63/9Y4R//39lpFLg1f/jHw5eUO+GvOfsIU4c3ottW3S6wWCLP2jIPqoxRjAyVGWoWqbT6dELQoxJA3Qu6+N7LkIIkiQhCEO0Tle99Q58/tsZUkq0MSSJYmy4yvTkGKvLK5Rfukz97OF3fL2XP/cMx/d+nHbYirHsG6ioLn7IdpVvoiIajRZKvNBLuLzebDcBKr/+bxiuFnl1R+HhsWNPU66UOXl0P7VKkV4QvsntPGzlGZNWlZJEkc34DNfKDA9VyOezgKDbC2m1u/SCqA8178LJ7Y93a2xfz3VspsdHKRQKHPhr/+hHvt6F639MwTVIlZzFK07fF3Pf0gDmriApuqWwvrjVCl4cH/pLtDoBI7XSfRAr4ciBvUyMDQMC1V+lDzKA2fFGd75xrfXAbcVxQhJvM556O9e65/l3GkDv+PrHNYpBY4xmqFamVCwQxPGPZdS9MxNjrq0/TNg5DfvdPgISD9oNDzIAQA/UN7sJf+plCpuOFHz7pf9wV3Fw4rN4mQyH9u8il/WJE/W2Jl9rjVIabczdYopI6QfHtvE9h4zvkcv45HMZshmfTMYnm/HwXRfXsbEtC9nnKQbX6ydlP6oRjAGlNYVchnw+i9I/3q567tX/SK2Q2etI9SGcpcPwzEPdgr0zCRPb1GJajtjCerXuea4q5iQ3d0ZppRmZGGJ8tIZlWURRnE7KjqGNwehUyialHGS80pL3ZagGpQ2J0ug4Xf3a3F0PabBMM2XLkti2hZBO3whgjEbptDaQfjZ9llS8rTxh8Cza4Lkuvu/96Bh3x5idHC3X27cejVXvFXjmSl8tIvou2LwVHZ3ePXNpRPbUARW2ve9fu1uyO/Er/xSEYHpihHw225+EFJ3cuwpFn04QSGkhAIUhimKiKCaM4hT7ByFBEBFGIVEcp4yo6sNN0kl0bAvXdfE8F99zyfgeGc/D8/q7wrawbAvLWOi+UbcR186M+IcpyaSUWJb9rhjg+Qtf5ODMR4/fWFg7G+M+C09eg2/q+7Nh+yHyExclHnVtzhYybnHnhXPPvgSux8ToELZtDdzJ9sRvv1lLWlhWP+CGEc12h61Gk816k3qjRavVodPp0usFRGFEHCeoJEH3XdTd60ksS2DbNrbj4HrpKs3nshQKOcqlItVKkUopTyGfw/c9HEeitUQlehBPdj7fg7cCGPr3NeJdCfDFnOd6DkfjxDyG+uad1LWnseBN9YB7R74ok/aTlYJ36PLX/+W9SYIy5PM+tUopXdXaDPy8lALbsrEsSRTHbG62WV3fYmVtk7W1TTY267SaLbrtHnEYksRJyun3iXRhdqSLYnsizHZZACPu1YO6vksmm6VYylOtlhgaqjA8XGOkWqZczJPxPcAiSRSJUgNh0/1GMP2fqUQRx/H2HX/s8f3L/4mDuz56/Nr82mOKzP8H21TufTFgx+rXzM56zC8fdOHk+Eht19LU6Jt8u+/7qUbGpDobKSS2nfr2OEnYqHdYWF7l5u1F5uaWWV/doNPuYJIEaQyOSN2KLyW2bWP1yTJJSlsIIfpzb/rlgLtIR2lDojVJkhA1QrqbTVYXQDgWmXyWWq3K5MQoM9NjTI2NUKkU8Fw3LZj0g/Y2tJVS9KlwgSUE3SCk2w0QvHswt+B7RVdyuCf1EZLxLVjq7YwF9n0aIS0KhSI6edK3mfBGqg/YqQLXcXEdZ1AStPqBtdXucmd+mfOvX+fqjds0NupYcYINZC2J67qp1txKA+o2/Sz63H569VRca7aLGdsxRexgTHf4+UQpYqWIlCKut1iot1mcW+LylQKTk2Ps3zvN3tkphmtlHNtJF5HSfaOm15SCvkqiRbPVwrXevYTP3jWOf3t+pBfGj5FtXDQd0+33K9zjggTQb2RwRy2RPHZg/55jL3z7cw90lrJfsRJS4NoOvSBgYWmNK9fvcPmNG7x+9TZhu0vVd6nksuQyHq7jpFWjvqzEGI0x3PX3fYKtF8d0wgjXsill/HvQsdmBXNIdZ+P3vYlWmjhO6IURrSBkc7FLY32Lxfklbtyc58C+WfbMTjJULeG6DnGSoiYwSCTGwPLaBs1mi5zvvWsGePGrv8Vjx54+8sqlG49E3e4fCiGW73o9gX1PLZhqARqHMzb7rEr+IWjBkKgU9zu2xWa9zRvXb3P56g1u3J7n+q0FVBCzq1ZmolxM+RZtSLQhVkl/lfcL6vT9ej9+GGPYaHfZ6vYYzmcpZz26UYLSCs+2cSxrkITpHRh+Gz3bjk3JcyiTI04Smt0eGwvLLC+ucvPmPEeO7OP4kX3MTI6Ry/gIAUpphBS0Ol3uLKzQ7fY4d+X331XKw7UFjsXuyFj7GBq6zsrKgB21t6GnMUYLrzghI/XYUCk/9sJz//ahWUsURYRRTLPd5Ts/OM+F197AtQzD5TwbuRz5nGCkkMP0y5lvgoIDt5JO/rYLaocha+0OnSimlPVohxFL9RaBShjO56jlsjhS9vX/g3rOIHKn0JNBebOcz1PKZekGEZtrWzz7re9x9dodnnjsBGeOH6RUyGM5NmC4PbfE4uIyst9M826O5179PcbL7xvutYJTutF4xRhzUwghjTHa3t7ZQiCwexOOxenJ8ZHhWw+1pqQXBJy7dJX1zTqLC8vsmaySy3q8cukmPlDJ+DiWJFb6Hu5e9LkF06/3bvNClhCEScJSo43AkHNtmr2IMFYkiaIXx6y2OjiWxVAu09f37UAw5q5q8a47S9UUQkqyGQ/HtmiHEfXlVb76tee5PbfMe99zguOH9tLp9Xjpwus0Gw2iz3zkL0QkPDM5Nr5x5daxKFAjwM2+yxd2H5lpqrUCm8le12X6+bfgw1+8+EWOH/gUX//2Dxgq5zm8e5Sx0Spv3FpidXmTqm3j2Vbfv6YB7i6CShGIFLK/Yg2WSBnJzU6XtXaHopcGynYQ0EOQsSySKGErism5DtVsBoQYtODEKiFSiozjDMRW27/UWtOLYywpybgONdfF6wVstbtcPHeJXrfLRr2JMYabt+bxLYtX/+f/5i/EAK1PfQjnf/l3u5XSU0KIl/oyRTFwQURM25IjEyNDM1fe4kL7/sW/p9MNyEnJ4b1jnDw4y+Ubi1y4dBNLafJFP8XU2vTpiXQ2gjhVJbt2ioIGnXJSsNUNWKg3MUrjijRIu1KQdV1KGZ8hIdjo9kiMJkoUrmNjSK8fKUUrCLGlxN+u/fapCIyhE0YDlJFxHPK+R8Z12Gi3uXXtFitrmxjLIul133Xfv3Nc+rVfYfw3f3ei1whmoVCG1gYg7cEM9Tq7bMGBYv6+0v59w/+t38evFHjyzCGOHpim0e7x2pXbNDeaTJTyqZKhr99UxiBMClXrvV6qGrYydzUzVup6mkFINuNzcGaUSsZPd48QVApZhqtFSsUCF28v8sbNRXpRhGvbA84kUZpuFJPzXHzHGWhrRN/NdaOYSCk8xyLrOilrKwXVfI5eHLO2tsFGL2C4VvwL71UYqpVHNtvLe0OpJ4nZ3A7CBrBR4bTjMj33G3//oRc4vu+TeBmPs8f3cWT/NI7jcP7KG8zNrZJ3bHKum/p8IwhVTJgoPNvCkK7EjOvg7MDYUgi22h0yOZ8Pnz3N40dmcW2LOFY4jk0h61MsZslVipS/e5H1rRb1tQal7N2y5LYBon4P6yDG9zsrw0TRiSLKsX9f/Jcp1PU9MIZuJ+D0oU/xyutf+gszgO/YWIJd6GQSuLjDAIWiTWtmcnz00Osfe+qBL370yM8RWhaH9k5z7MAMnudwa36N16/NoYKI0WJ+IIbCaFpBSKw1np0hiBOUNjiWhW1Zg9pBGMX0VMLpPbP8wkef5MDMaNrsoXTKfEqJ5dqIfI7puVVyWZ91tZUmaSKNIaFK6MVxKlPZAZa3A3yiNUGSxoltw6SxQ9GJYizLYqJcYqXZZmOjxamDP8urfaXfuz28SgEpGUMlY3BWwksq7Vp39KTtMG1LnAf7/f+HZi9i7+wExw7MUC7kaLZ6nL9ym8ZGk6LrkHGdPoMJYaJohxFaGxxpEcRJqqe3bO7mvLDV6VIs5nn0xH4OH5gmn8uQ8X0K+Sy5jI/tpH9va43blxnebdlIlQ1hoki2Wc+dGst+trxNYadFo7vsqDYp7G32InzHYaSQxxeSzc02Zw59+i/EAN9+4d8zOT520BZmjNHYxRgjARsdzNiC0Yel4Nnf+iIjQxWO7Z9hbKhCEMbMLW1w7cYCroFSxk/5tL4BOlFEpFO/KyQEcYIlBXb/+kIIEq1phRH7d0/w2LF9eLZNL4gIoogoTunqMIyJwggTxqh+rSDF/imEjbUmShLMTqVE/2ttDLFS5HM+tXIhzX7VDnq6v1BaQUisNDnPZbSQx0OwvtHgsR9BqPV2hmPJnNB6hNZiCSGQDA/bGDPlO85w1n/zBnj82NMYKTl5eJaJ0VRmuL7V4vK1ObqNDsU+J692KNW6UYQUgozjEClFO4oGRfV+vyadMMLL+pw5tpdDsxPoMMZsi7T6jlrKflFFShKdFl7S4GsGbkQDTr/1aMDri5TiiGJFrVJkcrSK7zupYmOHCDbRmkgpEqVR2lDIeIwWctjKsLRa532nfvFdN4DQClswQtwd5+xZWxLHNlqPzO7adezbF770JsjZDmN2z4yzf3YCz3Ho9AJuL65y9fo8Rcch6zn9mkBKFStjiJTCkRLHkrTDiFYQIZCpHFGmrqAdhOzZNcahPZNkMh5xkgwC886sOUWymiiKieMEQZoJK5O6Ht/3qJUKiP6k75TAREoxNlRmcqSS6opUcu/vE0WsEpTZPoQC8r7HaLGAiRULK5uc+Zu/+q4awLYEUlBDh+O89JIlqSeOJalh1JuWf+5ffolszufk4VmynouUgsX1Oleuz5N0Q8rZDLa0+0GVfn1AkyiN3V/t7SAkVgopGdRyY6VQEvZNj+JZFhsbTYSUO4sBb1Iu9IKAMEqQQtANI5rdAC0Fo8MVJoerg8rctsJaKUOsNVMjVcZqFSxLDmIAwpCYVKyb7BD5amMwiNQIhSxhL6D9599/d12QbeE5dhXFCGBJirIsoaz7q2N7PPH+v40ysGtyhMmRKkJApxdy7eYiC/Nr1HJZPMfp11LEwLUkOq1oSZkGugTI53zsvnTbmNR1KAytXsBWo0MYpdmqMHdh4o6iLVppWp2AMIxwHQtlDCvNNjGaSilHMedji5TeTqWQaYwxAqbHagyV84MJ3h5K31XZ6T4vnTbypvRF0fMYymRot3oc2fOJdw8JOTZTU5MHpDQyPKzMAAAgAElEQVRDqQFiarakIMy9JFRzYYVSIc/+mQlcxwYBNxZWuXFrCRElVLIZDGYQGLcJn8EqM6CMIZfz2T05jOfaaKNJtKIVRrieS62UZ7RWopD1U5piB+m8vZqlgChKqDd79IKYjGuTcR3CRJHpi7i2Wl16cZpwaZ2KAZTWuL7LzHiNYtZH9btpTJ+0UwYyGY9SPjegxMWAH0xr3NVshqLj0Gh0OH343UFGG5/4ILZjFbXWJUolS6LDimWRvb9UmmjNxGiVybEaxkC7F3Lhym221htUc1msHYhmJ18fK4XBECfp5+FqkQMz40hL0u6FbHV7dOKEY/um+eknjnPqwDQ53yVJ9L0qhf6ESCFp93psNlrEUYJrO0ggl/E5dmCGiZEKa/UmrSCg0evRCgKaQUA7ishmfUZrJTw31ZUOsuckzbRr1RIjtTJK9Q0gxaA8KfpK6qFcloyQrK03eer0jx+UL/363yOJIixJkUbDkiRJSYC7cyLPHPoU5VKeqbEaxZxPrBRXbiyyMLeKawSlbAZMv0F6R3ZpjCHuI4owSTBCMDZUYdf4EJHWLDXarLW65HI+H3//aU7s34VrWyRKD0qRO59jW7+5Xm+xsdXqnx0k2Gh3GRku85HHj3Pm4AyOFPi2jTGGbhyz1u5S74UUC9m0I7LvZrb7BGKVZtrDlSLVYn5Hs8e996ZfOi37HrY2zK9svSu7QKkECXmoZuR/9Ut/46NTk5NTO3U9nTBifLjKxHAVA6zXm5y/fBMTxgwXcrh2iigavR6tMERpnR6qhEldQaIIYwVCMjpUYd/MGNmMh1IpNTE5WuHoninyGZ84VgPy7J7d1K8VKgF3VjbZarRxhSSIY5pRxPvOHOTU/pl0IRgYLeYZLuYZKuTxLAvfdzi6b4pSpUiUGNCp/7UtiyhRZLMeY0NlCrn0rIjtJHLnLtzWuGYdm5LrEPVCTh381I8vh9QaIchCOyd37dr1WKVSGd4pfBVCMlwrUy0XaHcDrt1aYnO9Tt6xERhWGk0W6y0WGi1aYYhGI/uZaRAnhElCGCcYYLhS4MDuScaGy4MgNDM+TLVUQPcFWfIhuh0hIFKaG/NrdLsBSZKw3u6we3aMDz15AmnbvHZ9gdV6i14YEUUJcZQQRjHFvM8jh3dTzGVpdHq0u0FaWwhCNlsdCvkMUyMVPMfuo5+7G+B+CYslBTnXIW/bbGy1eOrMj+mK0uu7ZDM5mSvkp6//+i8Xt639xPv/NhnPpVrK47gW80vrvHblNkkvRCtNPQhoxhGBhFYco4xJGVDMAE+7rovGkJi0FWhycoR9sxMIWxIbw9RIFd9P/2abt7kfdkqZJm6dTsAbtxZZ22zQixW5Qo6//P4zHDu4i2YvYK3eAinoJAmNMGSj3SHWmunxIY7smcIGmu0OrX4+stnp0QxDJkerzIxWQetB9vwQ6RzKpDxWKeNhYsXGVvvHS8bSDxsTubYtrezKTz3Gnl8rpFBto0E241HMZ2g0O7z2xh1u31kmb9sEQCbnM1MrkgBXrs7jSQtbykGnesZ3GRqpsLJeJ9SKrO9RzOc4dWg3Xxl6hV4YM1It4br2jsM47tXoaG1wnJS0u3pniYvX51jaarJ7YoQPPXGcjz55kko2w6rZ4tCucXrBUdCGOE5odwMcx+LJ04cYHyqj4pSqnhyrYek0pmQqOc4c3k2tUiCMokFmfa9cMX2yuJ/XeJYkY9sUXYd2u8tjx5/muxc+/yNbQIAkxrKNMZG/uO4mffVzrBJKuSKuY3NncZ2rt5cQUlIZKlMbLjM9MczkWI2VjTpLc2u4QmIJQZCkjXj5rMfZI3t49ept2p0A2xI4wNnDuzl9eA+3FlcZqhRSgkzpQQJ1j/JBgCUtVupN/vz7F7mxuEZtqMSHnzrFz334MXZPDKPDmKFClp9+8iSPnthPN0nohTHtbo+s53Boaoyc59ALIk4dnCWb8VOI2u9DePL0IeaX1+kF0Q4eiR0QOP22FycopXBkqh0q+z6dZpt6o/Oj74ABHMPY7U577pFP/IPhcCpf3C4T5jIe2kCr08P3XU4c3cPeXWNMjtUYH6oQJ5q5xTV0kmB5HkLI1J8nimw+w9nDu4hVwrk35lBKg9HMTI/wkSeO89r1BSqlQiqG6p+Asg05tzvYtttT5xbXOPf6bcZrJT763lN8+sOPs296FJWkteJyPstQrUwsJT2lCJVCa40nJTkpkSqtnj12Yj9nj+/vM6kGS1oUCxluzq8QhHFaOxbi3hy8b4wgSQ2Qc9MikGdb5F2bThDx2LGn+e7Fd74LBuVsG20vLiy9XK/XHy34SXE7W3QdGylgenwoZRMrRQq5DK5tk/Fc2p0mm1stjNL9rpW7IikD7J4YIZfJ9Pt9JYkAxxiOzE7gWhZRHLO8usVouYBtS1S/8W47QElLDpr1zhyYZe9PjvLEyQNMDpfRKiHp09u2bRHFCUubDe6sbVFvdzHGUMr4TFRLTFSLZH23L7RKG8C3hde2EOkxObHqtz3dp4cTabN9rHQqn9xR+C94Hr1uj61m90dtSMAYEqSI7K9+9Wtfb62tTJUzzG7rfqy+9nKoUmJsqM+jqJQQC8OYVjeg1elh94vgWutUim4MRoBj2zx1+hATQ2VyWY/bc8tcvjbPa9fmWNtsYLRmuFLm1OHdvOfYHkbLRaI46ftGAVKCDTMzE/zcxBgzI1VyvksYhkRKYdkOriOZX9vihfPXeOXSDeqbDcIghcRGWBQqBY4f3MVTJ/ezd2oYKSVRmN5DCkHGdwmjmChJ0kUk7pPL9EUEWqcIz5EWRqRw1XdsfMuiE4Q8/tG/x4v/+TffcRAQECKSjn3j9p2Fkh127yZTfZm2TIsYYRT1uRIGarhYJfT6hfC0z0rv4OqhF0ZMjtcYrua5cmOBP/nWq/zBt15meXWLqu9Sdi0uJZpLb9wmVIaPfOBRcpUyaNU/qkCSkZJdYw7CcTBRTBRFGOnhZgFp0Wg1+Nr3LvONFy6gujFO0MVKYiwMa+2Aly5e51svX+b6nSU+8+H3cGzvNK5rkyQq7bQ3JoWucZIW83lzM5E2aUKpE0U3ivDdVLouhcC3LXphQnj9nTf1CSnR0MWy2jZCNRJDqHdYZ2dHSyojEYMaqzEpNxOEUX8HpIGqFyfoQWNGutVvzK/y+W+c4+uXlrm13OQDB2b56IlDuGGX23cWeOHmPH/67DmKu/fykb90CCuKQKeyFTPQi4JwwQW0UgjHIbAkL5y/wZ88+wrjToZf/Nj7Wb99k/rGRkqjGMOllU3+7I3bfP6b52iGmr/1ccmZw7NopQd0RLeXGiDnuINVv/MMY6U1iTF0k4SNbo+y8cl5LkYYPMvClZJWN3hHk3/ml/4Rie2gDC1sO5AkcktrutsV1YFL2XHi7s6zF7Qx/YRH4VgWUaxYaXao98L0oZXCsSUbq+s8f/4mLy/2EJkix0bLnB0qsDvvUXVtdlWLPDo1RmdllZdeuUS3G2K5HtJ2ENJCyNQIZvuIYimRto3l2HQ6PV743nmsVodDRR/ZaWIZRcZz8FybQsbn4HCZx6fHGK6NcH6px1fPzXFrtY7ju9hS0gsj2r0Q1a/WiR3hsX+8Id0wZHSozIceP87JE/toxHGaeBqTHj4iJWEYceQf/9bbNkD2pUs4ro/SbNGyEgnWVqJpbLeLSQlhFO/Q9fSz1P7W01oTRUl/JRmWGy0W600ilXL1SqXtQq/fXOTSUpNMZYSiDjhRyzPiSG7NL3L+ziJr3YDpWpmS0SzfnmNpeQW1rZbTCmE00hgkqbTF6FTDmWjD8sIyCzduMeJIhjzJzVu3uTC3zHO3Frm8VqcdRmQdh8MjFXYVs4yMTbHQtXj+9UXiTAk7m6XbC2i2Oqg+kBA7zqwQ/QjcDSJ2jQ/xyY++l89++ic4dnQPvUTRDWNcW6YyyURT/PI337YBgihmbW3jJsh1sJUEp6s0W8Jy2Pcbv41jWXSDMK0+DZjJgZ43TbhUQpIkNLsBbZWAnRa+LSEw2rDR7nFttUVbOYxWCuS6W4z7DraQnL+zyFdev8n55XW0EJRch7DVYWV9i2T7QAlzt+474IiMAcsi1Ial1U2iVoe8beFIwXqzxcvzy3z1jdu8urBGvRdiWZKS75KLOuyfGiVfG+HKYoO1EEy5QtsIGq0upt/8jbgLh7d3QKw14yMVTh3ezfvfc5QPP3EC1/foRXEqFujXMLpB+LYNEMYJm1ubcyCWYT2RYCdayvWtenNx9HN/iO85dHsRQRTvSJDMIDnRJm2O6AYh9TBiZLzGxHgNx7YGO+bWRpuNxMbLFfBMRNnE5GwLLQSrnR5X1jZZbLaJtcaWacEljJO0KUP0G2OEuPuxwzkoDL0wTieu38CXGMNGL2C906PeC+jFMaIvArCjgJJrMTI8hJEu1+dW6GUqtO0czV6EMHd3+qAfmbSwhBSMVksMZzxqrsOxPZOUSvn0aE2dUjCWkATh229rjRJNrFnHdhc4ckRJxr0EIeYWlpavBVFExnfpBSHtbpCynNtdJDswQhhFdMKQbCnLsUOzTI1UMcoMCui31hqE0qdcqWKiiJwtB4G8mvXZUysxXshhy1RaaLkOuUwG2b+NeHDiAkZjCUE2lwPLThs5jMFzbHaVixweqbGrUiDr2oOj7m0MJokZrtWo1Ua4cXuRduLS0B7NIMKWO/uR7x6VnyiFn0kZ06JrI6OIYtYn47uYvu5USoEt066g8suX314KICw0LGGbeS5dSiRLSzFSXo00S9tKNp0kNJrtQalwZwptjCGIYpCSg/um2TczRsZz+weapqr/1c0mWC7DIyPYdloz3sb4M6UCP7l/F49Oj5OxJR1lyBSLjI/UsNF9ZYR4cDupUvhSMDY6ilMq04oV3TAi77ocHR3iQ3umODM5Si2bTdumpNVnOg1Dw0MMjYywvlmn1+kwt7zJ6kaLrOOki4y7LbXGGIJtRcVIFd/3iBNNGCUkO+X2pIdLKaU59nf/2Q+d/KdO/BxBGG0qwxyl0gZgJJDgebcixfV2N6zHcYJnSZrtLq1uMKh8bd9UKY1AMD5S5fiBGfJZnzhK7lK4UhKHEba0GB8bpzY2zmYvohtEWEIwUshyenKEfUMV2t2AwHYZmZlhaHgIqXXKTj7goA8hBEalSdHw2AhT+/fTEBbzm5t4lmSimOPY+BB7h8rU8lkKvofRmmac4BeKjI+Nki8W6AYBd268waWLF1heXQdpDXZdX7RBL0qodyOGKyWGKkXsfgl0ebNBq9NF7qgVyL5Cz7R/eFbc7QVsNRpXtbbusLISAiLN0dvtOliXVjcarzXbHXIZhzBKz2jbyVcaY9BKUy3lOXFg1+D0xChOBrp/z3fJZlwEhpGRYQ4fO0HD9ri91aAbBGQcm7zrkCQxl5bXcUfGOXbqBNlcBqMeTg0PfqYUpYLPk+97Emt0gtdWt6h3utgSip5LznUo+h6uFKw2W3Qcn/HZWUaHh1O1dC/gW996jjvXX8WyOnSVIojSBDBVXCSst7u04gDHs1MxmW3R7IW8dmOBTruHbUmEFAOXtU3c/bDRCWNixTVs99a2hnj7n9soXP98rHl5YWW9blsSaXR6/qfW3D1MK/XzU6M1jh/chee56VFkJu001xjyuRzVahmtFbZlcfzkSfY++jh3lOTc4go31jZ4bX6ZF24vMm/lOPLE45w5fRRLJ4N+owd2KfYNY7TC1zGPP3qCMx/8S3QqY7y8sMrN1Q3Wmy2anS4bjSaXF5a5sNlm+uRZjp08hTCGer2O1nDx8g18Z4tHT2WoDgs2Oj16YUKzF7PZ65EpKfbtc4hUh26QSixvLq3z4rmr6FiRcex7GFxb9jVPbzEe/6lfZn2zsRYpLuLJmzvbVM0zz8Aznxu+Gt8KvrPW6HygG0TljO+BNiTKYFs2iUrQ2uD2OSLLSreuUunxkNoYEmPIF/OMDg+hVMLa2jqnTp3ks7/0N/hdBDde/QFrW920JdQvcPIjP8FPfewnmR4pIVp1BBIh3zqFRytMGDBUKvLJT34MbSTf+E9fpLVZZzNSFH2XVhQz11PI2QN88hd+kWNHj/Lyy6+yMHeHXRMjOBJ00GD3hODQPsmX/niLxWaMtA1jk5LTJ3IUcpKr1xVRolla3uAb373Ipat3KFoWvmMP+qO1AduSmPvOi3tTHfj2Eq0wmdNYl+nc/c8a2waQxtwMRD5/Keh1Xrx09Wbt1KG9k0E3pBcmjA5V6XRaqCS5R7Ww87PSBq2hUi6xZ3aSpfUOa2urSCk5e/o01UqVixfOc/vGDYS02HPwECdPHGKyoLG66cHi4od1hxqTblpjoLXBbG2YX/z5T7P/wAG+9+1nWb55g+WwR26szNkjx3jyAx/gxPHjWJbFlddfZ2V5gY/9/E9x7fYS//Z3ztFudfjkRybYaii++Kcr7B7P8POfGKZWEXzru21mx6ZxLJs/+MYP+NLXvoutFLm8N/A5WutUnec4bP7EYw997Pc99leZ32y2E8OruNZNEyVaICxAbfcHbJ9pfF1j/VEjUKe22sHk8uoa+UKBkeERiuUqnXaLMOhhtMECLNsenN+ptUEbKJWKHD28n/z8Kj0NWivyuTyHDx1kfGKCjc0thBAMVbIURBfRXcckCUJaOwCnfHAVw5hB44VJEqzeJtPFKqX3n2Xv3r2sra0TRSHZXJbJiQlmpiZxLIvzFy6wuTrHiSN7mD14kEs3FphbbHLtWp3xsQLve6TE8lpAN9DMLwfcmhfMLWQ5vjfLHz37Ei+++jqdeouRfKZfqzBYMqVlEjSu6/DKr/3Kw5OvepuVreaiUnyTYu62iO52udk7u/WNMR0hci9FJvjqlZtzZUuYA6VikeWVKnv2zmLZNu2mRRgGoDVaK4w2fXbPoISgWi0xPjVOdahKvREhoybGASMkpYxFabIGKoKwgWmvg4pT7kcY3tY/aBJ94a5lYeIA1VohnylxYraG2TsOwkJsy9h1gIoidGeVowfG2bd3mjAyvPD9S6i4ietZXLzSZXLY46efGuLbLzX402+t0e1Jsu4QOrrJ/NIqza02w/kMnm0P1HXaGCKl0YDvOg/3/R/5OzSCJA4SLuC6r5rNzcY9jdo7j6kRQgj27dtkcfEPu2H30PJmY6K8uJzPZH0q1QrDIzVsx6HdbBGHPYxO0qwxUfSURlmSkaEqGd9jtFZmtNjF6a2gkxZIpz8pCagIoQKE0f3Jv79k/Va1PO4GactKjzPo1DGdBkLaYLkYYfdJPBA6Zs9ohj3Tx0k0PPfciyzOXeLwPsnUSI21zYQ/+dYav/CxcfbP+nz3vGZuIWCsVKezuYVvCUaLWVzLSuFmXyURKU0vSbBsi/DsoYc+cu/qHe4srV0xUn6NKFrYPvCX/hly9zVqI7l2LaFavaLC8A9WNlujGW/tqUI+S+HGTfyMT6VcgoIk8X2SOMDeaNCLIrpJgvRchocq+JbEURqJhl4D0WsNXBVCYPqAW0h7RwH87f6LMvHmuJAkoPp9AtsldtEHeQIKnoP0Mrx84Qp/9pU/ZbK6zq6JIr1AcPVml6u3u8xOZjl9JM/pw0VWFxvkpMS3Zb+rRw5q1YJUrRGGEYFS5Io+L//2rz3E9/8idzq9TqB4Fcf5BmFYv/8csO0uSbPTFbG52SGb/fMgCIbnljfKGc857roupWLa6JDL5rAsC8/3cf1NsrksxUKWaqVILpfBMgYThhCGiH7L0l09hkBYNtgOCM3dyPtOjojp9xvrdPKFitMETvdlLgNvdpfFRUS0turcnlsg67YJui6bm5LmpoWrfF74fovRmsPosEu17OAoScZ1B3WBnf0NcaJoBhGJgGI+89CnXFtYY3WrfV4J9yuE4fyOlcbO42rud76pMbrdVfD+qEdYvDG3nPMcZ08um6FQKOBN+dj9wzryuSLHD+/H91wc12Wj0aSrFJ4UmDhBWHKAGtIVahBKYYQcuJAfDn8ejoqMUkildkgLxUB18P/3dmbLcV3XGf7OfPr06QlAYyImkqAocJBIkZREMVHZUmRbkWK5XOWKnRdw7vIAuWFSlSfIXa6cRFFKqoqjkktD5KgsUrLMQRwBkBgJNEDMU49nHnJxGiAJUwo1eVf1dddZa+211/7X2v8fN4f+I8dFlBXMQo5svo3xkQViz0GKDXQ5RVfGYHWjzqXrNRQtRpEFiLa7g80yM45x/QAviKi5LnXfx8jojH1B9D/z+GvcXt+a9yLOE4mf0GTM2g1vydxTNt0RnxR28sKRUiTdfLvu+53jd+ZfUWSp1zRNUoZBe3sRYshmM5x48hC9Xe2MT5cYvjXFE8cOU+jtQJDFJoq63XMVdiYTdsD3h0R+HN8/py7sUA/sjhEhUfAhDh+Y6nmA6gYhubUKRopsR5FcoYNaRcZAoCWXQpUFZEnA8jRGx2oIUoQhaMnzKpLSOooiHD/ADgM8BDYcF0kWyZsG0w+hvn/2B3/L0kbFtjwuRpL8WyJngYdobd6fgqL7hWXuE+MM8BkPZfnNuh+0jt2ZVzRd6zSazFSmaWKmU+iaQiGXwXI9hsdnuXb9Ft0tWQqmQdSw72X4bcIlQSSWxAcjf/fDAFF4kGn5IXzFgiCAJBPFCVAn3A+fCwKCJCLICrGs4Aki1WqDWqWBIcsUDY2cqSTT3HGMpgrU1xKsqbWQTFKEYYTrJ68pAyFGN1OkVAU5CDBUmen/+Kc/Mn7/v75DY2qela36zSDmNxjGNarVbaWJhzLnBl+kc9jcDS5BcMWXtNe3LFe8OjJxSlGUPlVVGRo6iGGk8P0QVVUZ3NvL9Owin3xyidZCjudPH8Mw0+C4zYHUxOix2Ew/uy51OyRZYqJUmtx2YoRoN+1YcxM1HQAJDB43+71IIqIkIUgifgzrdZvZyVnOnbvAxO0JDFVCURVsP2hKFcQ0XD8ZwtVUNEmi4frJsFkUE6sSLcU8XZ0tlGsOrudjqiIzDyF3LfzjvzB5d2XMCflvdP081WqZL5EylB9BeFgAXDPlXqjbUlhzQ+vC1ZE/U2Rlr6ZpHBjch6KqCSaezXD65BE+PHeJ9z74mEw2zbGjj2MaMnEYJodxnFQpO3kaHiD6i+KkWe6GMZKioMsSmiAkbcldgwM7Ix6yAs0XtqIQEwkCDgINx2Hp7jKXr43y0fmLXP78JpWNMoPF1kQTLIqQRIEgiKg20VpJFKg6LnU/IFIk8m1Z9g90M7i3mzAIOX9xFE2CqYdE//HBV5m6uzzT8MUPUI13cOr/78iE/Chn3cAAom2DHISjDvzKClg+f+Hay14QHhEFOHjwAKKUvJTs6+ni9InDXL4xxr+9/jbWT37ImWePk85kwHWJvaSbtY2eCvenF1HA80JGxu8wWVoklzUZ7Oth754ODE1NnNjsS+8AdKKYnDWKQixJ+H5AeWOL29MlPr9xi6uf32R6fJr11XXqDTuhSJPEHfiZ5vhh3XF2FJlSpk5Xdys9e9rp6y7S19VG3XY5d3GESrlKa0anfOLQAzZ6Yu/LTC6szdV93kfR/x2vPr1NyMGXqKk+igPi2Vk8oAwtDjgueIFP4P/+8k0nCKOTDcvh+LGj6HrC8zB0YB9+EDJ8e5r/+vX7zN1d5NTxIxzo30M6YyI2UU38AMKouTuSxjtxjCrJaKIIno/vuERBCGpibCQJQZZAlprgXIRr2SyublJaXGF+bpHpyRlGx6aYmpyhuraBFCQ9DkHXCIWkOxfHMWKT8bfueii6SkshQ19nG+3tBbo7CnS0FSgWspSrFldHphmbLJE3VD4dvidccejv/xlef5c7S+tTls8HyKk3eGJomCtXwm0mmm+kJ9y8Njez7lngLRlKLdA4AHwvJfOX+3qK/a/+6IWuY0cPkc3lkKVEeG1iZo4Ln49gux6HHtvPiSeH6NvXT76thZyZIqdrqLLCPQKgpEry/ACnORggyzJmOoUsy0mEhhENz6PiuFQbFlalRnV9g4XFZeZKCywsLLG4uMJUaYH5xTVyqkqbaeBFEWXbRVUUBloLyYsawPY8lmoNOnrbOTo0wEB3O7l0Cl1LLolb1QZ/uD7OlZuT6BIM37mnJPjc4Z+yVbfjmcW1204kvouk/ye+NbyN9d+b7/pmDhB2VUZCc+d0AXkk6bBM+AtTYehnr708eObZE3Q0tb6CIKRcrTExPc/U7F0sx6WlJU9/Xw/7+rrZ29NJe0cr6VwWSVOJmp0vRVUSoG9b+8Xz8V0fr9GgtllmaXWDu8trLK+uU9ksEzoWpiJQMHVyhkYYxgxP3+W9z26yVq6RT2koqozj+eiI9Bfy6IpMEEVsNWxqhPzohZMM7e9FFCTiOEKWRBqOx4Xr41wbmUKMQ0buM/6x/a8wt7xZrbn+qB+Lb6CY/4NbLZE8DOVhJefXcsAXrc5OIb2ygkgmo1Gzn5LF4Ie6wjMv/PlzZ04dG+LI0CC5bBY/CKlU66xtlFnf3GKzUsOyHHw/oGE5IAoUCjkKLXlkRUZTVQxDR9c14jim3rDY2KxSqdRxbQcpCsjoMm1Zg2IujaHIeJ6bEMPWLGq2hxPG2H5I1fYQZZFiPgNxxOTMAvPTd+nOZsikdOq2w6Zto7dm+KsXT9HVViCKEhbIjXKdS8NTjE2VkOOI65OJaMWpx37MWrnGerk2bfmciyTpPVT1Is88s8zHH2+nnYcqp36rDmiKEiSMxHSloHIQrOdViaeyOsWf/+ynLx4+uF/t7+3GMFKEYUi11mB9q0K5UqVas5hfWOHG7UnmFlbpbm/h+yeHyKYNbs8uMrO4jhcEWHbSxuxoybF/TzsHB7ro6cgjRVHi2EqdiuXihhCSlKWKpmOaBrlsIleVzxg0LJvfX7rJp+cu02mmKZgGq+Uq9TDk8PHHefHMMbKmQaVSZ3l1g5GJEndKS0SBiyGL+L5HpeFQsbz1rbp1xY/4NBL182RSN/i7rRpnH0AUHsn4j1wFffmYO2JCRixyR/sAAAURSURBVHpoFGZXPMG7umkHT/zqzV/PdBbSe/7mr3/+2kDfHtpac5jpFGY6RdBZxPV8Bnq7MAwdz7uG7zi0plROP7mfrUqZ335aIgwjDvZ1cPRADycO7+eJwT4KGYONWp2Z+VVmVsqsVC1iSSKbzdHekqeQz5Ix0xi6hqombCuqKqOoGplMJrkvRAmO5AYBRjbD0UMH6ejowHddNst1hm/fYWTsDr7nktYVrFDGCQR3frV80w24FkrS/5KRr1BxlthyHM4+AGbF8VeI6m/igF1Pnm75wDIBmxHGdN1z50orjVfffPvD0lNPHup/bH8fA71dtLXkSWkqmqqQNvRE+zGfYXhknI2ahZlNc/LoIKOTc4gx/OLlMzx5ZJBiWw7f8RibmOPS+CyrFRtVT9HZs4dCIUc+Y5I2UgkmJcs7fEEJzSVomkYul0VVtSaRR0AQg2noZIwUq6sbLC4uc2tsismpErZloWkqqKlocmbuou0zGoriZRTpJn5qgkq1vAtH+0qG317S2UTb8KumH2HXdktQmRMnRJaWIvBd4tgWRMJapSrMzEx7SErP2maZet1uEnPEKLJMIZ+ls9iKosjML29gux6dhSxD/V08ffQAr/zFM3T3dnJ3eYOPL47w2Y0pVmoumZYW9vb3MjjQQ09XkUIug6YqO3x1UVPTcJvIT1UUqrUGtyemwQ+QBKh5Ab4AEhFjE9MMj04wv7hOKMhYjjOxsVX+bHWz8qEdiR/HUvojUson7Ns3w9rdxs43fwPjf+0zYBfT+v09xPg+hyimSTYj8DgxP/BCThc7u0+1tbVme7s76O/roqu9SGshT0shSxAEXBuZ4NbtSY72d/Dy955m/2MDyJLAtevjfHDuc8bnVsm1FjgyNMhAb0K+ys4YfROA4552QRgmhBye6xGGEeN3Srzz/jnEho2pyKzaLg1iuooFHNezZ2dLY2HEchgzH0bMRkjjqNIYnrZKl2nxy186nD37tXL9d+GAR/rjk92CsVmnN4bngoiXvJijHXt6j6iaRrGtjZ6uDvp7u+jd046qKly+dovlpVVe+v6z/PgnLzE5foc33nyXxeUNHjswwJFDg7Tms0iSSBSGiTJeECZT2UEi8Oz5Po7j0bAsKtUaG5sVKtUaK+ubzC2uk1NUNFFg0/a4PXd3mDi6AZSQpBKCcIcgWEHPNVAzFaqnK3H8Vrhr13+jqP9WqqCvsv5BEMS30rTHOkdcnzNezPO6mT+up9IFRdVIm2naiy3093WhKCqj47Nk8zmee/Y4c6UFRoaH5wf7OxpPDe2rZrM5A9U8Uq7UaFgWVlPS3LIdLMuhYdlYjoNju9i2TaPewLJsfN9zFpeWrgYh44SsxqC5EXlflJcR5Y+QlDnU7AYnDlTi3/0uuD/Q7mvXxd+W4f+kDtj+mBMgO0X6w4CnIzjqBvS4Iflcodi2sra2JkjUNJklN0R1fXRZxBVF3D15caWnwHoUIK3VyUlaNpdtLbY7XqDOzM7UowgliBDiZlMsSl5SCeK9Qd9IFqlKApOixIjrsVF1UT1obzZKbgCNPy4s7qXV+DsylMyfaDU/wBcEYaZYZDnvcUEXGTB0is8/f+rxD37z3nAYYTs+diDgezFCHBJ3pqiLURSNLJO2XTJ+gLan28x2t/WvZ+JImJiaKcUhjqRSFwUqcUBDFXEVkcAVCUWBKAtYIpFdxi8cwr91awckmyIRq/Ob59j2JSpq/oi/4wj9P7/IIgF0b7ZzAAAAAElFTkSuQmCC',
      reply_to_me: 'png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABmJLR0QAAAD/AMzVCQEJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH3wUcDC4pvTwLKQAAIABJREFUeNq8vWeQZcd15/nLvPb5euV9V3vv0AAbAAnQSKQIkSIpUgQpaobSRszErlYzE7H6MLMRilhhR6s1MZrYjdBuKFYzo6UsRSNSokSKIpckLOHYDaC70QZtq8v7ev66zNwP99Xr6kY3CKCbeyMq6pV59+bLk3nM//zPSWGM4f/PSwghAQswgDLGGCGEBTtsuGQBFqWSTWRnsSgStnzAJjaSjANCxCRJDHGMlAHFYosoCpFSs2pphmzN3JwC1Kb7S0ACYvNzbxmXaL+U7e8a9jmw5EIzhxMNYuI+JDkUCmFXsLJzZOQy6+shfDaCr5n2M7R5mxMrftYCaH+wjS/aE2A6v+vbn6G6MIxqjWOiIVTci6RLQhHIWuBYFpYAqXR78hQJEq0hNtACmlrTQMoawl5HiGWkvUAk5qC+3hYGPPEEPPGEuWWibx5P+hq6u/NU48PoxkEbPSEFfa5FAXAEGG0IIsVarJnBct/Ayb5EsD6VCg65cd+fJoh3JYBNk8otE8vmh97yfxhjtBBCUhgpE6xtIQ6HJWYU9BbPYlwKeie2bjlYyOWGkjhCKYXAIAUYYzAGbpKnEAjLwrZthJDRerUyfX166g2tWcOwpGBWJcworFUcdwWrtEB5ZNHM/qS5Mb4nnkA88cRN4yvgOHuJ42Ou5D2exe4dO7Y9gE5suSErAxoB0mJtvTI1v7x2Oko4obGfA/8VqC9v/sz3TAC3TuhbXGazYNpqwIdMD04yQqx321IdsQS7MrY1OrFlbJ9j4dpS4NiS5pHd/ORL//5tjal88hzD3/whPT94Ed0ISJRBGQNYVOr1pbm5hUuJZjExXFdwSWvewHVnkbl1sj3r/Jt/Vje/+7u6/dm6sawHpVK/mHF4aMf4yMFizrOfee2rd3z+++/7PM0g5sKlyTeChFORsb5Fxv0hrdZ8ezfwVrvgbQvglslPlwEftGBRsg84ezZpr6BbhLTPhrMFHGcHSr3HEfo+V7B1fHRofymf6bUFyHyGp3/8p/dU9T382G9hzy2hjUAIC+m4nD5z5oUoYTE0TMVKXsZ2zpGIC3T5a6wrG5ofcKV6POeII7u3j++c/3e/wbUv/tLbet4jhx5nbmW9cn1u9YXIOF8F99vQWLpVK7wrAWya1HRl79tnMzubpaV7CVUW1zVYwTqt1goQbkgeBrKwthXUg55UD7gW+3bv3P5eoWPhuzZvtbLu5fX+o59jvdYgiGKkdMgXily88MazMUyHiguJsC4ivbybNH++v5x9ZMfY4MCTr94Y2yNHPkcUKbQBhEEAvufw9Ct/fdNzPnDf55hdqrQm55afCZX1f4P6IVDZmLvbCeGnCuCWyZdAH9jbwGyVqDGggESjWRFwVbnuBaLoGpABDtuSD3oWj24dHTqe9ZyMPn7gbauXe3Xdt/sz1FottNFgDJYQuJaF7TgEcbLg5krVWjPwkrA51J2xnNcu/0PnvQ8efJxEg5/x8TwfgyEIQoJWC0fCi2e+drOw3/tF3jhzeWmlHn4/0vb/xQfe9xJPPqnvtBPeUgCbXDMAD7cwQVR72JU8aku2jwwN7Hzj+vzAXXlJnPiZTv7erZ+g0giRlo2UAq0VWiW4FhQzLvmsT6INK5UGQZRQymcpl/JoowkjTXdPmfGxYQb6usllsxigWm8yOTXLtWvTxEGTVy584xZ19FlePX/1akPxn7TX/ee0Vmfv5J7aP23y2wY0g+McIqr9Ysbig8P95QdsYXxXxj/z1bvlz/6Bvh++jLuwil2tdcSWlPJU7t/H6//+N9/y/WvVJmFiKBY8pJQYA0ZCpA2RsbAchx+/8tds+dNvMf4Hf0kzjJlbXieIEnp7utm3ZycPHN1HuZhHCIFGEEUx27aM8Fq5i5OvnuH4/s/y4us3dsIzp77Gwa0f2/rG1MKjYVR5DZjb8BKFEGKzEOyfpnaE2OqDc0DG6td8i48P95W2Xpr9/s9koif+j79EaYNSBm1M6nq2JzwWECM6/pWoNslNLXL8m08iEUgJlpQkQz38+Dt/2PGQchkXEWsc28J1bITvYgw0WgGLa6ldAJj89U8w+eufAODo7k8TxXUc28a2BQKDkBJLSkyi8F2H3dtG6S0XwRiee+EEjx79VZ5+5cudzzP9N3+A//A/3xPH6n5N149hvXIbl/3NKuhNgZPr7idK/oUv9C/v3jo4ulk/pnc6dlcT/96DOwiimEhpEBLHtfE8n6zvkfF9PN/DdV1sSyKlQCDQxqCUJoojWkFIqxXQaoUEQUAcR2A0vmuT812aQcTcap0gUmR9H89zkFIipWS9UkepmN3jfTyzafI2rmN7f4VQGbZvG+eh+w+za/sYjm2jEoUQAs9zWVxZ46++8U8szc9x4uzXb3r/gW0f49L0wtcCMv+RuHVyIyDcHBvYb+3L+6Myin7Jk/qXxj/9wdHXvvYfUr068XGiJEllNL14d5Gy6zE2PERvbze95RLlUoFCPkcm4+G5LrZlI6VEa4VSqSeSCkGjlCJKEsIwFUSt1mC1UmFppcLS8irr6xVazRB0gjSaKAqJkxit00UXJYqcZ3P9v/u1247txLmv8/Chz3Hp4lUWl9a4/+h+Hr7/AF2lPK0gJk4SurtK7N21jZXlZd53+HM8+9pXOu/P+y6OZDhIkj0wdBrmklvjKPu2q/+JJ+BLX3KZnX3U1vrjW4d7J85+7T8w+J+/Sen3/wuFUp7+ni5yGf+uVc9HPvRe8vksGd/Dti0kMtV92hBGEdo2ZH2PrlIB13NQWpPE+kYMblJ1pbQmThJarYBqvcnyapWFxWUWFpeZW1hkdm6JSqWOZUsKWT/dBULQVcjywhc/fsfx/fjUV7jvN36X+rOv8fxLr1BvNHj4/kOMDPUhECRKMT4ySDabpVFZu+m9XrnA+Ojo3nOT82OaOattT8VbGWHRxkwErrtTJtHP93ZlD52d/C6Fl16n6/f/hJHhAQ7vnWB8sPee6P5s1qdaqzM1PU+l1qDWaBEEIYlK4RvXcSjmswz197J1yzCD/T0IIUg1pyH9OAIhBJYlKRZylEsFtowMEoUTrKxXuTYzxxuXrnP12jRra2s4EroK2ZtW61tdJ7/0P7bd2V/mxZdP0WyGPPLgEbZtGcaxbUqFHL7vU12/WZ0/9dyfcf/Bz3eTJCXosjbwqM2G2L6Ny2no7c2xvPqBjM2hvqG+7Cww+Jl/y86d4zxweCejAz1orTl3efquBfDlb36fZrNFGEUkiULpFPNBmA2/ASnAc1327d7Gz73vGNsmRjAItFI32TWlNEpDYjRCCISU9HaXKJeL7JgY49r1OU6ePs/FS1dpBNE7HuvJC9/k4UOf49SZczRaLT7ygePs2b6FnO/hODb6Nh690RopcbU2ogMIbgL95JtWPwhqtT5H6gd2TIweee3M33B456e4OPVt9u8eZ3SwFyEt5pfXefnUpbsWwMpqhVqjRRQlGMCyLBzHxnUcHMfBsS0saVFrNDn3xhVOnbtEkihsKVMwTghStDn9TEZvqCRFohXGpHBquVRg364JPvz+9/Doex/AzxU4sOOTdJ08947G++NTX6E773Ht2nW+/+SLXLo2DSL1wG4XU1lSAkRQuW3AdasADOCTsMex2KYGimLDWP3aJ36bob4ynmOzslbhlbNXWVpev2sB+J5LLuOTy2XIZTPkMj7ZjEvGc8lmPAr5LKViHomg1mjSaAUbWzZ11YRAiBso6U0/G9DGECcKpTS+67JlZJCHjh3k4eNH6e7pYfTx//4dj/m5175C0beZmprmR8/9hFfPXqTRaOJa1s3QxNHHWVlZm060XAGUEEJ8djPkfYsAOmoZFe71LHrOPPtnqRQtyWB/N/lMhmYQcPn6HG9cnaGQde9aALZlYVmpjy2FTHW5kFiWhSUlWmsarYBEKfp7u5kYG8ayLLTW3MaFftNX2+1Da02sFFobespFjt+3n4fec5hiuczhnb/8jsf941NfpeA5XLx4lR89d4LllTVymZvno94MWFheuYxlX4XRBBBfuwUdvUkAxhhDJpORkon+T354T2cbOTb5rI9tS67PLXP+8jRJFPHS61+7Bxkb0ujUtDH/diCmEk0YRNTqTWr1Bj09XTxwZB/7d29FCoHWmlscitsFlAghkFK2DbchThISpSgVchw7tIfj9x3Ecj0ePPD4Ox76M69+mYwtmJqaoVKpQvZmr7AZKSLNNRSvw3TMLav/VgGkf9DasQW9zmzq3xdefh0MCAHVRovzl2eYW1iha2zgnnhBWuvNCwClNWEc02gGNFsBQghGhvp473sO8dCxA/R2d7WjZJBSvpNcRicASxJNK4joKha4//Be9u3dST1KePCj/+odj//501+lp5BBa01zrdb5/fsOfZbrMwtnIsVJiCbbCLH56QIIQwF41noVgNoD+1FKsbZe5+zFKSanF/AsyfNP/+d7IoBmMyAIQ1pBSBBGBGFIFMUIAaVSgb27t/LRDz7Ehx95gLGRAZJEtTNj7zpF2gblDEEYU+4q8t73HKant4docu5d3fPUpb+jmM+yVm3ygfvToO7S9bnlZswLOJkX4LPNOyWy7NsYYa0M4WbZGKU5ffE61XoLHce8dunv7hkGlCiFSUzbA5L4nktXqcjoUD+7to2xfesoA73duI5NnCi0MT9V9bw9IUCcJFiWZHSonyMH9vDcCz/h4UOP8+NT7zxPcfrS37J/+yeZnF3m4I5Pcr4anNOW+1268mdZ+pq5Exx9OygiNrAU11oB4AOIsX7On5vEdR2Kn3r/Daz8wONw5vLdRcIfOE6zGWAwZDJpxNvX3U1/X5necpFsJgPGEMYxSptUn98bsgBSCFSisG2bA3u2c+HSVWan3n1s4z1ylNW/f4ql9cZKbOQ/oKLnzOJiPWV93J4pYd+CRAhKpYauN09fn569dHjiowdeu/Zdlv/5xwh/+3+nv1zg1f/z33XesN4M73oiHvvQw7SCEGMMnueSy/r4nosQgiRJCMIQrdNVb70Dnf92Likl2hiSRDHY183YyCCL8wt0nTjH+rG97zxQ+9ITHNz+cephLcayr6CidfFTtqt8k09SqdRQ4vlWwrnlar0KUP79/0Jfd5FXNyUejh94nK5y191DERmfvp4u+nrL5PNZQNBshdTqTVpB1HY1b7iTG1/36tq4n+vYjA0NUCgU2PXPfudd3+/05X+g4BqkSo7hFcdusblvKYCN/9QQXVNYX1+rBS8M9X6IWiOgv6d0i4uVsG/X9ntiA6I4IY4TkngD8dQbsdZN498sAL3p9d0KxaAxRtPb00WpWCCI7y7RtH18eNC19YcJG0dhp9v2gMTtdsPtBADQAvVkM+E7Xqaw6kjBsyf+8gbj4NDn8DIZ9uzccvdqQIgUfrBtfM8h43vkMj75XIZsxieT8clmPHzXxXVsbMtCijR40FqjlO4EZe9WCMaA0ppCLkM+n0Xpu/tMz7z61/QUMtsdqT6IM7cXnrijGrI3B2FiA1pM0xFrWK+ue56rijnJ1c1WWmn6h3sZGui5+0jYsVHakCiNjtPVr82N9ZAayzRStiyJbVsI6bSFAMZolE5zA+l300ZJxduKEzrRsjZ4rovve+/ex910TYwMdK3Xrz0Qq9Yr8MSFNltEtANC81ZwdPr0zNl+2VK7VFj3Xr70j51/OPRb/wsIwdhwP/ls9q4HeunaNEEQEUYhURyniKhqu5ukk+jYFq7r4nkuvueS8T0ynofntXeFbWHZFpax0Magdbo7NoK8zbDEW2SfkFJiWfY9EcBzp7/O7vHHDl6ZWToW4z4ND1+CJ/Wt0bB9uzww4KLEA67NsULGLW6+ce7pE+B6DA/0YtvWXQ/0ez94jiiMiOMElSRopTv6PR2XxLIEtm1jOw6ul67SfC5LoZCjq1Sku1ykXMpTyOfwfQ/HkWgtUYnu2JMb9xN3hkRoP9fcC0cXijnP9Rz2x4k5jnryeqraU1vwpnzAzVe+KJP6w+WCt+fcD//45iBBGfJ5n55y6Z7441fPX2EDSBdmU7goNibCbKQFMOJmPqjru2SyWYqlPN3dJXp7y/T19dDf3UVXMU/G9wCLJFFpwNc2ercKwbR/pxJFHMcbT7zr6+Vzf8vuLY8dvDS9dFyR+X9hA8q9xQZsWv2aiQmP6fndLhwe6u/ZMjd6M+ajjcH3/ZQjcw9WSrfnYbXBMskNKNm0043G3FiZKVZkSLQmSRKiSkhztcriDAjHIpPP0tPTzcjwAONjg4wO9lMuF/BcN02YtI32hmsrZQrQIQSWEDSDkGYzQHDv3NyC7xVdyd6W1PtIhtZgrrXZFtwUiBljtCgUiujkYd9m2Ovvvs1OFbiOi+s4cA8Gmsv4qY7u3D0l15qNZMaGvRKbENNNej5RilgpIqWI12vMrNeZnZrj3IUCIyOD7Nw+xvaJUfp6unBsJ11ESreFmt5TCtosiRrVWg3XuncBn71lCH9yur8VxsfJVs6Yhmm26xVuUkFtDpCQ4A5YIjm+a+e2A88/+6XbKku5kfSQd78DOvq+DbC14phGGOFaNqWMf5OQzSbPxbYlQtj47SFopYnjhFYYUQtCVmebVJbXmJ2e48rVaXbtmGDbxAi93SVc10mTNDoFKCUpYWt+aYVqtUbO9+6ZAF743h9x/MDj+145e+X+qNn8lhBi/obWE9g35YLpLkBlb8Zmh1XO38FbMCQq5cU498AIm7YjvqEOVupN1pot+vJZurIezShBaYVn2ziW1QnC9CYffsN7th2bkufQRY44Sag2W6zMzDM/u8jVq9Ps27eDg/t2MD4y2N55oJRGyDTbdn1mgWazxWsX/uaeQh6uLXAstkbG2kFv72UWFjro6EYyNbXKbjwsUcd7S/nB55/5kztGLVEUEUYx1XrzXmABHRXUCEOW6g0qQUikFfUwYmatwrXVdVabLRKtU/+/sxNS9dG2zalKSlKVJISgK59na38vo4U8zaU1nn7qJb7xrR/y4skzrFdrSCFwHRspYXJqjtnZeWS7mOZeXs+8+hWKOb9PGnWESqW/7QEJY4zplNIIgUC3hh2LoyND/X13lqakFQS8dvYi3/reM/dkgJYQRIlirlJHYMi5NtVWxMx6lShJaEUxi7UG1SDsTPZNHD9z43UnDmiTKoSUZDMeA6U8fb7H+vwi3/v+c3zzO09x8eoUnuuQKMWJ0+epViqEn/0IP4trfGRwyBYcIFD9G0MXQgi7vQs03T0FVpPtrsvYc2+Bh79w5usc3PVpfvjsT+jtyt+DyU8RydVGk6V6g6KXGsp6ENBCkLEskihhLYrJuQ7d2QwI0SnBiVVCpBQZx+mQrTb+qLWmFcdYUpJxHXpcF68VsFZvcua1s7SaTVbWqxhjuHptGt+yePV/+m9/JgKoffqDOP/b/7NVKT0qhDhBSlMUdif6jRizJfuG+3vHL7zFjXb8xz+n0QzIScne7YP3ABIWrDUDZtarGKVxRcr9dKUg67qUMj69QrDSbJEYTZQoXMfGYJBCEClFLQixpcTfyP22oQiMoRFGHS8j4zjkfY+M67BSr3Pt0jUWllYxlkXSat5z3b/5Ovt7v8XQH355uFUJJqDQBbUVQNobFplWY4st2FXMZ96S6uD/0d/glws8fN8e9u8au+uBhUlCNQjJZnx2jw9QzvipdyIE5UKWvu4ipWKBM5OzvHF1llYU4dp2BzNJlKYZxeQ8F99x2sFMGschBM0oJlIKz7HIug5KG4QUdOdztOKYpaUVVloBfT1FftZXb09X/2p9fnso9QgxqxtuqAFsVDjmuIxN/cFv3/EGB3d8Ci/jcezgDvbtHMNxnLse1Fq9QSbn8+FjR3lw3wSubRHHCsexKWR9isUsuXKRrhfPsLxWY32pQil7Iy25IYCoXcO64RnRrqwME0UjiuiK/Rt/a0McrmVT8j0whmYj4OieT/PK+W/8zATgOzaWYAs6GQHObBJAoWhTGx8ZGthz/mOP3PbND+z7FULLYs/2MQ7sGsfzHK5NL931oFoq4ei2CX71sYfZNT4AJkVGLUtiS4nl2oh8jrGpRXJZn2W1lgZpArQ2hCqhFccpTWWTc7tBQ0m0JkhSO7EhmNR2KBpRjGVZDHeVWKjWWVmpcWT3L/PqhW/+TATglQtIySAqGYRjEk4oG7Bw9IgNY7bEub3e/wuqrYh9u7ZwYNc4XYUcq5U6py5Msmd0kKK06CvmSXRawBbECfPVGo5lMVQssFivUwsiBgp5yrlMx51crFQpFvM8cGgne3eNkbEsVKI7k5QGSgJba1yRGuwbJRspNyhMFMkG6rkpqqTtDW1A2CA6qk0IgTZQD0NA0NWdob+QJ1mvsrpa5749n+Hk+XtvD559/s/ZPfHx3ZdnFgeTgdhl3jQlYKODcVswcKcQPPtHX6e/t8yBneMM9pYJwpipuRUuXZnBNVDK+CmeZlIBNKKISKd6V8hUIJYU2O37CyFItKYWRuzcOszxAzvwbJtWEBFEEVEcE0YxYRgThREmjFHtXEHqgqaTGGtNlCSYzUyJ9mttDLFS5HM+PV2FNPpVm+BpIExSAx4rTc5zGSjk8RAsr1Q4/i6IWm/nciyZE1r3U5stIQSSvj4bY0Z9x+nL+m/eAA8eeBwjJYf3TjA8UEZKwfJajXOXpmhWGhTbmLzaxFRrRhFSCDKOQ6QU9SjqJNXb9Zo0wggv63Pfge3smRhGhzFmg6TVVtRStpMqUpLoNPGSGl/TUSMacBz7RmKFNDjTxhDFip5ykZGBbnzfIYoTbqCOkGhNpBSJ0ihtKGQ8Bgo5bGWYW1znfUe+cM8FILTCFvQTN4c4dsyWxLGN1v0TW7YcePb0N97kctbDmK3jQ+ycGMZzHBqtgMnZRS5enqboOGQ9B91RrgJlDJFSOFLiWJJ6GFELIgQSS6ahq9KaehCybcsge7aNkMl4xEnSSVFuTqCkeWFNFMXEcYJAooxJG0Zoje979JQKaSS8CfPX7ckd7O1ipL+c8opUcvPfE0WsEpTZaEIBed9joFjAxIqZhVXu+43fvacCsC2BFPSgwyFOnLAk64ljSXow6k3LP/fH3yCb8zm8d4Ks5yKlYHZ5nQuXp0maIV3ZDLZMU4pik95OlMZur/Z6EBIrhZR0crmxUigJO8YG8CyLlZUqQsrNyYA3MRdaQUAYJUghaIYR1WaAloKBvjIjfd3t9OQGrCFQyhBrzWh/N4M9ZSxLtm1K6qMmJiXrJptIvtoYDCIVQiFL2Aqo/+Dle6uCbAvPsbtR9AOWpCi7JHTp9urYuB569F+iDGwZ6WekvxshoNEKuXR1lpnpJXpyWTzHaedSREe1JDrNaEmZGroEyOd87DZ125hUdSgMtVbAWqVBGKXRqjA33MTNgI9WmlojIAwjXMdCGcNCtU6MplzKUcz52CJlV0spOjbGCBgb7OlE7HrTzZW+wbLTbVw6LeRN4Yui59GbyVCvtdi37RP3zhNybEZHR3ZJaXpTAcT02JKCMDeDUNWZBUqFPDvHh3EdGwRcmVnkyrU5RJRQzmYwmI5h3MijdlaZAWUMuZzP1pE+PNdGG02iFbUwwvVcekp5BnpKFLI+2mz48aaz6tOEPERRwnq1RSuIybg2GdchTBSZNolrrdakFacBl9YG07YXru8yPtRDMeuj2tU0hhSiUAYyGY9SPteBxDc+hjEpA687m6HoOFQqDY7u/cw9EcDKJz6A7VhFrXWJUsmS6LBsWWRvTZUmWjM80M3IYA/GQL0VcvrCJGvLFbpzWaxNHs1maDlWCkNaFGEw9HUX2TU+hLQk9VbIWrNFI044sGOMjz50kCO7xsj5Lkmib2YptCdECkm91WK1UiOOElzbQbYTOQd2jTPcX2ZpvUotCKi0WtSCgGoQUI8islmfgZ4SnpvySjvRc5K6oz3dJfp7ulCqLQApOulJ0WZS9+ayZIRkabnKI0fv3iif/f1/TRJFWJIilYolSZKSAHfzRN6359N0lfKMDvZQzPnESnHhyiwzU4u4RlDKZsCQFkhvii6NMcRtjyJMEowQDPaW2TLUS6Q1c5U6S7UmuZzPxx89yqGdW3Bti0TpTipS3AQ3p8mf5fUaK2u1du8gwUq9SX9fFx958CD37R7HkQLftjHG0IxjlupN1lshxUI2rYhsq5mNOoFYpZF2X7lIdzG/qdjj5mcjUkZGl+9ha8P0wto92QVKJUjIQ3dG/ldf/PXHRkdGRjfj7I0wYqivm+G+bgywvF7l1LmrmDCmr5DDtVOPotJqUQtDlNZpUyVMqgoSRRgrEJKB3jI7xgfJZjyUUni2xchAmf3bRslnfOJYdcCzm3ZTO1eoBFxfWGWtUscVkiCOqUYR77tvN0d2jqcLwcBAMU9fMU9vIY9nWfi+w/4do5TKRaLEgE71r21ZRIkim/UY7O2ikEt7Rej2jtu8C2U745d1bEquQ9QKObL703efAtEaIchCPWdv2bLl+IVyuY9WdZP0JX09XXR3Fag3Ay5dm2N1eZ2CYyMwLFSq1IKQRhxzbeXtrYo7eXPNH/2njuF8M2kKIqW5Mr1EsxmQJAnL9QZbJwb54MOHkLbN65dnWFyvkS2JTketMIopdmW5f+9WirkslUaLejMgJy1aQchqrcHukR5G+8tMzS63vZ8bG+BWCoslBTnXIUgUK2s1HrnvCzxz8q/uKgkFuGQzOZkr5Mcu//5vFjek/dCj/5KM59JdyuO4FtNzy7x+YZKkFaKVZj0IqMYRgYRafC+adbyZ72/aXpQlJY1GwBvXZllardCKFblCjl989D4O7N5CtRWwtF4DKWgkCZUwZKXeINaasaFe9m0bxQaq9Qa1djyy2mhRDUNGBroZH+gGrTvR8x2ocyhjcCyLUsbDxIqVtfrdBWPpl42JXNuWVnbhF46z7fcKqau2UiGb8SjmM1SqDV5/4zqT1+fJ2zYBkMn5jPcUSYALF6fvwfS/+WetDY5jobTh4vU5zlyTWpIEAAAfZUlEQVSeYm6tytbhfj740EEee/gw5WyGRbPGni1DtIL9oA1xnFBvBjiOxcNH9zDU24WKU6h6ZLAHS6c2JVPOcd/erfSUC4RR1Imsb6YrpiOL23GNZ0kytk3RdajXmxw/+Dgvnv7qu5aAAEmMZRtjIn922U3a7OdYJZRyRVzH5vrsMhcn5xBSUu7toqevi7HhPkYGe1hYWWduaulebICbSLUbqsCSFgvrVX7w8hmuzC7R01viw48c4Vc+fJytw33oMKa3kOWjDx/mgUM7aSYJrTCm3myR9Rz2jA6S8xxaQcSR3RNkM37qorbrEB4+uofp+WVaQbQJR2KTC5z+2IoTlFI4MuUOdfk+jWqd9Urj3e+AjjuGseuN+tT9n/i3feFovkh79eUyHtpArdHC910O7d/G9i2DjAz2MNRbJk40U7NL6CS5JxmxDZdzo4Jtozx1anaJ185PMtRT4rH3HuEzH36QHWMDqESRJIqufJbeni5iKWkpRagUWms8KclJiVRp9uz4oZ0cO7izjaQaLGlRLGS4Or1AEMbtxL64OQZvCyNIUgHk3DQJ5NkWedemEUQcP/A4L5756rvd9QYbbc/OzJ1cX19/oOAnxY1o0XVspICxod4UTSwXKeQyuLZNxnOpN6qsrtUwd8vjBmxbotqFdxsGSlqyU6x3364Jtv/8AA8d3sVIXxdaJSRx0uYGWURxwtxqhetLa6zXmxhjKGV8hrtLDHcXyfpum2glQJhOEt8WIm2TE6t22dMtfDgBRqdMcK1UJ89sgILn0Wq2WKs272LXkyBFZH/ve9//YW1pYbQrw8QG78dqcy97yyUGe9s4ikoBsTCMqTUDao1W2i7gLq/lapOBriJRnLR1owApwYbx8WF+ZXiQ8f5ucr5LGIZESmHZDq4jmV5a4/lTl3jl7BXWVyuEQeoSG2FRKBc4uHsLjxzeyfbRPqSURGH6DCkEGd8ljGKiJEkZeOKmdFkbukgZ1hKBIy2MSN1V37HxLYtGEPLgY/+aF/7xD9+xERAQIpKGfWXy+kzJDps3gqk2TVumSYwwitpYCR02XKwSWu1E+F1zZl67zEfe/wC5chdolQ5NSDJSsmXQQTgOJoqJoggjPdwsIC0qtQrff+kcP3r+NKoZ4wRNrCTGwrBUDzhx5jJPnTzH5etzfPbD7+HA9jFc1yZJVFppbwytMCKKkzSZfxuHQJs0oNSJohlF+G5KXZdC4NsWrTAhfBcNS4SUaGhiWXUboSqJIdSbpHODf5lGiLINdW50r42ihCCM7okAvvP0axS3bucjH9qDFUWgDVLINlGpPSIXXEArhXAcAkvy/KkrfPvpVxhyMnzhY4+yPHmV9ZWVFEYxhrMLq3z3jUm++uRrVEPNv/i45L69E2ilO3BEs5UKIOe4nVW/uYex0prEGJpJwkqzRZfxyXkuRhg8y8KVklozeEef974v/g6J7aAMNWw7kCRyTWuaGxnVjTYAmzvubu69oI0hihLiSOFYd09NbCwscuKVszSbIZbrIW0HIS2ETIVgNloUS4m0bSzHptFo8fxLp7BqDfYUfWSjimUUGc/Bc20KGZ/dfV08ODZIX08/p+ZafO+1Ka4truP4LraUtMKIeitEtbN1YpN5bLc3pBmGDPR28cEHD3L40A4qcUwtDNHG4NgWjpSEYcS+/+GP3vbnzZ44i+P6KM0aNSuxwVpLNJWNcjEpIYzaNbmbOpBscOu11kRRWkghhGB3bw9ztTol36E742PnM/zP/+bzZFybb564zmTLpzk7yV5d5VBfF8JxWajU8KSg5LqUjGZ+coq5+QUK2yewhMCoBCFFSi3Z5KoKS5Jow/zMPDNXrtHvSHo9ydVrk0yuVpit1ujOZtjSVSDrOOztL7NW0yQDo8w0LZ47P8vIxARZFdJcXadaa6CUbtPjUyh6I5o2RtEMInZtGeRTj72XTMZDix9y/uxV7DCmlPVwpESFmuI3n4Sf0r1x4wqimKVq4yrIZbCVBKepNGvCctjxB3+KY1k0gzDNPnWQyQ6fN20NphKSJKHaDKirBOw08W0JgdGGlXqLS4s16sphoFwg11xjyHewheTU9Vn+6fxVTs0vo4Wg5DqEtQYLy2sk7dWOuZH37WBExoBlEWrD3OIqUa1B3rZwpGC5WuPk9Dzfe2OSV2eWWG+FWJak5LvkogY7RwfI9/RzYbbCUgimq0zdCCq1JqZd/I244Q5v7IBYa4b6yxzZu5VH37OfDz90CNf3aEUxApEmnQw0g7dfLx3GCatrq1Mg5mE5kWAnWsrltfXq7MCXvoXvOTRbEUEUbwqQTCc40SYtjmgGIethRP9QD8NDPTi21dkx11bqrCQ2Xq6AZyK6TEzOttBCsNhocWFpldlqnVhrbJkmXMI4SYsyRLswRmxi3W5SDgpDK4zTiWsX8CXGsNIKWG60WG8FtOIY0SYB2FFAybXo7+vFSJfLUwu0MmXqdo5qK0KYGzu9U49MmlhCCga6S/RlPHpchwPbRiiV8mlrTW3SwkEhCcK3D8lEiSbWLGO7M+zbpyRDXoIQUzNz85eCKCLju7SCkHozSFHOjSqSTT5CGEU0wpBsKcuBPROM9ndjlOkk0K8tVQilT1e5GxNF5GzZMeTdWZ9tPSWGCjlsmVILLdchl8kg248Rd4IrjMYSgmwuB5adFnIYg+fYbOkqsre/hy3lAlnX7qgSG4NJYvp6eujp6efK5Cz1xKWiPapBhC031yPfaJWfKIWfSRHTomsjo4hi1ifju5g271RKgS0FcZK87c5bRlhomMM205w9m0jm5mKkvBhp5sIkhYt1klCp1jupws0htDEmbXYqJbt3jLFjfJCM55Ikqq09BIurVbBc+vr7se00Z7zh44+XCvz8zi08MDZExpY0lCFTLDLU34ONbjMjboeMClAKXwoGBwZwSl3UYkUzjMi7LvsHevngtlHuGxmgJ5tFCoktrTbSaejt66W3v5/l1XVajQZT86ssrtTIOk66yNCdSkljDMEGo6K/G9/3iBNNGCUkidpEbUmbSymlOfCv/tefOvmPHPoVgjBaVYYpSqUV0ia+JHjetUhxud4M1+M4wbMk1XqTWjPoZL42HqqURiAY6u/m4K5x8lmfOEpuQLhSEocRtrQYGhyiZ3CI1VZEM4iwhKC/kOXoSD87esvUmwGB7dI/Pk5vXy9S6xSdvKXyZsMOGJUGRX2D/Yzu3ElFWEyvruJZkuFijgNDvWzv7aInn6XgexitqcYJfqHI0OAA+WKBZhBw/cobnD1zmvnFZZBWZ9e1SRu0ooT1ZkRfuURvuYjdToHOr1aoNZrITbkC2WbombdRK9FsBaxVKhe1tq6zsBAC7W539fo6WGcXVyqvV+sNchmHMIqoNZo3VQwaY9BK013Kc2jXlk73xChO2nYCPN8lm3ERGPr7+9h74BAV22NyrUIzCMg4NnnXIUlizs4v4/YPceDIIbK5TApt3AEa7vxOKUoFn4ff9zDWwDCvL66x3mhiSyh6LjnXoeh7uFKwWK3RcHyGJiYY6OtL2dKtgKeeeobrl1/Fsho0lSKI0gAwZVwkLNeb1OIAx7NTMpltUW2FvH5lhka9hW3JdnmW6RSIvJ1quUYYEysuYbvXNjjEG4fbKFz/VKw5ObOwvG5bEmk0YRR16qg2gDopJaMDPRzcvQXPc4mTFCcxOt3E+VyO7u4utFbYlsXBw4fZ/sCDXFeS12YXuLK0wuvT8zw/Ocu0lWPfQw9y39H9WDrp1BvdtkqxLRijFb6OefCBQ9z3gQ/RKA9ycmaRq4srLFdrVBtNVipVzs3Mc3q1ztjhYxw4fARhDOvr62gNZ85dwXfWeOBIhu4+wUqjRStMqLZiVlstMiXFjh0OkWrQDCK0NlydW+aF1y6iY0XGsW9CcG3Z5jy9xfXgL/wmy6uVpUhxBk92Gg/YgHniCXjiS30X42vBj5cqjfc3g6gr43ugDYky2JZNohK0NrhtjMiy0q2rVMrl1MaQGEO+mGegrxelEpaWljly5DCf++Kv82UEV179CUtrzbQk1C9w+CM/xy987OcZ6y8hausIZLok3iKERytMGNBbKvKpT30MbSQ/+tuvU1tdZzVSFH2XWhQz1VLIiV186le/wIH9+zl58lVmpq6zZbgfR4IOKmwdFuzZIfnGP6wxW42RtmFwRHL0UI5CTnLxsiJKNHPzK/zoxTOcvXidomXht9srmDaNxbYkJvvWXYTV5By1MJnSWOdo3DhZY0MA0pirgcjnzwatxgtnL17tObJn+0jQDGmFCQO93TQaNVSS3MRa2PxdaYPWUO4qsW1ihLnlBktLi0gpOXb0KN3lbs6cPsXklSsIabFt9x4OH9rDSEFjNdPG4uKnIRvGpJvWGKitMNHTxxc+/xl27trFS88+zfzVK8yHLXKDXRzbd4CH3/9+Dh08iGVZXDh/noX5GT72+V/g0uQcf/Jnr1GvNfjUR4ZZqyi+/p0Ftg5l+Pwn+ugpC556sc7E4BiOZfN3P/oJ3/j+i9hKkct7HZ2jtU7ZeY7D6s8dv+Ow33f815herdYTw6u41lUTJVogLEBt1Ads9DS+rLH+vhKoI2v1YGR+cYl8oUB/Xz/Frm4a9Rph0MJogwVYtt3p37lRl1UqFdm/dyf56UVaGrRW5HN59u7ZzdDwMCurawgh6C1nKYgmormMSRKEtDY5nPL2WQxjOoUXJkmwWquMFbspPXqM7du3s7S0TBSFZHNZRoaHGR8dwbEsTp0+zeriFIf2bWNi927OXplharbKpUvrDA0WeN/9JeaXApqBZno+4Nq0YGomy8HtWf7+6RO88Op5Gus1+vOZdq7CYMkUlknQuK7DK7/3W3cOvtbrLKxVZ5XiSYq5SRGJjndtb3K1hTGmIUTuRGSC7124OtVlCbOrVCwyv9DNtu0TWLZNvWoRhgFojdYKo00b3TMoIejuLjE0OkR3bzfrlQgZVTEOGCEpZSxKIz2gIggrmPoyqDjFfoThbR3Q1MYnpGVh4gBVWyCfKXFoogezfQiEhdigsesAFUXoxiL7dw2xY/sYYWR4/uWzqLiK61mcudBkpM/jo4/08uyJCt95aolmS5J1e9HRVabnFqmu1enLZ/Bsu8Ou08YQKY0GfPfOhSoPfuS/phIkcZBwGtd91ayuVm4q1N7cpkYIIdixY5XZ2W81w+ae+dXKcNfsfD6T9Sl3l+nr78F2HOrVGnHYwugkjRoTRUtplCXp7+0m43sM9HQxUGzitBbQSQ2k056UBFSEUAHC6Pbk35qyfqtcHjeMtGWl7Qwa65hGBSFtsFyMsNsgHggds20gw7axgyQannnmBWanzrJ3h2S0v4el1YRvP7XEr35siJ0TPi+e0kzNBAyW1mmsruFbgoFiFteyUnezzZKIlKaVJFi2RXhsz50LUC5e5/rc0gUj5feJopmNhr+0e8jdUqiN5NKlhO7uCyoM/25htTaQ8ZYeKeSzFK5cxc/4lLtKUJAkvk8SB9grFVpRRDNJkJ5LX28Z35I4SiPR0KogWrWOqkIITNvhFtLelAB/u0eUiTfbhSQB1a4T2Eixi7aTJ6DgOUgvw8nTF/juP32Hke5ltgwXaQWCi1ebXJxsMjGS5ei+PEf3FlmcrZCTEt+WOJaVGtlOMj1la4RhRKAUuaLPyT/9vTvo/i9wvdFqBIpXcZwfEYbrt/YB26iSNJtVEaurDbLZHwRB0Dc1v9KV8ZyDrutSKqaFDrlsDsuy8Hwf118lm8tSLGTpLhfJ5TJYxmDCEMIQYUya2+vwMQTCssF2QGhuWN530vbgRqNukgSh4jSA0+1mTR1tJjoJFEREbW2dyakZsm6doOmyuiqprlq4yuf5l2sM9DgM9Ll0dzk4SpJx3U5eYHN9Q5woqkFEIqCYz9xxlEszSyyu1U8p4f4TYTi9aaWxuV3Nrco3FUazuQje37cIi1em5nOe42zLZTMUCgW8UR+73awjnytycO9OfM/FcV1WKlWaSuFJgYkThCU7XkO6Qg1CKYyQHRXy092fO3tFRimkUpuohaLDOjBt0r8OQqTtkC+XKHb1cuHMDCYKsEwW384wVMiyuFLnpVdrOJ7BsQXojexg2800hjBOiBJNLQypxzHZgs/5O6z+43s+ybnltalI8zRaPkO7Y9at8FZaI3aDkGA2nfJg4MCktk79bT2OBy9cmfqYY1tj+XyeTDZLf38fGCgWCxw7vI+xoX4uXJ7k9NlLHDqyn/LYAMKWbRR1I+cqOsyETquS26x8Yzbz1Nu6XLx5jQiROkxG3cTquanVDSKNWkU2Q3Ggj1J5gFrFJougu5TBtQW2JWhGHq+fryEsTVZ4aXkVqWuttSaIE1oqIUKwEoRYtqQrn+XyfXtvY3j/G+ZWKq1mxIvasr+PDma4zVmbm1WQ3nywzKbDOBNiLijb/ko9TnrOX5lyPN8bzLY7U+XzefK5DL7nUC4VaIYRpy9c45VXzzLcXaScz6IbrRsafqPhkpAYS9688m8tDJDi5k7Lt+lXLIQAy0abFKgTm+FzIRCWRNgOxnaIhKRabVCrNMjaNn1Zj1LeSdncxuC5gvpSijX1lFMmhVKaME6rKRNh8PMZMq6DnSRkXZvLf/n7b5r8LX/6LRqXplhYq59KDH9PNvsK1erGSRO37Zyb3Omcw/ZuCEmSE7Hl/cVaM5Qnz7zxgOM4467rsnfvbrLZDHGscF2XHVvHuHxtlmeeeYmecolHHzpCNp+DIGwTUtNJN7Ktfm4J6jpNsmR6Umn7AACEvrXtWHsTtQUAKQxu2vleLIm0LIQliQ0s11tcu3iNp556gTfOvUHWtXBch1actA/qMDTCOCXhei6eZdEIY4IkIdYG41p093UxNNjNei0gjGLyruTqbZq7lv/9H3NxeuF8oPgmvv801eo6b3GUof02Dh4WQJjPhC/UW5aqhar5wskz73NsZ6vneezcsQ3HdVNMvFjgofsP8L2nXuI7332SQjHHkYN7yGdtjFKpMTapl9LR02xmxqWnIzVbEaEyWI6Db1t4QiDMZlhQ3PQeYTvQrrCVwqCFIEDQCALmpud5+ZXX+cHTL/LyT05RWVlnR19PeuaY1lhSkCSaahuttaSgGoTU4wTtWHT1Ftk+McyOrcOoRPH0i6/jWXDpNqv/6I6Pc2l6/mojlt/FzX6LoP5TKRP227F1ExPIVgvsRL0ewJeaCfNPv/DKY1GiDkgBu3fvRFpppeT46BAPHdvPy6+d58/+4m9pfuoXeO+DR8kVChCGmCjNZm2gp2KzepGCKFKcuXCFi5OzlIp5doyPsnVkgKznpkJskwY6AJ2Uqa1xHIxlEccJ6ytrnLs8yU9eO8vJn5zi8oXLLC8uU2+0sG0by5Id+Jk2/bAeBJ0TmTJ5n6HhHkZH+hkf7mN8qJd6K+SpF89QWa/SU/BZP7bvpjk6tPUxLs4sXa/H/COO/+dE9cu0G3LwFqepvh0BmGvXiIB16A4gCCFKYpL4uZdPBYnS9zeaAUePHMT30z4Pe3duI04Up89d5m++8Y9cn57lgaMH2LllhFwhj2yjmsQJKN3eHWlpEcbgWjaelBDFxEGIThS46WRjWQjbAttqg3OasNlidnGVydkFpq7PcvniVV4/f4lLF69SXVrBStIch/A9lEizc8YYJAKlDfUwwvFdussFxgd76e8vMzxQZqC3TF+5yHq1yckzlzl/cZKurMuzp28cXLHvd/4Q/uLbXJlbvtSM+S525q84tPc0J06oDR7DXZ0nLDafkMMTwFdtmOyGxk7gAxmbX9w22rfl4x/90NCRg/solkrYVnrw2htXr/PCT87QCiP27drOscN7Gd+2ha7ebkr5DCXfw7WdjW3QjnENUZwQtIkBtm2Tz2WwbTtdoUrTiCIqQUi10aRZqVFdXmFmdp7rkzPMzMwxO7vApckZpmaXKLkuvfkskdast0Jcx2Gip5xW1ACtKGKu1mBgrJ+DeyeYGO6nlMvge2mQuFZt8PyrFzhx6iK+Baev3DhJ8OH9n2at3jJXZ5fOBVp+G8v/MnHz9AbWf4PfdXcCELd4RqK9c4aALixrv4361bzD3s9+8rEd733wGAPts76SRLFerfHG5SkuXZvm/+vtzJbjqq4w/J359HS6W1JLak0tLHmQLTF4wIArrgAhCUVCUqlKheQFuMwrOG+SK6rgIpVKKoSQUNgJoRgCxpZsa7TUki21WupWnx7OPOSi28Y4SQUDZlWd61N7rbX3Xvtfe/+/5bj09eUoTYxxaGKER8aGGRzqJ5U1kDSVqNf5UlSlC/Td0X7xfHzXx+t0aNUb7FRr3KrsUanuY9YbhI5FWhHIp3WySY0wjJlfu8Wb719lr9Eil9BQVBnH89ERKeVz6IpMEEUcdGxahPzwudPMTI0jChJxHCFLIh3H44PPlri8sIoYhyzc4/zHp15is1Jvtlz/mh+Lr6Gk/4LbLNN9GMp/Kzm/UgD+lw0PC6ndXUQyGY2WfVIWgx/oCmef+84z5848PsPszDRZw8APQsxmm71ag/36AXWzhWU5+H5Ax3JAFMjns+T7csiKjKaqJJM6uq4RxzHtjkWt3sQ027i2gxQFZHSZASNJIZsiqch4nkuz3eGgZdGyPZwwxvZDmraHKIsUchmII1bWb7O1dosRI0MmodO2Heq2jd6f4cfPn6E4kCeKukIStUabj+ZXWVwtI8cRn610RSvOHHmZvUaL/UZrzfK5FEnSm6jqh5w9W+HixTvLzt0C+qEFoCdKIHQ3mmICzKNgnVclTho6hVd+/rPnTxydUkvjIySTCcIwpNnqsH9g0jCbNFsWW7d3uXJjhc3bVUYG+3j29AxGKsmNjW3Wt/fxggDL7rYxh/qyTI0OcnSyyNhQDimKuoE125iWixtCSLcsVTSddDpJ1kiR0DVymSQdy+afH13lvUsfM5xOkU8nqTaatMOQE08c4/lzj2Okk5hmm0q1xsJymZvlHaLAJSmL+L6H2XEwLW//oG194ke8F4n638kkrvDrgxYXvoAofCnnf+kq6P88bhG7ZKTHr8HGrid4n9bt4NHfvv679eF8avRXv3jlJ5MTowz0Z0mnEqRTCYLhAq7nMzleJJnU8bzL+I5Df0Ll6cemODAb/PW9MmEYcXRiiLnDY5w6McWj0xPkM0lqrTbrW1XWdxvsNi1iScIwsgz25cjnDDLpFEldQ1XlLs+pKqOoGplMpnteiLo4khsEJI0Mc8ePMjQ0hO+61Btt5m/cZGHxJr7nktIVrFDGCQR3q9q46gZcDiXpb2TkTzCdHQ4chwtfALPi+AGy+usE4L4nT9d9oEJAPSK51vbczfJu50ev//7t8snHjpeOTE0wOV5koC9HQlPRVIVUUu9qP+YyzC8sUWtZpI0Up+emubayiRjDL188x2Oz0xQGsviOx+LyJh8tbVA1bVQ9wfDYKPl8llwmTSqZ6GJSsnyXL6hLcwmappHNGqiq1iPyCAhiSCd1MskE1WqN7e0K1xdXWVktY1sWmqaCmohW1jc/tH2uhaL4MYp0FT+xjNls3IejPZDj75h0oatt+KDLj3DfdOuiMqdOiezsROC7xLEtiIQtsymsr695SMrYXr1Bu233iDliFFkmnzMYLvSjKDJblRq26zGcN5gpFXly7jAvfe8sI+PD3KrUuPjhAu9fWWW35ZLp6+OR0jjTk2OMFQvksxk0VelenYninn48d4n8VEWh2epwY3kN/ABJgJYX4AsgEbG4vMb8tWW2tvcJBRnLcZZrB433q3XzbTsSL8ZS6h0Syj84dGidvVudu2P+Gs7/ynvAfUzr9/YQ43sCoqTTGBmBY8R83wt5ujA8cmZgoN8YHxmiNFGkOFigP5+jL28QBAGXF5a5fmOFudIQL373SaaOTCJLApc/W+KtS/9iabNKtj/P7Mw0k+Nd8lXuXqPvAXC9blXc44n2wxDP9QjDiKWbZf7w50uIHZu0IlO1XTrEFAt5HNezNzbKi2FEJYzZCiM2IqQlVGkRT6tSTFu8+qrDhQtfaa1/GAH4Uj8+PSIk623GY3gmiHjBi5kbGh2fVTWNwsAAY8UhSuNFxkcHUVWFjy9fp7JT5YVnn+Lln77AytJNXnv9T2xXahw5PMns8Wn6cwaSJBKFYVcZr6cVGQZdgWfP93Ecj45lYTZb1OomZrPF7n6dze19soqKJgrUbY8bm7fmiaMrQBlJKiMINwmCXfRsBzVj0nzajOM3wvtm/dfK+m+kCnoQ+40giG+kGIx1Zl2fc17MeT2de0JPpPKKqpFKpxgs9FGaKKIoKteWNjByWZ556gk2y7dZmJ/fmi4NdU7OHGoaRjaJmp5tmC06loXVkzS3bAfLcuhYNpbj4Ngutm3TaXewLBvf95ztnZ1Pg5AlQqoxaG5EzhflCqL8DpKyiWrUOHXYjN99N7g30e5p18XflOO/1QDcGcwpkJ0CpTDgyQjm3IAxNySXzRcGdvf29gSJliaz44aoro8ui7iiiDuaE3fH8uxHAdJem6ykGVmjvzDoeIG6vrHejiKUIEKIe02xqPuSShA/v+gbySJNSWBFlFhwPWpNF9WDwV6j5ArQ+c/C4vNlNX5IjpL5lqw3AF8QhPVCgUrO4wNdZDKpUzh//syxt/745nwYYTs+diDgezFCHBIPJ2iLURQtVEjZLhk/QBsdSRsjA6X9TBwJy6vr5TjEkVTaooAZB3RUEVcRCVyRUBSIDMASiewGfv44/vXrd0GyVbpidX5vH7tziIp6H/FDztB/A168l/a1/nj6AAAAAElFTkSuQmCC'
    },
    proto : 'vichan'
  };
}
if (pref.features.domains['lainjp']) {
  site2['lainjp'] = { //lainchan.jp // ?.?.?
    nickname : 'lainjp',
    home : site.protocol + '//lainchan.jp/lainicon.ico',
    X_FRAME_OPTIONS: true,
    CONTENT_SECURITY_POLICY_DATAURI: true,
    CONTENT_SECURITY_POLICY_FRAME: true,
    protocol: 'https:',
    domain_url: 'lainchan.jp',
    postform_rules: null,
    features : {page: false, graph: true, setting2: false, postform:true},
    boards_json:site2['DEFAULT'].generate_boards_json([['cyb',2],['tech',2],['cult',2],['\u03bb',1],['\u03bc',1],['psy',1],['feels',1],['q',2],['r',1]],2),
    check_func: site2['lain'].check_func,
    components: {
      boardlist: '#overheader',
//      postform_comment: 'textarea[name="'+ pref.cpfx+'draft"]',
//      postform_submit: 'button[name="'+ pref.cpfx+'send"]',
      __proto__:site2['vichan'].components
    },
    parse_funcs: {
      catalog_html: {
        sticky: function(th){return th.pn.getElementsByTagName('strong')[0].textContent.search(/\(sticky\)/)!=-1},
        proto: 'lain.catalog_html'
      },
    },
    post_container: function(post_pn,no){return post_pn;},
    update_posts_remove: function(th_old,i,pnode,merge){
      var tgt = th_old.posts[i].pn;
      if (i==0 && tgt.classList.contains('op')) this.update_posts_insert_pack(tgt);
      else pnode.removeChild(tgt.nextSibling);
      pnode.removeChild(tgt);
    },
    catalog_json2html3_thumbnail: function(obj, board) {
      return (obj.ext==='.jpg' || obj.ext==='.jpeg' || obj.ext==='.gif' || obj.ext==='.png')? 'https://' + this.domain_url + board + 'thumb/' + obj.tim + '.png' :
             (obj.ext==='.pdf')? 'https://' + this.domain_url + '/static/pdf.png' : '';
    },
    favicon : {
      __proto__: site2['lain'].favicon,
      none: '/lainicon.ico',
    },
    proto: 'lain'
  };
}
//if (window.location.href.search(/localhost/)!=-1) {
//  pref.test_mode['94'] = true;
////  pref.virtualBoard.scan_domains['dist'] = 'none';
//}
if (pref.features.domains['dist']) {
  site2['dist'] = { //distchan
    nickname: 'dist',
    domain_url: 'localhost/about', // 'localhost',
    boards_json:{boards:[{board:'a', pages:1}]},
//    check_func: function(){ // must be owned.
//      site0.add_domain(this.nickname);
//      var href = window.location.href;
//      if (href.indexOf(this.domain_url)===-1) return false;
//      var config = JSON.parse((document.getElementById('json-config')||{}).textContent||null)||{};
//      site.whereami = (config.view==='headline'? 'catalog' : config.view) || 'other';
//      site.config(this.domain_url,this.nickname);
//      if (site.board==='/about/') site.board = '/'+decodeURI(href.split('/')[href.split('/').indexOf('about')+1])+'/'; // patch
//      site.postform = document.getElementsByClassName('postForm')[0];
////      site.proxy = '//'+this.domain_url+'/proxy/';
//      site.proxy = '//'+this.domain_url+'/proxy.php?url=';
//      setTimeout(function(){
//        pref_func.str2obj = function(func_org){
//          return function(key){
//            func_org(key);
//            if (window.location.href.indexOf(this.domain_url+'/about/')===-1) {
//              var obj0 = pref['catalog_board_list_obj'][0];
//              obj0[0].key = 'rss'+obj0[0].key;
//              obj0[0].domain = 'rss';
//              obj0[1].key = obj0[1].key.replace(/dist/,'rss');
//           }
//          };
//        }(pref_func.str2obj);
//        pref_func.settings.onchange_funcs['board_sel_refresh']();
//      },0);
//      return true;
//    },
    board2url:{},
    trim_list: 'no',
    make_url4 : function(dbt, trim_page){ // modified from 4chan
      var page = parseInt(dbt[2],10);
      var url_prefix  = site.protocol + '//' + this.domain_url  + dbt[1];
      var page = parseInt(dbt[2],10);
      if (dbt[3]==='catalog_html' || (trim_page && dbt[2]==0 && dbt[3]==='page_json')) dbt[3] = 'catalog_json'; // catalog_html is a skelton.
      return (dbt[3]==='page_json')?       [url_prefix + (page+1)+'.json', 'json', (trim_page)? dbt[0]+dbt[1]+'j0' : undefined] : // dbt[2] is string.
             (dbt[3]==='catalog_json')?    [url_prefix + 'catalog.json', 'json', dbt[0]+dbt[1]+'j0'] :
             (dbt[3]==='thread_json')?     [url_prefix + dbt[2] + '.json', 'json'] :
             (dbt[3]==='page_html')?       [url_prefix + ((dbt[2]!=0)? (parseInt(dbt[2],10)+1) :''), 'html'] :
             (dbt[3]==='thread_html')?     [url_prefix + dbt[2], 'html'] : null;
    },
    link_dbtp2href: site2['meguca'].link_dbtp2href,
    make_tack: site2['meguca1'].make_tack,
    testPoster: !pref.test_mode['94']? null : (function(){
      var queue = [];
//      var post_func = DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(function(){ // working code.
//        var area_com = document.getElementsByTagName('form')[0].getElementsByTagName('textarea')[0];
//        var button_submit = document.getElementsByTagName('form')[0].getElementsByTagName('input')[0];
//        var post = queue.shift();
//        if (post) {
////          console.log(post.com);
//          area_com.textContent = post.com;
////          button_submit.click();
//          setTimeout(submit,600);
//          if (queue.length>0) post_func();
//        }
//        function submit(){
//          button_submit.click();
//        }
//      }),1000);
      var pn_message = document.getElementsByClassName('message')[0];
      pn_message = pn_message.parentNode.insertBefore(pn_message.cloneNode(),pn_message.nextSibling);
      var a_update = pn_message.parentNode.insertBefore(pn_message.cloneNode(),pn_message.nextSibling);
      if (pn_message) {
        pn_message.innerHTML = '<div></div><div></div><div></div><div></div>';
        var pn_hist   = pn_fifo_factory(pn_message.childNodes[0]);
        var pn_makeThread = pn_fifo_factory(pn_message.childNodes[1]);
        var pn_prune  = pn_fifo_factory(pn_message.childNodes[2]);
        var pn_errors = pn_fifo_factory(pn_message.childNodes[3]);
      }
      var xhr_reqs = new Map();
      var xhr_frees = [];
//      var a_update = document.getElementsByClassName('a_update')[0];
      var post_func = DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(function(){
        var all_data = queue.shift();
        var op = all_data.op;
        var post = all_data.post;
        if (post) {
          if (post.no===op) post.new_thread = true; // patch for bug.
          var data = {type:'json', action:(post.new_thread)? 'makeThread' : (post.pruned)? 'prune' : 'post', board:post.board.slice(1,-1), thread:op, body:queue.length+': '+(post.com||'').replace(/&#039;/g,'\''), subject:post.sub, debug:op};
          if (post.com && post.com.indexOf(' is ')!=-1) data.tag = post.com.substr(post.com.indexOf(' is ')+4).split(/[\W]/)[0];
          var pn = document.createElement('div');
          var key = data.action+'/'+data.board+'/'+data.thread;
//          pn_messages.set(pn,key);
//          var xhr_events = xhr_events_factory(pn);
          var xhr = xhr_frees.shift() || new_xhr();
          xhr_reqs.set(xhr,{pn:pn, data:data});
          xhr.open('POST', '//localhost/post.php', true);
          xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
          xhr.send(EncodeHTMLForm(data));
          pn.textContent = timestamp()+': '+key+(data.tag? ' '+data.tag:'');
          var old = pn_hist(pn);
          if (old)
            if (old.textContent.indexOf('OK.')===-1) pn_errors(old);
            else if (old.textContent.indexOf('prune')!==-1) pn_prune(old);
            else if (old.textContent.indexOf('makeThread')!==-1) pn_makeThread(old);
        }
          function new_xhr(){
            var xhr = new XMLHttpRequest();
            xhr.addEventListener('load',  xhr_events, false);
            xhr.addEventListener('error', xhr_events, false);
            xhr.addEventListener('abort', xhr_events, false);
            xhr.responseType = 'json';
            return xhr;
          }
          
//          function xhr_events_factory(pn_in) {
//            var pn = pn_in;
//            return function(e) {
          function xhr_events(e) {
            var xhr = e.target;
            var myobj = xhr_reqs.get(xhr);
            var pn = myobj.pn;
            var data = myobj.data;
            var str = ((xhr.status==200)? xhr.response && xhr.response.message : xhr.status) || '';
            var timestamps = (xhr.status==200)? xhr.response && xhr.response.timestamps : null;
            var label_elapsed = false;
            if (timestamps) str += timestamps.reverse().map((v,i,a)=> !i? v-a[a.length-1] : a[i-1]-v).map(v=>' '+(!label_elapsed? (label_elapsed = true, 'Elapsed: '+v+' sec = '):'')+(1/v)+' operations/sec').join();
            var str2 = pn.textContent;
            var str2_idx = str2.indexOf(': ')+2;
            str2 = [str2.slice(0,str2_idx), str2.slice(str2_idx)];
            //            str2[1] = str2[1].replace(/(makeThread|post|prune)/,'');
            str = str && str.replace(/operations\/sec/g,'ops/s').split(' ').map(function(v,i,a){
              return a[i+1] && a[i+1].search(/(ops\/)*s/)!=-1? v.slice(0,6)
                : v.length>1 && str2[1] && str2[1].indexOf(v.slice(0,-1))===0? ''
              : v;}).join(' ');
  //            if (str===null) str = 'null: '+data.action;
            pn.textContent = str2[0]+ timestamp() +': '+ str + ', '+str2[1];
//              pn_messages.delete(pn);
  //            if (xhr.status==200 && localStorage) localStorage['newPost'] = xhr.response.no;
            a_update.textContent = data.thread+'/'+queue.length;
  //            a_update.click(undefined, op); // fail to pass;
            if (data.board===site.board.slice(1,-1) && data.action!=='prune') a_update.click();
            if (queue.length>0) post_func(pref.test_mode.num || 500);
            xhr_reqs.delete(xhr);
            xhr_frees.push(xhr);
//            }
          }
          function EncodeHTMLForm(data) {
            var data_out = [];
            var i=0;
            for(var key in data) data_out[i++] = encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
            return data_out.join('&').replace(/%20/g,'+');
          }
//        }
      },500));
      function timestamp(){
        var time = new Date();
        return time.toLocaleTimeString() + '.' + ('00' + time.getMilliseconds()).slice(-3);
      }
      function pn_fifo_factory(pn){
        var filled = false;
        return function(pn_add){
          pn.insertBefore(pn_add,pn.firstChild);
          if (filled) return pn.removeChild(pn.lastChild);
          else filled = pn.childNodes.length>=10;
          return null;
        };
      }
      return function entry_func(post, op){
        if (typeof(post)==='string') queue = queue.concat({post:{board:'/'+post.split('/')[1]+'/', pruned:true}, op:post.split('/')[2]});
        else if (post) queue = queue.concat({post:post, op:op});
        post_func(pref.test_mode.num || 500);
      }
    })(),
    features : {catalog: true},
    parse_funcs: {
      'catalog_json': {
        ths: function(obj, parse_obj) {
          var ths = [];
          var page_base = parseInt(parse_obj.page || 0, 10);
          for (var j=0;j<obj.length;j++) {
            var th = Object.create(obj[j]);
            obj[j].board = obj[j].board? '/' + obj[j].board +'/' : parse_obj.board; // for both virtual and physical
            th.page = (page_base + Math.floor(j/15)) + '.' + j%15;
            th.posts = [obj[j]];
            if (th.stickyTags) th.tags_OP = th.stickyTags.split(' ').map(v=>'#'+v);
            th.tags = th.tags.split(' ').map(v=>'#'+v);
            obj[j].__proto__ = parse_obj;
            ths[ths.length] = th;
          }
          return ths;
        },
        op_img_url: function(){return '/distchan.png';},
        key: function(th){return th.domain+th.board+th.no;},
        has_posts: false,
        __proto__: site2['meguca'].parse_funcs.catalog_json
      },
      'thread_json': {
        ths: function(obj, parse_obj){
          obj[0].board = '/' + obj[0].board +'/';
          obj[0].__proto__  = parse_obj;
          return [{key: parse_obj.domain + parse_obj.board + obj[0].no, posts:obj, nof_posts:obj.length, __proto__:obj[0]}];
//          return site2['DEFAULT'].parse_funcs['thread_json'].ths({posts:obj, nof_posts:obj.length, board:'/'+obj[0].board+'/'}, parse_obj);
        },
        __proto__: site2['meguca'].parse_funcs.thread_json
      },
      'page_json': {
        __proto__: site2['meguca'].parse_funcs.page_json
      },
      'catalog_html': {
        ths_hook: undefined, // conceals ths_hook in 4chan
        ths: function(doc){
          var obj = JSON.parse(doc.pn.getElementById('json-data').textContent);
          return site2['dist'].parse_funcs['catalog_json'].ths(obj, site2['dist'].wrap_to_parse.prep_pfunc(doc.domain, doc.board, 'catalog_json'));
        },
        tn_imgs: function(th){
          return th.pn && Array.prototype.slice.call(th.pn.querySelectorAll('a>img, a>video')) || [];
        },
        __proto__: site2['4chan'].parse_funcs['catalog_html']
      },
      'page_html': {
        __proto__: site2['4chan'].parse_funcs['page_html']
      },
      'thread_html': {
        ths: function(doc){ // ERROR in test_mode['182'], com getter fail to overwrite itself, see site2['meguca2'].parse_funcs.post_json_template
          var obj = JSON.parse(doc.pn.getElementById('json-data').textContent);
          var ths = site2['dist'].parse_funcs['thread_json'].ths(obj, site2['dist'].wrap_to_parse.prep_pfunc(doc.domain, doc.board, 'thread_json'));
//          ths[0].pn = document.getElementById('threads').appendChild(cnst.dom('<div></div>'));
          return ths;
        },
        get_omitted_info: function(post){
          var omit_info = post.pn.nextSibling && post.pn.nextSibling.lastChild;
          return (omit_info && omit_info.classList.contains('summary'))? omit_info : undefined;
        },
        set_omitted_info: function(post, info){post.pn.nextSibling && post.pn.nextSibling.appendChild(info);},
        __proto__: site2['4chan'].parse_funcs['thread_html']
      },
      'catalog_json_template': {
        get sub(){return this.subject;},
        __proto__: site2['meguca2'].parse_funcs['catalog_json_template']
      },
      'page_json_template': {
        pn: undefined,
        get sub(){return this.subject;},
        __proto__: site2['meguca2'].parse_funcs['catalog_json_template']
      },
      'thread_json_template': {
        get sub(){return this.subject;},
        __proto__: site2['meguca2'].parse_funcs['thread_json_template']
      },
      'post_json_template': {
        pn: undefined,
        get sub(){return this.subject;},
        __proto__: site2['meguca2'].parse_funcs['post_json_template']
      },
      __proto__: site2['meguca'].parse_funcs
    },
    catalog_json2html3: site2['4chan'].catalog_json2html3,
//    catalog_json2html3: site2['meguca'].catalog_json2html3,
    catalog_native_prep: function(pn_filter,pn_tb,pn_hi, clg){
      /*clg.view = clg.pref.INST.view =*/ clg.set_view_class(-1, site.view);
//      pn_filter.getElementsByTagName('SELECT')['INST.view'].value = site.view;
      if (clg.mode==='float') return;
      var ctrl = document.getElementById('ctrl');
//      if (!pref.test_mode['194']) pn_tb.childNodes[3].style.float = 'left';
      pn_tb.style.float = 'right';
      ctrl.appendChild(pn_tb.childNodes[3]);
      ctrl.appendChild(pn_tb);
      var settings = ctrl.appendChild(cnst.dom('<div style="float:right"><span>Sort By:</span><span> View:</span></div>'));
      var ordering = pn_filter.getElementsByTagName('SELECT')['catalog.order.ordering'];
      ordering.parentNode.removeChild(ordering.nextSibling); // remove <br>
      settings.childNodes[0].appendChild(ordering);
      settings.childNodes[1].appendChild(pn_filter.getElementsByClassName('viewSel')[0]);
      ctrl.appendChild(pn_filter);
    },
    catalog_get_native_area: function(){
      return document.getElementById('threads');
    },
    page_json2html3_prep_omitted_info: function(posts0, th, force_expander) { // from 4chan but removed 4chan's url by changing img to span
      var omit_info = this.parse_funcs['page_html'].get_omitted_info(posts0);
      if (!omit_info) {
        omit_info = document.createElement('span');
        omit_info.setAttribute('class','summary');
//        omit_info.innerHTML = '<div class="postLink mobile"><a href="'+site2[th.domain].link_dbtp2href_abs([th.domain, th.board, th.no, th.no])+'" target="_blank" class="button">View Thread</a></div>' + // patch
//        omit_info.innerHTML = '<span class="extButton expbtn" title="Expand thread" data-cmd="expand" data-id="' + th.posts[0].no + '">[+]</span>' +
        omit_info.innerHTML = '<span></span></span>';
        this.parse_funcs['page_html'].set_omitted_info(posts0,omit_info);
      }
      if (force_expander) omit_info.appendChild(cnst.config_expander(th.key));
      return omit_info.childNodes[0];
    },
    post_json2html_file: function(post, board, tn_w, tn_h, dummy, pftn) {
      var multifile = post.extra_files? pref.cpfx+'multifile' : '';
      var html = site2['4chan'].post_json2html_file(post, board, tn_w, tn_h, multifile);
      if (post.extra_files && pftn.s2) for (var i=0;i<post.extra_files.length;i++) html += site2['4chan'].post_json2html_file({no:post.no+'_'+i, __proto__:post.extra_files[i]}, board, pftn.w2, pftn.h2, multifile);
      return html;
    },
    catalog_json2html3_file : function(obj,thumb_url, tn_w, tn_h, pftn) {
      var html = site2['4chan'].catalog_json2html3_file(obj, thumb_url, tn_w, tn_h);
      if (obj.extra_files && pftn.s2)
        for (var i=0;i<obj.extra_files.length;i++) html += site2['4chan'].catalog_json2html3_file({no:obj.no+'_'+i, __proto__:obj.extra_files[i]}, site2[obj.domain].catalog_json2html3_thumbnail(obj.extra_files[i],obj.board), pftn.w2, pftn.h2);
      return html;
    },
    __proto__: site2['4chan'], // proto: '4chan',
  };
  Object.defineProperty(site2['dist'].parse_funcs['page_json'],'ths',{value:site2['dist'].parse_funcs['catalog_json'].ths, configurable:true, enumerable:true, writable:true});
  Object.defineProperty(site2['dist'].parse_funcs['page_html'],'set_omitted_info',{value:site2['dist'].parse_funcs['thread_html'].set_omitted_info, configurable:true, enumerable:true, writable:true}); // patch
  Object.defineProperty(site2['dist'].parse_funcs['page_html'],'get_omitted_info',{value:site2['dist'].parse_funcs['thread_html'].get_omitted_info, configurable:true, enumerable:true, writable:true}); // patch
  site2['rssn'] = { // rssnews = rss + distchan
    nickname: 'rssn',
    domain_url: window.location.href.indexOf('rssnews.sakura.tv')!=-1? 'rssnews.sakura.tv' : 'localhost',
    boards_json: undefined, // {boards:[{board:'news', pages:1}]},
    url_boards_json: function(){return [site.protocol + '//' + this.domain_url + '/' + 'boards.json', 'json'];},
    postprocess_board: function(val){
      for (var b in val) {
        var bd = liveTag.mems.init({domain:this.nickname, board:'/'+b+'/'});
      }
    },
    features: {setting2:false, postform:false},
    nativeVirtualBoard: true,
    check_func: function(href){ // must be owned.
//      site0.add_domain(this.nickname);
      if (href.indexOf('192.168.')!==-1) this.domain_url = href.replace(/^https*:\/\//,'').replace(/\/.*/,'');
      if (href.indexOf(this.domain_url)===-1) return false;
      if (pref.test_mode['208'] && !CCAPI.native && href.search(/[\?&]cc([^=]|$)/)===-1) {CCAPI.suppress = true; pref_func.apply_prep_save = function(){}; return false;} // override pref_func.apply_prep_save to void, see pref_func.set_event_target
      var config = JSON.parse((document.getElementById('json-config')||{}).textContent||null)||{};
      site.view = config.view;
      site.whereami = (config.view==='headline'? 'catalog' : config.view) || 'other';
      site.config(this.domain_url,this.nickname);
      if (site.board==='/about/') {
        var hrefs = href.split('/');
        var bt = hrefs.slice(hrefs.indexOf('about')+1);
        site.board = '/'+decodeURI(bt[0])+'/';
        if (config.view==='thread') site.no = parseInt(bt[1],10);
        site.domain2 = 'dist';
      } else site.board = '/'+config.board+'/';
      site.postform = document.getElementsByClassName('postForm')[0];
//      site.proxy = '//'+this.domain_url+'/proxy/';
      site.proxy = '//'+this.domain_url+'/proxy.php?url=';
      site.embed_to = {
        top:function(){return document.getElementById('content').previousElementSibling;},
        bottom:function(){return document.getElementById('botFooter');}
      };
      site.nativeVirtualBoard = this.nativeVirtualBoard;
      if (pref.test_mode['195'] && site.whereami==='other') {
        setTimeout(function(){
          var tables = document.querySelectorAll('form>table');
          for (var i=0;i<tables.length;i++) cnst.set_tack_float2(tables[i]);
        },0);
      }
      site.hasViewSel = true;
      if (!pref.test_mode['206']) site2['rss'].dnd_rss();
      return true;
    },
//    dist_boards: ['/operate/'],
    make_url4 : function(dbt, trim_page){
      return site.domain2? site2[site.domain2].make_url4(dbt, trim_page)
        : dbt[0]===site.nickname && dbt[1]==='/_blank/'? null
        : [site.protocol + '//' + this.domain_url + dbt[1] + 'catalog.json', 'json', dbt[0]+dbt[1]+'j0']; // virtual board
//      var d = this.dist_boards.indexOf(dbt[1])===-1? 'rss' : 'dist';
//      return site2[d].make_url4(dbt, trim_page) ||
//        d==='rss' && [site.protocol + '//' + this.domain_url + dbt[1] + 'catalog.json', 'json', dbt[0]+dbt[1]+'j0'] || null; // virtual board

    },
    get board2rss_options(){return site2['rss'].board2rss_options;},
    catalog_native_prep: function(pn_filter,pn_tb,pn_hi, clg){
      this.__proto__.catalog_native_prep(pn_filter,pn_tb,pn_hi, clg);
      var pn_rss = document.getElementById('json-rss');
      if (!pn_rss) return;
      var rss_list = JSON.parse(pn_rss.textContent);
      if (rss_list.length==0) return;
      if (!CCAPI.blocked_by_disclaimer) this.catalog_native_prep_rss(rss_list);
      else {
        pClg.auto_update.stop_if_running();
        CCAPI.restart_from_disclaimer = this.catalog_native_prep_rss.bind(this,rss_list, true);
      }
      var sel = document.getElementById('styleSelector');
      if (sel) sel.addEventListener('change',styleSheet.make_styles,false);
    },
    catalog_native_prep_rss: function(rss_list, restart){
//      if (!pref[pClg.mode].env.refresh_initial) setTimeout(function(){pClg.refresh(true, true, true);},0);
      if (pref.catalog_board_list_sel===0) {
//        liveTag.tags_reserved_init(['#'+site.board.slice(1,-1)],true);
        cataLog.scan_boards_keyword_callback2(this.nickname+','+site.board+',j0,catalog_json',
                                              {date:Date.now(), status:200, response:rss_list},
                                              ['rss_list',{refresh:pClg, __proto__:cataLog.scan_boards_keyword_callback2_default_args}]);
//      cataLog.scan_boards.scan_init('rss_list_scan', [], {refresh:pClg,
//                                                          callback: function(){scan.scan_refresh(null, [], 4, {});},
////                                                          indicator: null, from_auto:from_auto, load_on_demand:pref[mode].load_on_demand,
//                                                          priority:4});
      }
//      else {
//        var keys = pref.catalog_board_list_obj[pref.catalog_board_list_sel];
//        for (var i=1;i<keys.length;i++) {
//          var dbt = cnst.name2domainboardthread(keys[i].key,true);
//          liveTag.mems.init({domain:dbt[0], board:dbt[1]});
//        }
//      }
      if (restart && pref[pClg.mode].auto_update) pClg.auto_update.restart();
    },
    catalog_board_list_obj_dynamic : function(){ // pick up selected boards from boards.html
      if (site.board!=='/_blank/' || window.location.href.indexOf('_blank')!==-1) return;
      var pn_rss = document.getElementById('json-rss');
      if (!pn_rss) return;
      var rss_list = JSON.parse(pn_rss.textContent);
      var bds = [];
      for (var i=0;i<rss_list.length;i++) for (var j=0;j<rss_list[i].length;j++) bds[bds.length] = site2['rss'].add_rss_append([null, rss_list[i][j][2], rss_list[i][j][3]])[0];
      return pref_func.str2obj_replace0(bds);
    },
    parse_funcs:{
      'catalog_json':{proto:'RSS.catalog_json'}, // for virtual board
    },
    favicon : {
      __proto__: site2['DEFAULT'].favicon,
      none: '/favicon.ico',
      reply: '/favicon_nr.ico',
      reply_to_me: '/favicon_nrtm.ico',
    },
    proto: 'dist',
  };
}



if (pref.features.domains['RSS']) {
  site2['RSS'] = { // RSS 1.0
    prohibitThreadScan: true,
    passiveWatch: true, 
    differentAPI: true,
    no_JSONAPI: true,
    max_page: 1,
    trim_list: 'no',
    make_url4: function(dbt){
      var lth = liveTag.mems[dbt[0]] && liveTag.mems[dbt[0]][dbt[1]] && liveTag.mems[dbt[0]][dbt[1]][dbt[2]]; 
      if (lth && lth.th) return [lth.th.link, 'html'];
//      if (dbt[3].indexOf('catalog')!=-1) return null;
      var rss_options = site2[dbt[0]] && site2[dbt[0]].board2rss_options[dbt[1]];
      var url = rss_options && rss_options[0][dbt[2]];
      if (!url) return null;
      if (this.rss2dbpEtag && this.rss2dbpEtag[url] && this.rss2dbpEtag[url][2] && this.rss2dbpEtag[url][2][0]==418) return null;
      var opt = rss_options[1];
      var opt_req = {xhr_local:true};
      var allowed = ['overrideMimeType','responseType','xhrPatchFunc'];
      for (var i=0;i<allowed.length;i++) if (opt[allowed[i]]) opt_req[allowed[i]] = opt[allowed[i]];
      //      var url = dbt[0]===this.directory? this.board2rss_options[dbt[1]][0][dbt[2]] : this.url_rss[dbt[2]] || dbt[2]==0 && this.home;
      dbt[3] = dbt[3].replace(/_json/,'_html');
      return [(opt && opt.directConnection? '' : site.proxy||'')+url, 'xml', dbt[0]+dbt[1]+(dbt[3].indexOf('catalog')!=-1?'c':'p')+dbt[2], opt_req];
//              opt && opt.overrideMimeType? {overrideMimeType:opt.overrideMimeType, xhr_local:true} : {xhr_local:true}];
//      return url? [(site.proxy||'')+url, 'xml', dbt[0]+dbt[1]+'p'+dbt[2]] : null;
//      return url? [url, 'xml', dbt[0]+dbt[1]+'p'+dbt[2]] : null;
    },
    link_dbtp2href_abs: function(dbtp, quote){
      var url = this.make_url4(dbtp);
      return url && url[0] || '';
    },
    XMLParserRE: function(xml){return new site2['BBC'].HTMLParser(xml);}, // for memory leak
    parse_funcs: {
      'page_html': {
        db2pnos: {},
        add_nos: function(retval, db,pg){
          if (this.db2pnos[db]) {
            for (var p in this.db2pnos[db]) if (p!=pg) {
              var nos = this.db2pnos[db][p];
              if (nos) for (var i=0;i<nos.length;i++) retval[nos[i]] = null;
            }
          }
          return retval;
        },
        ths: function(doc){
          var nos = [];
          var ths = [];
          var items = doc.pn.getElementsByTagName('item');
          for (var i=0;i<items.length;i++) {
//            var pn_link = items[i].getElementsByTagName('link')[0];
            ths[ths.length] = {
              pn_org: items[i],
              page: (doc.page||0) + '.' + i,
//              link: pn_link && pn_link.textContent,
              __proto__: doc.__proto__};
            nos[i] = ths[i].no;
          }
//          if (doc.type_source==='catalog') this.clean_collisions(ths, doc);
          var db = doc.domain+doc.board;
          if (!this.db2pnos[db]) this.db2pnos[db] = {};
          this.db2pnos[db][doc.page||0] = nos;
          return ths;
//          return this.ths_array(doc, doc.pn.getElementsByTagName('item'));
        },
        isPermaLink: function(th){
          var pn = th.pn_org.getElementsByTagName('guid')[0];
          return pn && pn.getAttribute('isPermaLink');
        },
        guid: function(th){
          var pn = th.pn_org.getElementsByTagName('guid')[0];
          return pn && pn.textContent || '';
        },
        link: function(th){
          var pn = th.pn_org.getElementsByTagName('link')[0];
          return pn && pn.textContent || '';
        },
        sub: function(th){
          var pn = th.pn_org.getElementsByTagName('title')[0];
          return pn && pn.textContent || '';
        },
        time: function(th){ // https://www.dublincore.org/specifications/dublin-core/dcmi-terms/#
          var pn = th.pn_org.getElementsByTagNameNS('http://purl.org/dc/elements/1.1/', 'date')[0]; // getElementsByTagName('dc:date') returns []
          return pn && Date.parse(pn.textContent);
        },
        time_pubDate: function(th){
          var pn = th.pn_org.getElementsByTagName('pubDate')[0];
          return pn && Date.parse(pn.textContent);
        },
        name: function(th){
          var pn = th.pn_org.getElementsByTagNameNS('http://purl.org/dc/elements/1.1/', 'creator')[0];
          return pn && pn.textContent || '';
        },
        com: function(th){
          var pn = th.pn_org.getElementsByTagName('description')[0];
          return pn? pn.textContent : '';
//          var pne = this.isTrusted && th.pn_org.getElementsByTagNameNS('http://purl.org/rss/1.0/modules/content/','encoded')[0];
//          return (pn? pn.textContent : '') + (pne? pne.textContent : '');
        },
//        com_content_encoded: function(th){
//          var pne = this.isTrusted && th.pn_org.getElementsByTagNameNS('http://purl.org/rss/1.0/modules/content/','encoded')[0];
//          return pne? pne.textContent : '';
//        },
        tags: function(th){
          return this.tags_extract(th.pn_org.getElementsByTagName('category'));
        },
        tags_dc: function(th){
          return this.tags_extract(th.pn_org.getElementsByTagNameNS('http://purl.org/dc/elements/1.1/', 'category'));
        },
        tags_extract: function(pns){
          var tags = [];
          for (var i=0;i<pns.length;i++) if (pns[i].textContent) {
            var tag = '#'+pns[i].textContent.replace(/[<>"']/g,'').replace(/\s*&\s*(\w)/g,function(m,p1){return p1.toUpperCase();}).replace(/[\s&\.]/g,'');
//            var tag = '#'+pns[i].textContent.replace(/[<>"']/g,'').replace(/&([^\s])/,'$1').replace(/\s+([\w&])/g,function(m,p1){return p1.toUpperCase();});
            var tags_translated = this.dic_tags[tag];
            if (!tags_translated && (tag in this.dic_tags)) continue; // deletion
            if (Array.isArray(tags_translated)) for (var i=0;i<tags_translated.length;i++) tags[tags.length] = tags_translated[i];
            else tags[tags.length] = tags_translated || tag;
          }
          return tags.length>0? tags : null;
        },
        no: function(th){ // needs to save dic for filter (not implemented yet). Solution will be changing fullname to domain/board/url.
          var linkTime = th.link + th.time;
          var no = this.hash_collisions[linkTime] && this.hash_collisions[linkTime][0] || this.hashCode(linkTime); //  & 0xff; // for debug
          var lt;
          while (lt = th.dic_no2linkTime[no]) if (lt===linkTime) return no; else {no++; var col = true;}
          if (col) this.register_collision(th, linkTime, no);
          if (col) console.log('WARNING: hash collision: '+no+', '+linkTime);
//            var dic_linkTime = th.dic_no2linkTime[no];
//            if (dic_linkTime) {
//              if (dic_linkTime[0]===link && dic_linkTime[1]===time) return no;
//              console.log('WARNING: hash collision: '+no+', '+link+', '+time+', '+dic_linkTime[0]+', '+dic_linkTime[1]);
//              while (dic_linkTime = th.dic_no2linkTime[++no]) if (dic_linkTime[0]===link && dic_linkTime[1]===time) return no;
//              this.register_collision(th, no, linkTime);
//            }
          th.dic_no2linkTime[no] = linkTime;
//          var no = this.hashCode(link); // miss updated articles with the same url
//          var dic_link = th.dic_no2link[no];
//          if (dic_link===link) return no;
//          if (dic_link) {
//            console.log('WARNING: hash confliction: '+no+', '+link+', '+dic_link);
//            while (dic_link = th.dic_no2link[++no]) if (dic_link===link) return no;
//          }
//          th.dic_no2link[no] = link;
          return no;
        },
        filename: function(th){
//          th.extracted = true; // for multientry from prepare_html_extract_params. this function is also used for "preparing all other params" // May be bug, posts are not parsed.
          var pn = th.pn_org.getElementsByTagName('enclosure')[0];
          var url = pn && pn.getAttribute('url');
          if (!url) return undefined;
          var idx = url.lastIndexOf('.');
          Object.defineProperty(th, 'ext', {value: url.slice(idx), writable:true, configurable:true, enumerable:true});
//          th.ext = url.slice(idx);
          return url.slice(0,idx);
//          return Object.defineProperty(th, 'ext', {value: url.slice(idx), writable:true, configurable:true, enumerable:true}).ext;
        },
        filename_from_txt: function(th){
//          th.extracted = true; // for multientry from prepare_html_extract_params. this function is also used for "preparing all other params" // May be bug, posts are not parsed.
          var pn = th.pn_org.getElementsByTagName('description')[0];
          if (!pn) return undefined;
          var txt = pn.textContent;
          var re = /(https*:\/\/[^\s]*?)\.(jpg|jpeg|png|mp4)/g;
          var r;
          var count = 0;
          while (r = re.exec(txt)) {
            if (count++==0) {
              var filename = r[1];
              Object.defineProperty(th, 'ext', {value: '.'+r[2], writable:true, configurable:true, enumerable:true});
//              th.ext = '.'+r[2];
            } else {
              if (count===2) th.extra_files = [];
              th.extra_files[th.extra_files.length] = {filename:r[1], ext:'.'+r[2], __proto__:th.__proto__}; // extraction is done after wrap_to_parse.post, see prepare_html_prep_posts
            }
            if (count>=this.max_files) break;
          }
          return filename;
        },
        nof_files: function(th){return th.filename? 1 : 0;},
        op_img_url: function(th){return th.filename? th.filename+th.ext : site2['RSS'].icon;},
        get_op_src: function(th, img){return img.src;},
        sticky: function(th){return false;},
        nof_posts: function(th){return 1;},
        time_unit: 1,
        get_max_page: function(){},
        hashCode: function(str){ // https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
          var hash = 0;
          for (var i=0;i<str.length;i++) {
            hash = ((hash<<5)-hash)+str.charCodeAt(i);
            hash = hash & hash; // Convert to 32bit integer
          }
          return hash & 0x7fffffff; // Convert to >=0
//          return hash;
        },
        sanitize_lv: (function(){
          var pn = document.createElement('div');
          return function(str,lv){
            if (lv>=4) return str;
            if (lv===0) return pref_func.sanitize(str);
            pn.innerHTML = str;
            var elems = pn.getElementsByTagName('*');
            for (var i=elems.length-1;i>=0;i--) {
              if (elems[i].tagName==='SCRIPT') elems[i].remove();
              if (lv>=3) continue;
              elems[i].removeAttribute('style');
              var attrs = elems[i].attributes;
              for (var j=attrs.length-1;j>=0;j--) if (lv===1 || attrs[j].name.slice(0,2)==='on') elems[i].removeAttribute(attrs[j].name); // https://html.spec.whatwg.org/multipage/webappapis.html#event-handlers-on-elements,-document-objects,-and-window-objects
            }
            return pn.innerHTML;
          };
//          return function(str,lv){ // working code
//            if (lv>1) return str;
//            if (lv==1) {
//              pn.innerHTML = str;
//              var scs = pn.getElementsByTagName('script');
//              for (var i=scs.length-1;i>=0;i--) scs[i].remove();
//              return pn.innerHTML;
//            } else return pref_func.sanitize(str);
//          };
        })(),
        rm_cached_vals: function(th){
          delete th.com;
//          delete th.sub; // requires these if trust level is NOT fixed to 0.
//          delete th.name;
//          delete th.filename;
        },
        isTrusted: 0,
//        design: 0,
        dic_tags: {},
        dic_funcs: null, // initialized in site2['rss']
        hash_collisions: {},
        register_collision: function(th, linkTime, no){
          var src = site2[th.domain].board2rss_options[th.board][0][th.page.split('.')[0]].replace(/^(https*:)*\/\//,'');
          if (no===undefined) delete this.hash_collisions[linkTime];
          else this.hash_collisions[linkTime] = [no, src, th.board, th.domain];
//          var h = this.hash_collisions;
//          var obj = Object.keys(this.hash_collisions).reduce((a,c)=>((a[h[c[1]]]||(a[h[c[1]]]={}))[c]=h[c[0]],a),{});
          if (localStorage) localStorage[pref.script_prefix+'.RSS.collisions'] = JSON.stringify(this.hash_collisions);
        },
        hash_collisions_init: function(){
          function load(e){
            var key = pref.script_prefix+'.RSS.collisions';
            try {
              if (!e || e.key===key) site2['RSS'].parse_funcs['page_html'].hash_collisions = JSON.parse(localStorage[key]||'{}');
            } catch(e){}
          }
          load();
          window.addEventListener('storage', load, false);
        },
        clean_collisions: function(ths, nos){ // nos is nos_recovered in 'rm_items_404_check'.
          var cols = this.hash_collisions;
          if (Object.keys(cols).length==0) return;
          var th0 = ths[0];
          var src = site2[th0.domain].board2rss_options[th0.board][0][th0.page.split('.')[0]].replace(/^(https*:)*\/\//,'');
//          var nos = ths.reduce((a,c)=>(a[c.no]=null, a),{});
          for (var lt in cols) if (cols[lt][2]===th0.board && cols[lt][3]===th0.domain && cols[lt][1]===src && !(cols[lt][0] in nos)) {
//          var linkTimes = ths.reduce((a,c)=>(a[c.link+c.time]=true, a),{});
//          for (var lt in cols) if (cols[lt][2]===src && !(lt in linkTimes)) {
            console.log('release collision: ', cols[lt][3]+cols[lt][2]+cols[lt][0], cols[lt][1]);
            this.register_collision(th0, lt);
          }
          for (var no in th0.dic_no2linkTime) if (!(no in nos)) delete th0.dic_no2linkTime[no];
        },
        dic_no2linkTime_init: function(doc){
          var dic_no2linkTime = {};
          for (var lt in this.hash_collisions) {
            var col = this.hash_collisions[lt];
            if (col[2]===doc.board && col[3]===doc.domain) dic_no2linkTime[col[0]] = lt;
          }
          return dic_no2linkTime;
        },
        proto: 'dist.catalog_html' // 'slad.catalog_html'
      },
      'page_html_template': {
        get guid_def(){return this.parse_funcs.__proto__.__proto__.guid.call(this.parse_funcs, this);},
        get isPermaLink_def(){return this.parse_funcs.__proto__.__proto__.isPermaLink.call(this.parse_funcs, this);},
        get link_def(){return this.parse_funcs.__proto__.__proto__.link.call(this.parse_funcs, this);},
        get guid(){return this.exe_sub('guid');},
        get isPermaLink(){return this.exe_sub('isPermaLink');},
        get link(){return this.exe_sub('link');},
        get sub_def(){return this.parse_funcs.__proto__.__proto__.sub.call(this.parse_funcs, this);},
        get time_def(){return this.parse_funcs.__proto__.__proto__.time.call(this.parse_funcs, this);},
        get time_pubDate_def(){return this.parse_funcs.__proto__.__proto__.time_pubDate.call(this.parse_funcs, this);},
        get name_def(){return this.parse_funcs.__proto__.__proto__.name.call(this.parse_funcs, this);},
        get com_def(){return this.parse_funcs.__proto__.__proto__.com.call(this.parse_funcs, this);},
        get tags_def(){return this.parse_funcs.__proto__.__proto__.tags.call(this.parse_funcs, this);},
        get no_def(){return this.parse_funcs.__proto__.__proto__.no.call(this.parse_funcs, this);},
        get filename_def(){return this.parse_funcs.__proto__.__proto__.filename.call(this.parse_funcs, this);},
        exe_sub_sanitize: function(prop, lv) {
          var pf = this.parse_funcs;
          return Object.defineProperty(this,prop,{value:pf.sanitize_lv(pf[prop](this),lv), enumerable:true, configurable:true, writable:true})[prop];},
        get com(){return this.exe_sub_sanitize('com',this.parse_funcs.isTrusted);},
        get sub(){return this.exe_sub_sanitize('sub',this.parse_funcs.isTrusted? 1:0);},
        get name(){return this.exe_sub_sanitize('name',0);},
        get filename(){return this.exe_sub_sanitize('filename',0);},
//        get com() {
//          var pf = this.parse_funcs;
//          var com_s = pf.sanitize_lv(pf.com(this), pf.isTrusted);
//          return Object.defineProperty(this,'com',{value:com_s, enumerable:true, configurable:true, writable:true}).com;},
        get time_created(){return this.time;},
        get time_posted(){return this.time;},
        get time_bumped(){return this.time;},
        get time_tu1000(){return this.time/1000;},
        get tags(){return this.exe_sub('tags');},
        get filename(){return this.exe_sub('filename');},
        get ext(){return this.exe_sub('filename')? this.ext : '';},
        get nofFiles(){return !this.filename? 0 : (!this.extra_files)? 1 : this.extra_files.length+1;}, // copied from vichan.json_template
//        nof_posts: 1,
//        sticky: false,
//        get link(){
//          var pn = this.pn.getElementsByTagName('link')[0];
//          return pn && pn.textContent;
//        },
        get posts(){return [this];},
        tn_w: 250,
        tn_h: 250,
//        tn_wh: 250,
        size_from_img: (function(){
//        func_resize: (function(){
          function set_size(tgt, tn_w, tn_h, w, h){
            tgt.tn_w = tn_w;
            tgt.tn_h = tn_h;
            tgt.w = w;
            tgt.h = h;
          }
          return function(img, clg, nw, nh){ // onload(clg,e){
            var post = this;
            if (post.extra_files) for (var i=0;i<post.extra_files.length;i++)
              if (post.extra_files[i].filename+post.extra_files[i].ext===img.src) {post = post.extra_files[i]; break;}
            var fw = nw/250; // size of thumbnail is calculated to fit to 250x250 as 4chan
            var fh = nh/250;
            var f = fw>fh? fw : fh;
            if (f<1) f = 1;
            var tn_w = f===fw? 250 : nw/f;
            var tn_h = f===fh? 250 : nh/f;
//            var ecT = e.currentTarget;
//            ecT.onload = null; // release bound function(myself)
//            clg.image_resize(ecT, true);
////            clg.image_resize_1(ecT,post.tn_wh,post.tn_wh);
////            ecT.style.width = '';
////            ecT.style.height = '';
//            var tn_w = ecT.getAttribute('width');
//            var tn_h = ecT.getAttribute('height');
//            var w = ecT.naturalWidth || ecT.videoWidth;
//            var h = ecT.naturalHeight || ecT.videoHeight;
            if (tn_w!=post.tn_w || tn_h!=post.tn_h || nw!=post.w || nh!=post.h) {
              var p = post;
              var d;
              do {
                if (p.hasOwnProperty('filename') || (d = p.hasOwnProperty('domain'))) set_size(p, tn_w, tn_h, nw, nh); // 'filename' for ripping popup, 'domain' for seeking pfunc
                if (d) break;
              } while (p = p.__proto__);
//              while (post && !post.hasOwnProperty('no')) post = post.__proto__; // rip if popup
//              if (!post) return;
//              set_size(post, tn_w, tn_h, w, h);
//              while (post && !post.hasOwnProperty('domain')) post = post.__proto__; // seek to pfunc
//              if (!post) return;
//              set_size(post, tn_w, tn_h, w, h);
            }
            var after_func = site2[post.domain_html][clg.view+'_after_onload'];
            if (after_func) after_func(img, post);
//            set_size(post.__proto__.__proto__.__proto__, tn_w, tn_h, w, h);
          };
//          return function(clg){
//            return onload.bind(this,clg);
//          };
////          return function(pn){
////            var img = pn.getElementsByTagName('img')[0];
////            if (img) if (img.complete) onload.call(this, {currentTarget:img});
////            else img.onload = onload.bind(this); // collide with image_resize_onload_add in Clg.prototype.insert_thread_format_th_pn and overwritten.
////          };
        })(),
        proto: 'DEFAULT.common'
      },
      'catalog_html': 'page_html',
      'catalog_html_template': 'page_html_template',
      'catalog_json': {
//        ths_check_duplicates: function(tgts, tgt){
//          var t = common_func.fullname2dbt(tgt);
//          for (var i=0;i<tgts.length;i++) {
//            var sk = typeof(tgts[i])==='string'? tgts[i] : tgts[i].key;
//            var s = common_func.fullname2dbt(sk);
//            if (t[0]==s[0] && t[1]===s[1] && (t[2].replace(/[cjpqh]/,'')||0)==(s[2].replace(/[cjpqh]/,'')||0)) return true;
//          }
//          tgts.push(tgt); // add to scan_refresh_ex_list
//        },
        ths: function(obj, parse_obj, sb){ // obj:[validTime, etag, url, option]
          var clg = sb && sb.refresh || pClg;
          var vbd = parse_obj.board;
          var tag = vbd? '#'+vbd.slice(1,-1) : null;
          var tgts = [];
          var vm = [];
          var now = Date.now()/1000; // TEMPORARILY, SHOULD USE valule.date from callback2
          for (var i=0;i<obj.length;i++) {
            for (var j=0;j<obj[i].length;j++) {
              var o = obj[i][j];
              var dbpEtag = site2['rss'].add_rss_append([null, o[2], o[3]]); // o[3]? JSON.parse(o[3]) : null]);
              if (o[0]===418) { // for rss.html
                dbpEtag[2] = [418, 0];
                pref_func.settings.onchange_funcs['dashboard.rss_update_status'](o[2]);
                continue;
              }
              var tgt = dbpEtag[0];
              vm[vm.length] = tgt.slice(0,tgt.lastIndexOf('/')+1);
              if (pref.catalog.board.ex_list && pref_func.merge_obj5(tgt,pref.catalog.board.ex_list_obj2,null)) continue;
              if (sb.done_list && sb.done_list.test_and_add(tgt)) continue;
//                  if (!sb || !sb.tgts || !this.ths_check_duplicates(sb.tgts, tgt)) { // add to scan_refresh_ex_list
              if (!sb.from_auto || o[0]<now || dbpEtag[1]!==o[1]) {
                tgts[tgts.length] = tgt;
                if (o[1]) dbpEtag[1] = o[1];
              }
              if (i>0) continue;
              var dbp = tgt.split('/');
              if (pref.virtualBoard.vtag==='add') {
                if (tag) {
                  var lbd = liveTag.mems.init({domain:dbp[0], board:'/'+dbp[1]+'/'});
                  if (lbd.btag!==tag && (!lbd.btag2 || lbd.btag2.indexOf(tag)===-1)) liveTag.postprocess_board_add_btag((lbd.btag2||[]).concat(tag),lbd);
                }
              } else if (pref.virtualBoard.vptagsel) clg.liveTag.set_tag_by_program('#'+dbp[1], 'in', true);
            }
            if (i==0 && tgts.length>0) if (tag) if (pref.virtualBoard.vtag==='add'? pref.virtualBoard.vtagsel : pref.virtualBoard.vptagsel) clg.liveTag.set_tag_by_program(tag, 'in', true);
          }
          if (tgts.length>0) cataLog.scan_boards.scan_init(vbd? 'rss_board:'+vbd : 'dnd_rss', tgts, {refresh:sb.refresh, done_list:sb.done_list,
//                                                                                 indicator: null, from_auto:from_auto, load_on_demand:pref[mode].load_on_demand,
                                                                                                priority:4});
          if (vbd) Object.defineProperty(liveTag.mems[parse_obj.domain][vbd],'vm',{value:vm, writable:true, configurable:true});
          return [];
        },
      }
    },
    catalog_json2html3_src: function(obj) {
      return (obj.ext)? (obj.filename.indexOf('http')!==0? this.protocol : '') + obj.filename + obj.ext : '';
    },
    catalog_json2html3_thumbnail: function(obj) {
      return obj.hasOwnProperty('op_img_url') && obj.op_img_url || this.catalog_json2html3_src(obj); // hasOwnProperty is patch.
    },
    icon:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAIAAAD9b0jDAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAKZJREFUSEvtlUsOgCAQQ/XiXh01JCjza4eMO4krgUdpm7C3Y6sfF7T8qyfeV39kNmss3QNBx0EZOg3tdA6dhHLoJSiSHKZvRke4zFXKozsWc9C+me4c8lRo4bgIquPWXGUCBxVxIy4NjbmzWD+o2L5QLEo/2CymXmIRVDdpbGah3jrzv38JFJRZeySWgwoTfmjo9VwpvyXTTMJT7v1hnimi/PnDPoGemX4YfR1utvgAAAAASUVORK5CYII=',
    proto: 'dist', // 'slad',
  };
  if (localStorage) site2['RSS'].parse_funcs['page_html'].hash_collisions_init();

  site2['rss'] = {
    directory: 'rss',
    home: null, // url_rss works well for step, (tested in sputonik)
    parse_funcs: {
      'page_html': {
        ths: function(doc){
//          var pfunc_proto = doc.parse_funcs.__proto__; // site2['RSS'].parse_funcs['page_html'];
////          if (Array.isArray(doc.pn)) return pfunc_proto.ths(doc); // for virtual
          var template = doc; // .__proto__.__proto__.__proto__;
          while (template && !template.hasOwnProperty('board')) template = template.__proto__;
          template.dic_no2linkTime = this.dic_no2linkTime[doc.domain+doc.board] || (this.dic_no2linkTime[doc.domain+doc.board] = this.dic_no2linkTime_init(doc)); // share the same dic between catalog and page
//          if (doc.domain!==site2[doc.domain].directory) while (template && !template.hasOwnProperty('domain')) template = template.__proto__; // seek to template
          var tags = {};
          var items = doc.pn.getElementsByTagName('item');
          for (var i=0;i<items.length;i++) for (var j=0;j<items[i].childNodes.length;j++) tags[items[i].childNodes[j].tagName] = true;
          if (!tags['title']) Object.defineProperty(template,'sub',{value:'', writable:true, configurable:true, enumerable:true});
          if (!tags['dc:creator']) Object.defineProperty(template,'name',{value:'', writable:true, configurable:true, enumerable:true});
          /*if (tags['content:encoded']) Object.defineProperty(template,'com',{get:this.com_content_encoded, configurable:true, enumerable:true});
          else*/ if (!tags['description']) Object.defineProperty(template,'com',{value:'', writable:true, configurable:true, enumerable:true});
          if (tags['dc:category']) Object.defineProperty(template,'tags',{get:this.tags_dc, configurable:true, enumerable:true});
          else if (!tags['category']) Object.defineProperty(template,'tags',{value:[], writable:true, configurable:true, enumerable:true});
          if (tags['pubDate']) Object.defineProperty(template,'time',{get:this.time_pubDate, configurable:true, enumerable:true});
//          if (tags['enclosure']) Object.defineProperty(template,'ext',{get:function(){return this.exe_sub('ext');}, configurable:true, enumerable:true});
          var rss_options = site2[doc.domain].board2rss_options[doc.board];
          template.parse_funcs = this.fmt_opt(rss_options[1]); // format and rip not to come here again
//          var options = site2[doc.domain].board2rss_options[doc.board][1]; // site2[doc.board.slice(1,-1)].rss_options; // ===template.parse_funcs.__proto__;
////          if (options.__proto__!==pfunc_proto) { // format if the first time, share options object between page_html and catalog_html.
//          var o_keys = Object.keys(options);
//          for (var i=0;i<o_keys.length;i++) {
//            var j = o_keys[i];
////          for (var j in options) if (options.hasOwnProperty(j)) { // works, but this has so deep prototype
//            var o = options[j];
//            if (j=='dic_tags') {
//              this.fmt_dic_tags(o);
//              o.__proto__ = this.dic_tags.incase;
//            } else if (typeof(pfunc_proto[j])==='function' && typeof(options[j])!=='function') options[j] = this.dic_funcs[o] || this.eval_func(o); // for multi entry, catalog_html and page_html
//          }
////            options.__proto__ = pfunc_proto;
////          }
//          template.parse_funcs = options; // rip to not come here again
          var pfunc_org = site2['RSS'].parse_funcs['page_html'];
          if (template.parse_funcs.nof_files===pfunc_org.nof_files) Object.defineProperty(template,'nof_files',{value:1, configurable:true, enumerable:true, writable:true});
          if (template.parse_funcs.sticky===pfunc_org.sticky) Object.defineProperty(template,'sticky',{value:false, configurable:true, enumerable:true, writable:true});
          var ths = doc.parse_funcs.ths(doc);
//          if (doc.type_source==='catalog') this.clean_collisions(ths, doc);
          if (pref.RSS.sec_auto) if (template.parse_funcs.isTrusted===0) if (rss_options[2]) this.auto_isTrusted(ths, template, doc); // {
//            var idx = site2['rss'].rss_appended.map(v=>v[0]).indexOf((doc.domain==='rss'?'':doc.domain)+doc.board);
//            if (idx!==-1) {
//              var threshold = ths.length/2;
//              for (var i=0;i<ths.length;i++) if (ths[i].com.search(/&lt;\/?(a|img|s|strong|i|b)(\s|&gt;)/)!==-1) if (--threshold<0) {
//                template.parse_funcs.isTrusted = 1;
//                var rss_appended = site2['rss'].rss_appended[idx];
//                if (!rss_appended[2]) rss_appended[2] = {};
//                rss_appended[2].isTrusted = 1;
//                while (i>=0) delete ths[i--].com;
//                break;
//              }
//            }
//          }
          return ths;
//          return doc.parse_funcs.ths(doc); // can change ths // this.__proto__.ths(doc);
        },
        tags_dc: function(){return this.exe_sub('tags_dc');},
        time_pubDate: function(){return this.exe_sub('time_pubDate');},
//        com_content_encoded: function(){return this.exe_sub('com_content_encoded');},
        dic_tags:{incase:{__proto__:site2['RSS'].parse_funcs['page_html'].dic_tags},
                 always:           site2['RSS'].parse_funcs['page_html'].dic_tags},
        dic_funcs: (site2['RSS'].parse_funcs['page_html'].dic_funcs = {__proto__:{
//          dic_tags: {}, // can place dic_tags for all here. {dic_tags:{}} for use
          sub_news24: function(th){return th.sub_def.replace(/[\s\d/:]+\u66f4\u65b0$/,'');},
//          sub_news24: function(th){return this.__proto__.sub(th).replace(/[\s\d/:]+\u66f4\u65b0$/,'');},
          no_news24: function(th){return parseInt(th.link.slice(th.link.lastIndexOf('/')+1,th.link.lastIndexOf('.')),10);},
//          no_news24: function(th){return parseInt(th.link.split('/').slice(-1)[0].replace(/\.html$/,''),10);}, // ok
          xhr_ntv: function(str){return str.replace(/<title>([^<]*)<\/title>/g,function(m){return m.replace(/&/g,'&amp;');});},
          xhr_5ch: function(str){return str.replace(/<(title|description)>([^]*?)<\/(title|description)>/g,(m,p1,p2,p3)=>'<'+p1+'>'+pref_func.sanitize(p2)+'</'+p3+'>');}, // contains &, <, \n in title and description, '.' in regular expression DOESN'T contain \n, [^] means all letters, https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
          sticky_sputnik: function(th){return parseInt(th.pn_org.getElementsByTagNameNS('https://sputniknews.com/', 'priority')[0].textContent,10)<=1;},
          com_5ch: function(th){return th.com_def.replace(/#(\d+);/g,function(m,p1){return String.fromCodePoint(p1);}).replace(/http[^\s]*/g,'<a href="$&">$&</a>');},
//          com_5ch: function(th){return th.com_def.replace(/#\d+;/g,'&$&');},
          toCode: function(str){return str.replace(/(&amp;|&)*#(\d+);/g,function(m,p1,p2){return String.fromCodePoint(p2);});},
          nof_posts_srad: function(th){var pn = th.pn_org.getElementsByTagNameNS('http://purl.org/rss/1.0/modules/slash/','comments')[0];
                                       return (pn && parseInt(pn.textContent,10)||0)+1;},
        }}),
        fmt_opt: function(opt){ // opt.__proto__.__proto__ === pfunc_proto
          var pfunc_proto = opt.__proto__.__proto__;
          if (opt.patch) this.fmt_patch(opt);
          var keys = Object.keys(opt);
          for (var i=0;i<keys.length;i++) {
            var j = keys[i];
//          for (var j in opt) if (opt.hasOwnProperty(j)) { // works, but this has so deep prototype
            var o = opt[j];
            if (j=='patch') continue;
            else if (j=='dic_tags') this.fmt_dic_tags(o).__proto__ = this.dic_tags.incase;
            else if ((j==='xhrPatchFunc' || typeof(pfunc_proto[j])==='function') && // xhrPatchFunc doesn't exist not to add req.xhrPatchFunc in pfunc_proto.
                     typeof(o)!=='function') // for multi entry, catalog_html and page_html
                   opt[j] = this.dic_funcs[o] || this.eval_func(o) || pfunc_proto[o];
          }
          return opt;
        },
        fmt_patch: function(opt){
          var patch = opt.patch;
          delete opt.patch;
          if (patch&01) {opt.xhrPatchFunc = 'xhr_ntv'; opt.responseType = 'text';}
        },
        fmt_dic_tags: function(dic){
          for (var i in dic) {
            var v = dic[i];
            if (Array.isArray(v)) {for (var j=0;j<v.length;j++) if (v[j][0]!=='#') v[j] = '#'+v[j];}
            else {
              if (v && v[0]!=='#') dic[i] = '#'+v; // allow erasing tag
              if (i[0]!=='#') {dic['#'+i] = dic[i]; delete dic[i];}
            }
          }
          return dic;
        },
        eval_func: function(str){
          if (typeof(str)!=='string' || str.indexOf('function')===-1 && str.indexOf('=>')===-1) return null;
          try {
            var f = new Function('"use strict";return ('+str+');')();
            return (typeof(f)==='function')? f : null;
          } catch(e){
            pref_func.report_error('ERROR: in parsing Function strings: '+str);
            return null;
          }
        },
        dic_no2linkTime: {},
        auto_isTrusted: function(ths, template, doc){
          var threshold = ths.length/2;
          for (var i=0;i<ths.length;i++) if (ths[i].com.search(/&lt;\/?(a|img|s|strong|i|b)(\s|&gt;)/)!==-1) if (--threshold<0) {
            template.parse_funcs.isTrusted = 1;
            var rss_appended = site2['rss'].rss_appended;
            var idx = rss_appended.map(v=>v[0]).indexOf((doc.domain==='rss'?'':doc.domain)+doc.board);
            if (idx!==-1) {
              var opt = rss_appended[idx][2] || (rss_appended[idx][2] = {});
              opt.isTrusted = 1;
            }
            while (i>=0) delete ths[i--].com;
            if (pref.dashboard.rss_live) pref_func.settings.onchange_funcs['dashboard.rss_redraw'](true); // auto_isTrusted is applied only to rss_append.
            break;
          }
        },
        get opt_def(){return site2['rss'].opt_def;},
        proto: 'RSS.page_html',
      },
      'catalog_html': 'page_html',
//      'page_html_template': {__proto__:this.parse_funcs['page_html_template']},
    },
    board2rss_options: {},
    rss2dbpEtag: {},
//    get add_rss(){return [];},
//    set add_rss(args){
//      this.add_rss_append(args);
//    },
    opt_def: {__proto__:site2['RSS'].parse_funcs.page_html},
//    get opt_def(){return site2['RSS'].parse_funcs.page_html;},
//    opt_def: Object.defineProperties({},{ // patch
//      directConnection: {get: function(){return site2['rss'].parse_funcs.page_html.directConnection;}},
//      overrideMimeType: {get: function(){return site2['rss'].parse_funcs.page_html.overrideMimeType;}}
//    }),
    add_rss_append: function([db, urls, opt_in], done_init, remake){ // append is default
      var append = !done_init;
      if (!Array.isArray(urls)) urls = [urls];
//      if (args[0]) args[0] = (args[0].match(/^(\w+\/)*[\w\-]+/)||[])[0];
      if (!db) {
        var dbpEtag;
        if (append && (dbpEtag = this.rss2dbpEtag[urls[0]])) return dbpEtag;
        var bd = this.add_rss_bd(urls[0]);
      } else {
        var [dm,bd] = db.split('/');
        if (dm && bd) {
          if (site0.site2keys.indexOf(dm)===-1 && (!site2[dm] /*|| !append*/)) this.add_rss_mkdir(dm); // append only. several entries are made at once usually.
          if (dm!==this.directory && site2[dm]) return site2[dm].add_rss_append([bd, urls, opt_in], done_init, remake); // bd instead of db is for safe, preventing infinite loop.
        }
        bd = '/'+(bd||dm)+'/'; // for omitting '/' at head and tail
      }
      dm = this.directory;
      var init = done_init && !(dm+bd in done_init);
      if (init) done_init[dm+bd] = null;
//      site2[args[0]] = { 
//        url_rss: urls,
//        home: args[2] || args[1],
//        __proto__:this
//      };
//      site3.setup(args[0]);
//      if (args[2] && args[2].overrideMimeType) for (var i=0;i<urls.length;i++) httpd.xhr_patch[(site.proxy||'')+urls[i]] = args[2].overrideMimeType;
//      if (args[2] && args[2].overrideMimeType) setTimeout(function(){for (var i=0;i<urls.length;i++) httpd.xhr_patch((site.proxy||'')+urls[i], args[2].overrideMimeType);},0); // setTimeout is a patch to change order.
      if (opt_in && opt_in.patch) this.parse_funcs['page_html'].fmt_patch(opt_in); // opt_in may contain xhrPatchFunc and must be formatted here.
      if (append && !remake) this.rss_appended.push([(dm==='rss'? '' : dm)+bd, urls[0], opt_in]); // this.rss_appended.set(dba, [dba, urls[0], opt_in]);
//      if (append && !remake) {
//        var dba = (dm==='rss'? '' : dm)+bd;
//        for (var i=0;i<this.rss_appended.length;i++) if (this.rss_appended[i][0]===dba) {var entry = this.rss_appended[i]; break;}
////        var entry = this.rss_appended.get(dba);
//        if (entry) {
//          if (typeof(entry[1])==='string') entry[1] = [entry[1], urls[0]];
//          else entry[1].push(urls[0]);
//        } else this.rss_appended.push([dba, urls[0], opt_in]); // this.rss_appended.set(dba, [dba, urls[0], opt_in]);
//      }
      var opt_live = this.board2rss_options[bd] && this.board2rss_options[bd][1]; // reuse opt to change values live, opt is in prototype chain of parse_funcs of existing threads
      var opt = opt_live || {__proto__:this.opt_def}; // {}; // opt must exist to test isTrusted in dashboard. 
//      if (!opt) opt = opt_in? (opt_in.__proto__ = this.opt_def, opt_in) : {__proto__:this.opt_def}; // {};
      if (/*/!append*/init && opt_live) for (var i in opt) if (opt.hasOwnProperty(i)) delete opt[i];
      if (opt_in && (/*!append*/init || !opt_live)) for (var i in opt_in) opt[i] = opt_in[i];
      if (remake || opt.xhrPatchFunc) this.parse_funcs['page_html'].fmt_opt(opt); // xhrPatchFunc is used before parsing, it must be formatted before the parse
//        opt.xhrPatchFunc = pfunc.dic_funcs[opt.xhrPatchFunc] || pfunc.eval_func(opt.xhrPatchFunc); // xhrPatchFunc is used before parsing, it must be formatted before the parse
//      if (!opt) opt = {__proto__:this.opt_def}; else opt.__proto__ = this.opt_def; // {}; // opt must exist always to test isTrusted in dashboard.
      var b2rss_urls = this.board2rss_options[bd] && this.board2rss_options[bd][0];
      if (/*append*/!init && b2rss_urls) {
        var start = b2rss_urls.length;
        for (var i=0;i<urls.length;i++) if (append || !(urls[i] in done_init)) b2rss_urls[b2rss_urls.length] = urls[i]; // remove registered urls.
      } else {
        b2rss_urls = urls;
        this.board2rss_options[bd] = [urls, opt, append && !remake || remake==='auto'];
      }
      for (var i=start||0;i<b2rss_urls.length;i++) { // always urls.length===1 when append, reverse ordering is not necessary.
        var url = b2rss_urls[i];
        if (done_init) done_init[url] = null;
        var dbpEtag = this.rss2dbpEtag[url];
        if (!dbpEtag) dbpEtag = this.rss2dbpEtag[url] = [dm + bd + 'c' + i, null, null];
        else dbpEtag[0] = dm + bd + 'c' + i;
      }
//      if (append && this.board2rss_options[bd]) {
//        var urls_pre = this.board2rss_options[bd][0];
//        var idx = urls_pre.push(urls[0]) - 1;
//        dbpEtag = this.rss2dbpEtag[urls[0].replace(/^(https*:)*\/\//,'')] = [dm + bd + 'p' + idx, null];
////        this.rss2dbpEtag[urls_pre[0].replace(/^(https*:)*\/\//,'')][0] = dm + bd + 'p0'; // cause racing at initial access.
//      } else {
////        if (append) this.rss_appended.push([(dm==='rss'? '' : dm)+bd, urls[0], opt]);
//        this.board2rss_options[bd] = [urls, opt];
//        for (var i=urls.length-1;i>=0;i--) dbpEtag = this.rss2dbpEtag[urls[i].replace(/^(https*:)*\/\//,'')] = [dm + bd + (urls.length===1?'c':'p') + i,null];
//      }
////      for (var i=urls.length-1;i>=0;i--) dbpEtag = this.rss2dbpEtag[urls[i].replace(/^(https*:)*\/\//,'')] = [dm + bd + (urls.length===1?'c':'p') + i,null]; // , this.rss_registered, append];
      if (/*!append*/init) if (liveTag && liveTag.mems[dm] && liveTag.mems[dm][bd]) liveTag.mems[dm][bd].p = {};
//      if (!liveTag) return; // bail out at initial load
//      var pkey = 'ih'+(pref.debug_mode.parse_error? '_debug':''); // see wrap_to_parse
//      var p = (p=liveTag.mems[dm]) && (p=p['/'+args[0]+'/']) && p.p;
//      if (p) delete p[pkey];
//      var ld = liveTag.mems[args[0]];
//      if (ld) for (var b in ld) delete ld[b].p[pkey];
      //      this.rss_registered++;
      if (!remake && pref.dashboard.rss_live) pref_func.settings.onchange_funcs['dashboard.rss_redraw'](append);
      return dbpEtag;
    },
    add_rss_bd: function(url){
      var ds = url.replace(/^(https*:)*\/\//,'').split('/')[0].split('.').reverse();
      return '/' + (ds[0]!=='jp' || ['ac','ad','co','ed','go','gr','lg','or'].indexOf(ds[1])===-1 || ds[2]==='pref' || ds[2]==='city'? ds[1] : ds[2]) + '/';
    },
    add_rss_mkdir: function(dir){
      site2[dir] = {
        directory: dir,
        board2rss_options: {},
        __proto__:this
      };
      site3.setup(dir);
      if (liveTag && liveTag.mems[dir]) liveTag.mems[dir].p = Object.create(null);
    },
    rss_appended: [],
    rss_appended_get_str: function(){return this.rss_appended.map(v=>JSON.stringify(v)).join('\n');},
//    rss_appended: new Map(), // []
//    rss_appended_get_str: function(){return Array.from(this.rss_appended.values()).map(v=>JSON.stringify(v)).join('\n');},
//    rss_registered: 0,
    generate_registered_rss: (function(){
      var proto = {
        get db_def(){return !this.obj[1]? '' : site2['rss'].add_rss_bd(Array.isArray(this.obj[1])? this.obj[1][0] : this.obj[1]);}, 
        get db(){return this.obj[0]||'';}, //  || site2['rss'].add_rss_bd(Array.isArray(this.obj[1])? this.obj[1][0] : this.obj[1]);}, 
        set db(v){this.obj[0] = v;},
        get isTrusted(){return this.obj[2] && ('isTrusted' in this.obj[2])? this.obj[2].isTrusted : this.isTrusted_def;},
        set isTrusted(v){if (v!==this.isTrusted_def) (this.obj[2]||(this.obj[2]={})).isTrusted = v;
                         else if (this.obj[2]) delete this.obj[2].isTrusted;},
//        set isTrusted(v){(this.obj[2]||(this.obj[2]={})).isTrusted = v;},
        get isTrusted_def(){return site2['rss'].opt_def.isTrusted;},
//        get t1(){return this.isTrusted>0;},
//        get t2(){return this.isTrusted>1;},
        get urls_arr(){return Array.isArray(this.obj[1])? this.obj[1] : [this.obj[1]];}, // arr notation
        get url(){return Array.isArray(this.obj[1])? JSON.stringify(this.obj[1]) : this.obj[1];}, // string notation
        set url(v){if (typeof(v)!=='string' || v[0]!=='[') this.obj[1] = v;}, // obj[1] is string or ARRAY. JSON.parse() is required when an ARRAY is set.
        get opt(){var str = this.obj[2] && JSON.stringify(this.obj[2], function(k,v){return k==='isTrusted'? undefined : v;});
                  return str && str!=='{}'? str : '';},
//        get opt(){return !this.obj[2]? '':JSON.stringify(this.obj[2], function(k,v){return k==='isTrusted'? undefined : v;}).slice(1,-1);},
        set opt(o){ // in-place change for keeping prototype
          if (typeof(o)==='string') return; // discard changes from apply_prep_set
          try{
            for (var i in this.obj[2]) if (this.obj[2].hasOwnProperty(i)) if (i!=='isTrusted' && !(i in o)) delete this.obj[2][i];
            if (!this.obj[2]) this.obj[2] = {};
            for (var i in o) this.obj[2][i] = o[i];
          } catch(e) {}
        },
//        set opt(v){
//          try{
//            var add_isTrusted = this.obj[2] && this.obj[2].isTrusted!==this.isTrusted_def;
//            var obj = JSON.parse('{'+v+'}');
//            if (add_isTrusted) obj.isTrusted = this.obj[2].isTrusted;
//            this.obj[2] = (v||add_isTrusted)? obj : {};
//          } catch(e) {}
//        },
        get db_formatted(){var dbs = (this.db||this.db_def).split('/'); return (dbs[1]&&dbs[0]||'rss')+'/'+(dbs[1]||dbs[0])+'/';},
        get sticky(){
          var obj = pref.catalog.style_general_list_obj2[this.db_formatted];
          return obj && obj.cmd && obj.cmd.STICKY;},
        get colStyle(){
          var obj = pref.catalog.style_general_list_obj2[this.db_formatted];
          return obj && obj.style && ((obj.style.background? 'background:'+obj.style.background+';':'') + (obj.style.border? 'border:'+obj.style.boader+';':'')) || '';},
        stringify: function(){
          return JSON.stringify(this.obj[2] && Object.keys(this.obj[2]).length>0? this.obj : this.obj.slice(0,2));
        }
      };
      var proto_global = {
        set isTrusted_opt_def(v){if ('isTrusted' in this.obj[2]) this.opt_def.isTrusted = v; // mirror
                                 else delete this.opt_def.isTrusted;},
        get isTrusted_def(){return site2['rss'].parse_funcs['page_html'].isTrusted;},
        __proto__:proto
      }
      function RSS_extract(obj, pfx, retval, str_in, start, end, outer_start, outer_end){
        var tgt = retval[pfx? (pfx.match(/dic_tags|dic_funcs|DEFAULT/)||['other'])[0] : 'arr'];
//        if (pfx) retval[(pfx.match(/dic_tags|dic_funcs|DEFAULT/)||['other'])[0]] = obj;
        tgt[tgt.length] = {obj, str:str_in.slice(start, end), pfx, start, end, outer_start, outer_end, str_whole:str_in.slice(outer_start,outer_end), __proto__:proto};
//            : obj[0]? (idx=obj[0].indexOf('/'), [idx>=0 && obj[0].slice(0,idx)||'rss', '/'+(idx>=0? obj[0].split('/')[1] : obj[0])+'/'])
//            : ['rss', site2['rss'].add_rss_bd(Array.isArray(obj[1])? obj[1][0] : obj[1])];
//        arr[arr.length] = {db:db, obj:obj, str:str_in.slice(start, end), pfx:prefix, s:start, e:end};
      }
      return function(val_new){
        var retval = {gl: [{obj:['DEFAULT','',null], db_formatted:'', opt_def:this.opt_def, __proto__:proto_global}],
                      arr: [],
                      auto: this.rss_appended.map(v=>({obj:v, __proto__:proto})),
//                      auto: Array.from(this.rss_appended.values()).reverse().map(v=>({obj:v, __proto__:proto})),
                      new: val_new||[{obj:['','',{__proto__:this.opt_def}], /*db:'',*/ __proto__:proto}],
                      DEFAULT: [], dic_tags:[], dic_funcs:[], other:[], mult_dbs:{}};
//        var retval = {arr:[], auto:this.rss_appended.reverse().map(v=>({obj:v, __proto__:proto})), new:val_new||[{obj:['','',{}], /*db:'',*/ __proto__:proto}]};
        pref_func.parseJSONCs(pref.RSS.str, RSS_extract, 'RSS_DASHBOARD', true, retval, pref.RSS.str);
        retval.gl[0].obj[2] = retval.DEFAULT[0] && retval.DEFAULT[0].obj || {};
        return retval;
      };
    })(),
    dnd_rss: function(){
      document.body.addEventListener('dragover', function(e){
        if (gGEH.drag.dragging) return;
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy';
      },false),
      document.body.addEventListener('drop', function(e){
        if (gGEH.drag.dragging) return;
        e.preventDefault();
        e.stopPropagation();
        var dt = e.dataTransfer;
        if (!dt) return;
        var url_dropped = dt.getData('text/uri-list');
        if (url_dropped) dnd_rss_1(url_dropped);
        else (dt.getData('text/plain').match(/https*\S*(\s|$)/mg)||[]).map(v=>v.slice(0,-1)).forEach(dnd_rss_1); // || getData('text/html');
        function dnd_rss_1(url){
          var url = url.replace(/https*:\/\/feedly\.com\/.*http/,'http');
          if (pref.RSS.dnd.nf) {
            var pn = e.target;
            var pn13 = pref_func.settings.pn13;
            while (pn && pn!==pn13) pn = pn.parentNode;
            if (pn===pn13) {site2['rss'].add_rss_append(['',url]); return;}
          }
          var opt_pClg = {refresh:pClg};
          var pfunc = site2['RSS'].parse_funcs['catalog_json'];
          if (pref.RSS.dnd.f_vbd) scan.scan_ui('dnd_rss_vbd', {tgts:[site.nickname+site2['rss'].add_rss_bd(url)+'j0'], opt_pClg});
          else if (pref.RSS.dnd.f_rec) {
            httpd.req({initiator:'dnd_rss_rec',
                       tgts:[{domain:'RSS', url:site.protocol+'//'+site2[site.nickname].domain_url+'/rss.json?url='+url, responseType:'json', data_type:'json', xhr_local:true}],
                       callback_1:function(req,val){pfunc.ths(val.response,{},opt_pClg)},
                       callback_1_fail:function(req,val){if (val.status===418) pfunc.ths([[[418,'',url]]],{},opt_pClg)}},6);
          } else pfunc.ths([[[0,'',url]]],{},opt_pClg);
        }
      },false);
    },
    proto: 'RSS'
  };
}

////////  if (!site2['futaba']) site2['futaba'] = { // working code.
////////    nickname : 'futaba',
////////    check_func : function(){
////////      if (window.location.href.search(/2chan.net/)!=-1) { // futaba
////////        site.config('2chan.net','futaba');
////////        site.max_page = site2['futaba'].max_page_futaba[site.server_name + site.board];
////////        return true;
////////      } else return false;
////////    },
////////    max_page_futaba : {
////////      'may/b/' : 11,
////////      'jun/b/' : 10,
////////      'dec/b/' : 8,
////////    },
////////    max_page : function(bn){return site2['futaba'].max_page_futaba[bn];},
//////////    make_url : function(board,no){return ['http://'+site.server_name+'.2chan.net' + board + ((no==0)? 'futaba' : no) + '.htm', 'html'];},
////////    make_url4 : function(dbt){return ['http://'+site.server_name+'.2chan.net' + dbt[1] + ((dbt[2]==0)? 'futaba' : dbt[2]) + '.htm', 'html'];},
////////    get_ops : function(doc){
////////      var ops = [];
////////      var responds = doc.getElementsByClassName('hsbn');
//////////      for (var i=0;i<responds.length;i++) ops.push(responds[i].href.substr(-13,9)); // doesn't work for sub-docment.
//////////      for (var i=0;i<responds.length;i++) ops.push(responds[i].getAttribute('href').substr(-13,9)); // may
////////      for (var i=0;i<responds.length;i++) ops.push(responds[i].getAttribute('href').replace(/res\//,'').replace(/\.htm/,''));
////////      return ops;
////////    },
////////    get_posts : function(doc) {
////////      var posts = [];
////////      var inputs = doc.getElementsByTagName('input');
////////      for (var i=0;i<inputs.length;i++) if (inputs[i].value=='delete') posts.push(inputs[i].name);
////////      return posts;
////////    },
////////    format_thread : function(doc){return doc;},
////////  };

  var Debug = /*(!pref.debug_mode['8'])? null :*/ (function(){
//    debug_show_proto_func_pool: null,
    function debug_parse_funcs(str, values_obj, dic_proto) {
      debug_parse_funcs_Init(str.indexOf(':')===-1);
      /*if (pref.debug_mode['8'])*/ return debug_parse_funcs_dig(str.split(':')[0], str.split(':')[1], undefined, values_obj, dic_proto); // .replace(/\//g,',/').replace('::,/','::/');
//      var search_key = 'debug____proto';
//      var ex_keys = [search_key,'proto']; // 'proto' for patch (obsolete)
//      var domain = str.split(':')[0];
//      var type = str.split(':')[1];
//      var pfuncs = site2[domain].parse_funcs;
//      var start_point_template = pfuncs[type+'_template'] || pfuncs[type.split('_')[0]+'_template'];
//      var start_point = pfuncs[type];
//      var func_pool = this.debug_show_proto_func_pool;
//      return str+'::'+ (dig_horizontal(start_point_template) + dig_horizontal(start_point) || 'NONE');
//      function dig_horizontal(obj){ // ,ex_keys,search_key) {
////        if (!search_key) search_key='proto';
////        if (!ex_keys) ex_keys = ['proto'];
//        if (!obj || !obj[search_key]) return '';
//        var str = '/'+obj[search_key] + ':';
//        var keys = Object.keys(obj);
//        for (var i=0;i<keys.length;i++) if (ex_keys.indexOf(keys[i])==-1) {
//          str += keys[i] + ',';
//          ex_keys.push(keys[i]);
//          if (func_pool) func_pool[keys[i]] = Debug.isOwnFunc(obj, keys[i]);
//        }
//        return str + dig_horizontal(obj.__proto__); // , ex_keys, search_key); // for 'debug____proto'
//      }
    }
    function debug_parse_funcs_entry(str, str2) {
      var expand = (!str2)? pref.debug_mode.pfunc_expand : pref.debug_mode.pfunc_comp_expand_same || pref.debug_mode.pfunc_comp_expand_diff;
//      if (expand) this.debug_show_proto_func_pool = {};
//      if (pref.debug_mode['8']) console.log(this.debug_parse_funcs_dig(str.split(':')[0], str.split(':')[1]).replace(/\//g,',/').replace('::,/','::/'));
      var pool_0 = {};
      console.log(debug_parse_funcs(str, pool_0));
      if (str2) {
//        var pool_0 = this.debug_show_proto_func_pool;
//        this.debug_show_proto_func_pool = {};
        try {
          var dic_proto = JSON.parse(pref.debug_mode.pfunc_comp_proto||null);
        } catch(e) {
          console.log('ERROR in JSON string: '+pref.debug_mode.pfunc_comp_proto);
        }
//        if (pref.debug_mode.pfunc_comp_proto) {
//          var dp = str2.split(':');
//          var start_point = site2[dp[0]].parse_funcs[dp[1]];
//          var proto_org = start_point.__proto__;
//          dp = pref.debug_mode.pfunc_comp_proto.split(':');
//          dp = site2[dp[0]].parse_funcs[dp[1]];
//          if (dp) start_point.__proto__ = dp;
//        }
        var pool_1 = {};
        console.log(debug_parse_funcs(str2, pool_1, dic_proto));
//        var pool_1 = this.debug_show_proto_func_pool;
      }
      if (expand)
        if (!str2) for (var i in pool_0) csrLog(i+': ', fmt(pool_0[i]));
        else {
          for (var i in pool_1) {
            var same = pool_0[i][0] === pool_1[i][0];
            if (pref.debug_mode.pfunc_comp_expand_same && same || pref.debug_mode.pfunc_comp_expand_diff && !same) {
              csrLog(i+': '+ (same? 'same':'differ') + ': ', fmt(pool_0[i]));
              if (!same) csrLog(i+': differ: ', fmt(pool_1[i]));
            }
            delete pool_0[i];
          }
          for (var i in pool_0) csrLog(i+': only_in_original: ', fmt(pool_0[i]));
        }
//      this.debug_show_proto_func_pool = null;
//      if (proto_org) start_point.__proto__ = proto_org;
      function csrLog(str, obj){
        console.log(str + (obj && (obj.toString? obj.toString() : 'Object, which doesn\'t have toString.')));
      }
      function fmt(pool){
        return pool[1]? pool[0].toString().replace(/^function/,'get') : pool[0];
      }
    }
    function debug_parse_funcs_dig(domain, src_type, prop, values_obj, dic_proto){ // prop for vertical digging
////      var src_types = [src_type+'_template', src_type.slice(src_type.indexOf('_')+1)+'_template', src_type];
////      for (var s=0;s<src_types.length;s++) {
//////        if (s==1 && pref.debug_mode['8']) continue;
////        var str = this.debug_parse_funcs_dig(domain, src_types[s], prop);
////        if (str.indexOf('::END')>=0) break;
////      }
//////      var pf = site2[domain].parse_funcs[src_types[s]];
////      return (domain+':'+src_type+'::'+ str.replace(/::END/,'')).replace('::/'+domain+'.'+src_type+':','::');
//////      return pref.debug_mode['8']? str : s<2? domain+':'+src_types[2]+'::'+str.replace(/::/,':') : str; // str.replace(/^.*?::\//,'').replace(/:/,'::').replace(/\./,':');
////    },
////    debug_parse_funcs_dig: function(domain, src_type, prop){
      var s4objs = new Map();
      for (var i in site4) s4objs.set(site4[i],null);
      var s2dpf = site2[domain].parse_funcs;
      var start_point_template = src_type? s2dpf[src_type+'_template'] || s2dpf[src_type.split('_')[1]+'_template'] : null;
      var start_point = src_type? s2dpf[src_type] : site2[domain];
      var ex_keys = Object.defineProperties(values_obj||{}, {debug____proto:{value:null}, proto:{value:null}});
//      var ex_keys = Object.create(null, {debug____proto:{value:null}, proto:{value:null}});
//      if (this.debug_show_proto_func_pool) this.debug_show_proto_func_pool = ex_keys;
      var ownProp = false;
      return (domain+(src_type?':'+src_type:'')+'::'+dig(start_point_template, start_point, prop) + ((prop && !ownProp)? ' undefined' : '')).replace('::/'+domain+'.'+src_type+':','::');
//      return (domain+':'+(src_type? src_type+'::'+dig(start_point_template, null, prop):':') + (!ownProp? dig(start_point, null, prop) : '') + ((prop && !ownProp)? ' undefined' : '')).replace('::/'+domain+'.'+src_type+':','::');
//      return domain+':'+src_type+'::'+ dig(start_point, prop);
      function dig(obj, obj2, prop){
        if (!obj || !obj['debug____proto']) return obj2 && (!obj || s4objs.has(obj))? dig(obj2, null, prop) : '';
        var keys = !prop && Object.keys(obj).filter(function(key){var op; return !(key in ex_keys)? (op = Object.getOwnPropertyDescriptor(obj,key), ex_keys[key] = [op.value||op.get, op.get], true) : false;}); // horizontal dig
        ownProp = prop && obj.hasOwnProperty(prop); // used as an end_flag also.
        var ownFunc = ownProp && isOwnFunc(obj, prop);
        var ownValue = ownProp && getOwnValue(obj, prop);
        var mapObj = ownProp && parse_funcs_Map.get(ownFunc||ownValue);
        var refCount = mapObj? ++mapObj.refCount : 1;
//        var refCount = (ownFunc)? ++parse_funcs_Map.get(ownFunc).refCount : (ownValue)? parse_funcs_Map.set(ownValue, {refCount:1, count:'value'}).refCount : 0;
//        var ownFuncValue = ownFunc || ownValue;
//        if (ownProp) {
//          var ref = Debug.parse_funcs_Ref.get(ownFuncValue);
//          if (ref===undefined) {
//            ref = {original: (ownFunc)? Debug.parse_funcs_Map.get(ownFunc).original:'', count:1};
//            Debug.parse_funcs_Ref.set(ownFuncValue, ref);
//          } else ref.count++;
//        }
        return '/'+obj['debug____proto']+':'+
          (ownProp? /*'::END'+*/ prop + (mapObj? ' (==='+mapObj.original+', '+refCount+')'
                                                : ', '+ownValue) // ownValue && !pref.debug_mode['8']? ', '+ownValue : '')
                  : (prop? '':keys.join(',')) + dig(get_proto(obj), obj2, prop));
      }
      function get_proto(obj){
        var me = dic_proto && obj['debug____proto'];
        var p = me && dic_proto[me] && dic_proto[me].split('.');
        var proto = p && (src_type? site2[p[0]].parse_funcs[p[1]||me.split('.')[1]] : site2[p[0]]);
        if (!proto) return obj.__proto__;
        dic_proto[me] = null; // prevent infinite loop
        return proto;
      }
    }
    function debug_parse_funcs_all(str) {
      var site2func = pref.debug_mode.pfunc_all_site2;
      var domains = site2func? Object.keys(site2) : Object.keys(site2).filter(function(d){return site2[d].hasOwnProperty('parse_funcs');});
      var types = ['','_json','_html']; // '' for common
      var types_tgt = types.map(function(v){return pref.debug_mode.types[v];});
      var srcs = ['common','post','thread','page','catalog','base'];
      var objs = {};
      debug_parse_funcs_Init(site2func);
      for (var d=0;d<domains.length;d++)
        if (site2func) dig_1(domains[d], null, str, true);
        else for (var t=0;t<types.length;t++) for (var s=0;s<srcs.length;s++) {
          var domain = domains[d];
          var src_type = srcs[s]+types[t];
          if (domain!=='DEFAULT' && srcs[s]==='common' || (site2[domain].no_JSONAPI? types[t]==='_json' : src_type==='base_html')) continue;
          if (!site2[domain].parse_funcs[src_type]) continue;
          dig_1(domain, srcs[s]+types[t], str, types_tgt[t]);
//        var str1 = debug_parse_funcs_dig(domain, srcs[s]+types[t], str);
////        if (pref.debug_mode['8']) {
////                var result = this.debug_parse_funcs_parse(domain, src_type, str);
////                var strs = result[0];
////                var hier_str = result[1];
////                src_type = result[2];
////        //      try {
////                var obj_root = site2[domain].parse_funcs[src_type];
////                if (obj_root) {
////                  var obj = obj_root[str];
////                  var flag = true;
////                  for (var i in objs) if (objs[i][0]===obj) {strs[strs.length] = ' (==='+i+')'; flag=false; objs[i][1]++; break;}
////        //        if (flag) objs[domain+':'+src_type] = obj;
////                  if (flag) objs[hier_str] = [obj,1];
////                  if ((typeof(obj)==='string' || typeof(obj)==='number')) strs[str.length] = ', '+obj;
////                }
////        //      } catch(e){
////        //        if (src_type.indexOf('_template')==-1) throw e; // templates don't have parse_funcs and cause excaption.
////        //      }
////        //        console.log(strs.join(''));
////                var str0 = strs.join('').replace(/ *\(.*/,'');
////                if (str0!==str1.replace(/ *\(.*/,'')) {console.log('ERROR1:');console.log(str0);console.log(str1);}
////        }
//        if (pref.debug_mode.domains[domain] && types_tgt[t]) console.log(str1);
        }
      var num = 0;
      if (pref.debug_mode.pfunc_all_expand) {
        /*if (pref.debug_mode['8']) for (var i in objs) console.log((num++)+'('+objs[i][1]+'): '+ i +': '+objs[i][0]);
        else*/
        for (var i of parse_funcs_Map.keys()) {
          var ref = parse_funcs_Map.get(i);
          var isObj = i!==null && typeof(i)==='object';
          if (ref.refCount>0) console.log((num++)+'('+ref.refCount+'/'+ref.count+'): '+(ref.original? ref.original+': ':'')+(isObj?'':ref.type==='get'?i.toString().replace(/^function/,'get') : i),isObj?i:'');
        }}
      function dig_1(domain, src_type, prop, show){
        var str = debug_parse_funcs_dig(domain, src_type, prop);
        if (pref.debug_mode.domains[domain] && show) console.log(str);
      }
    }
//    debug_parse_funcs_parse: function(domain, src_type, str, force) { // working code
//      var src_types = [src_type+'_template', /*src_type.slice(src_type.indexOf('_')+1)+'_template',*/ src_type];
//      for (var s=0;s<2;s++) {
//        if (pref.debug_mode['8']) {
//          var str0 = this.debug_parse_funcs(domain+':'+src_types[s]);
//          var str1   = this.debug_parse_funcs_dig(domain, src_types[s]).replace(/ *\(.*/,'');
//          var str1m  = str1.replace(/\//g,',/').replace(/:,\//g,':/').replace(/NONE$/,'');
//          var str1m2 = str1m.replace(/$/,',');
//          if (str0!==str1 && str0!==str1m && str0!==str1m2) {console.log('ERROR0:');console.log(str0);console.log(str1);console.log(str1m);}
//        }
//        var strs = this.debug_parse_funcs(domain+':'+src_types[s]).split(',');
//        for (var i=0;i<strs.length;i++) {
//          var idx_s = strs[i].lastIndexOf(':');
//          if (strs[i].substr(idx_s+1)===str) {
//            strs.splice(i+1,strs.length-i-1);
//            var hier_str = strs[(i>0)?i-1:i].replace(/[^\/]*\//g,'').replace(/:[^:]*$/,'').replace(/\./,':');
//            var root_str = strs[0].replace(/::.*/,'');
//            if (hier_str!==root_str) strs[strs.length] = ' (==='+hier_str+')';
//            return [strs, hier_str, src_types[s]];
//          }
//          if (idx_s===-1) strs.splice(i--,1);
//          else strs[i]=strs[i].substr(0,idx_s+1);
//        }
//      }
//      return [strs, hier_str, src_types[s]];
//    },
//    debug_parse_funcs_all_old: function(str) {
//      var domains = site0.debug_domains.filter(function(v){return pref.debug_mode.domains[v];});
//      var types = site0.debug_types.filter(function(v){return pref.debug_mode.types[v];}); // ['','_json','_html']; // ,'_json_template','_html_template']; // '' for common
//      var srcs = ['common','post','thread','page','catalog','base'];
//      var objs = {};
//      for (var d=0;d<domains.length;d++) for (var t=0;t<types.length;t++) for (var s=0;s<srcs.length;s++) {
//        var domain = domains[d];
//        var src_type = srcs[s]+types[t];
//        if (domain!=='DEFAULT' && srcs[s]==='common' || (site2[domain].no_JSONAPI? types[t]==='_json' : src_type==='base_html')) continue;
//        if (!site2[domain].parse_funcs[src_type]) continue;
//        var result = this.debug_parse_funcs_parse(domain, src_type, str);
//        var strs = result[0];
//        var hier_str = result[1];
//        src_type = result[2];
////      try {
//        var obj_root = site2[domain].parse_funcs[src_type];
//        if (obj_root) {
//          var obj = obj_root[str];
//          var flag = true;
//          for (var i in objs) if (objs[i][0]===obj) {strs[strs.length] = ' (==='+i+')'; flag=false; objs[i][1]++; break;}
////        if (flag) objs[domain+':'+src_type] = obj;
//          if (flag) objs[hier_str] = [obj,1];
//          if ((typeof(obj)==='string' || typeof(obj)==='number')) strs[str.length] = ', '+obj;
//        }
////      } catch(e){
////        if (src_type.indexOf('_template')==-1) throw e; // templates don't have parse_funcs and cause excaption.
////      }
//        console.log(strs.join(''));
//        var str0 = strs.join('').replace(/ *\(.*/,'');
//        var str1 = this.debug_parse_funcs_dig_vertical(domain, srcs[s]+types[t], str);
//        if (str0!==str1) {console.log('ERROR1:');console.log(str0);console.log(str1);}
//      }
//      var num = 0;
//      if (pref.debug_mode.pfunc_all_expand) for (var i in objs) console.log((num++)+'('+objs[i][1]+'): '+ i +': '+objs[i][0]);
//    },
    function isOwnFunc(pf,prop){
      var desc = Object.getOwnPropertyDescriptor(pf, prop);
      return desc.get || desc.value && typeof(pf[prop])==='function' && desc.value;
    }
    function isOwnGet(pf,prop){
      return Object.getOwnPropertyDescriptor(pf, prop).get;
    }
    function getOwnValue(pf,prop){
      return Object.getOwnPropertyDescriptor(pf, prop).value;
    }
    function isOwnValue(pf,prop){
      return Object.getOwnPropertyDescriptor(pf, prop).hasOwnProperty('value');
    }
    function site2_funcs_enumerator(func, callback){
      func(callback, site2, '');
    }
    function parse_funcs_enumerator(func, callback, delim){
      for (var i in site2) if (site2[i].hasOwnProperty('parse_funcs')) func(callback, site2[i].parse_funcs, i+(delim||'.'));
    }
    function props_enumerator(callback, obj, label){
      for (var j in obj) if (obj.hasOwnProperty(j) && typeof(obj[j])!=='string')
        for (var k in obj[j]) if (obj[j].hasOwnProperty(k)) callback(obj[j], k, label+j);
    }
    function proto_labeller(callback, obj, label){
      for (var j in obj) if (obj.hasOwnProperty(j)) obj[j]['debug____proto'] = label+j;
    }
//    function parse_funcs_enumerator(callback){ // working code
//      for (var i in site2) if (site2[i].hasOwnProperty('parse_funcs'))
//        for (var j in site2[i].parse_funcs) if (site2[i].parse_funcs.hasOwnProperty(j) && typeof(site2[i].parse_funcs[j])!=='string')
//          for (var k in site2[i].parse_funcs[j]) if (site2[i].parse_funcs[j].hasOwnProperty(k)) callback(site2[i].parse_funcs[j], k, i, j);
//    }
//      parse_funcs_Map: (function(){
//        var map = new Map();
//        for (var i in site2) if (site2[i].hasOwnProperty('parse_funcs'))
//          for (var j in site2[i].parse_funcs) if (site2[i].parse_funcs.hasOwnProperty(j))
//            for (var k in site2[i].parse_funcs[j]) if (site2[i].parse_funcs[j].hasOwnProperty(k) && typeof(site2[i].parse_funcs[j])!=='string')
//              if (Object.getOwnPropertyDescriptor(site2[i].parse_funcs[j], k).value && typeof(site2[i].parse_funcs[j][k])==='function') map.set(site2[i].parse_funcs[j][k], i+':'+j+'.'+k);
//        return map;
    //      })(),
    pref_func.settings.onchange_funcs['Debug'] = {
      site2func: function(){
        var prop = pref.debug_mode.site2func;
        if (!prop) return;
        var dic = new Map();
        for (var i in site2) if(site2[i].hasOwnProperty(prop)) dic.set(site2[i][prop],i);
        console.log(Object.keys(site2).map(function(i){return i+': '+dic.get(site2[i][prop]);}).join(', '));
        if (pref.debug_mode.site2func_expand) Array.from(dic).forEach(function(kv){console.log(kv[1]+': ', typeof(site2[kv[1]][prop])==='object'? site2[kv[1]][prop] : site2[kv[1]][prop].toString());});
      },
//      dump_site2: function(){ // working code
//        var name = pref.debug_mode.site2func;
//        if (!name) return;
//        for (var i in site2) if(site2[i].hasOwnProperty(name)) site2[i]['debug____'+name] = i;
//        var str = '';
//        var funcs = {};
//        for (var i in site2) {
//          str += i+': '+site2[i]['debug____'+name]+', ';
//          funcs[site2[i]['debug____'+name]] = site2[i][name];
//        }
//        console.log(str);
//        if (pref.debug_mode.site2func_expand) for (var i in funcs) console.log(i+': ',funcs[i]);
//      },
      dump: function(){if (pref.debug_mode.pfunc) debug_parse_funcs_entry(pref.debug_mode.pfunc);},
      comp: function(){if (pref.debug_mode.pfunc_comp) debug_parse_funcs_entry(pref.debug_mode.pfunc, pref.debug_mode.pfunc_comp);},
      pfunc_all: function(){if (pref.debug_mode.pfunc_all) debug_parse_funcs_all(pref.debug_mode.pfunc_all);},
    };
    pref_func.settings.onchange_funcs['debug_mode'] = pref_func.settings.onchange_funcs['Debug'];
    var parse_funcs_Map;
    function debug_parse_funcs_Init(site2func){
      parse_funcs_Map = new Map();
      var enumerator = site2func? site2_funcs_enumerator : parse_funcs_enumerator;
      enumerator(proto_labeller);
      enumerator(props_enumerator, function(pf,prop, label){var func = isOwnFunc(pf,prop) || getOwnValue(pf,prop);
                                                            var prim = !isOwnFunc(pf,prop) && getOwnValue(pf,prop)!==null && typeof(getOwnValue(pf,prop))!=='object';
                                                            if (func || isOwnValue(pf,prop)) {
                                                              var f = parse_funcs_Map.get(func);
                                                              if (f) f.count++; // keep older labels
                                                              else parse_funcs_Map.set(func, {original:prim? 'PRIMITIVE:' : label+'.'+prop, count:1, refCount:0, type:isOwnGet(pf,prop)? 'get' : prim? 'prim' : 'func'});
                                                            }
//                                                            if (func || isOwnValue(pf,prop)) parse_funcs_Map.set(func, {original:prim? 'PRIMITIVE:' : label+'.'+prop, count:0, refCount:0});},':'); // always overwrite to use newer labels
                                                           },':');
//      enumerator(props_enumerator, function(pf,prop, label){var func = isOwnFunc(pf,prop) || getOwnValue(pf,prop);
//                                                            if (func || isOwnValue(pf,prop)) parse_funcs_Map.get(func).count++;});
    }
//    function debug_parse_funcs_Init(){ // working code
//      for (var i in site2) if (site2[i].hasOwnProperty('parse_funcs'))
//        for (var j in site2[i].parse_funcs) if (site2[i].parse_funcs.hasOwnProperty(j)) site2[i].parse_funcs[j]['debug____proto'] = i+'.'+j;
//      parse_funcs_Map = new Map();
//      parse_funcs_enumerator(function(pf,prop, i, j){var func = isOwnFunc(pf,prop); if (func) parse_funcs_Map.set(func, {original:i+':'+j+'.'+prop, count:0, refCount:0});});
////      parse_funcs_InitCount();
//////      Debug.parse_funcs_Ref = new Map();
////    }
////    function parse_funcs_InitCount(){
////      for (var i of parse_funcs_Map.keys()) {
////        var tgt = parse_funcs_Map.get(i);
////        tgt.count = 0;
////        tgt.refCount = 0;
////      }
//      parse_funcs_enumerator(function(pf,prop, i, j){var func = isOwnFunc(pf,prop); if (func) parse_funcs_Map.get(func).count++;}.bind(this));
//    }
    return {
//      parse_funcs_InitRefCount: function(){
//        for (var i of this.parse_funcs_Map.keys()) this.parse_funcs_Map.get(i).refCount = 0;
//      },
//      parse_funcs_Ref: null,
      lastReloaded: Date.now(),
      diff:(function(){
        function prep_call(func,src,props){
          var dst = prep_src();
          var diff = {};
          for (var i=0;i<props.length;i++) diff[props[i]] = func(dst[props[i]],src[props[i]]);
          return pref.test_mode['200']? dst : diff;
        }
        function test_out(cap,dst,src){
          console.log('overwritten: '+cap+': '+JSON.stringify(src));
          verify(dst, src, 'O');
          verify(src, dst, 'R');
        }
        function scopy(src, proto, rev){
          var dst = Object.create(proto||src.__proto__);
          if (proto && rev) dst.proto = dst.__proto__;
          var keys = Object.keys(src);
          for (var i=0;i<keys.length;i++) dst[keys[i]] = src[keys[i]];
          if (proto && !rev) dst.proto = dst.__proto__;
          return dst;
        }
        function prep_src(sel){
          var proto = {};
          var leaf = {};
          proto['src'] = {i0:'a', i1:'a', i2:'a', i3:'a', i18:'a', i4:'b', i5:'b', i6:'b', i7:'b', i8:'b', i9:'b', i16:'b', i19:'b', i10:'a', i11:'a', i12:'a', i13:'a', i14:'a', i15:'a', i17:'a'};
          leaf['src']  = {i0:'a', i1:'a', i2:'a', i3:'a', i18:'a', i4:'a', i5:'a', i6:'a', i7:'a', i8:'a', i9:'a', i16:'a', i19:'a',                                                                __proto__:proto['src']};
          proto['dst'] = {i0:'a', i1:'a', i2:'b', i3:'c', i18:'c', i4:'b', i5:'b', i6:'a', i7:'b', i8:'c', i9:'c', i16:'c', i19:'d', i10:'a', i11:'b', i12:'a', i13:'a', i14:'c', i15:'b', i17:'c'};
          leaf['dst']  = {i0:'a', i1:'b', i2:'a', i3:'c', i18:'b', i4:'a', i5:'b', i6:'a', i7:'c', i8:'a', i9:'c', i16:'b', i19:'c',                   i12:'a', i13:'b', i14:'c', i15:'a', i17:'b', __proto__:proto['dst']};
          var tgt = sel? 'dst' : 'src';
          return {before:leaf[tgt], proto:proto[tgt], after:scopy(leaf[tgt]), comp:scopy(leaf[tgt], scopy(proto[tgt])), compr:scopy(leaf[tgt], scopy(proto[tgt]), true)};
        }
        function verify(dst,src, path){
          var ex_list = {'.tabID':null, '.cli.json_out_str':null, __proto__:null};
          for (var i in dst) if (dst[i]!==null) {
            if (typeof(dst[i])==='object') verify(dst[i],src[i], path+'.'+i);
            else if (dst[i]!==src[i]) if (!((path+'.'+i) in ex_list)) // /*path.indexOf('.filter')!==0 &&*/ i!=='json_out_str' /*&& i!=='catalog_board_list_str'*/)
              console.log('ERROR: ex_verify: pref'+path+'.'+i+', dst:'+dst[i]+', src:'+src[i]);
          }
        }
        function run_test() {
          var dst = prep_src(true);
          var diff = JSON.stringify(pref_func.obj_elim_the_same(prep_src(),dst));
          console.log('diff: A: '+diff);
          test_out('A',dst, pref_func.pref_overwrite(prep_src(),JSON.parse(diff))); // auto // parse for emulation of loading from localStorage, cuts prototype chain

          var order = ['before','comp','compr','proto','after'];
          diff = JSON.stringify(prep_call(pref_func.obj_elim_the_same,dst,order));
          console.log('diff: '+diff);
          diff = JSON.parse(diff);

          test_out('H',dst, pref_func.pref_overwrite(prep_src(),diff)); // handmade
          test_out('S',dst, prep_call(pref_func.pref_overwrite,diff,order)); // standard
          test_out('R',dst, prep_call(pref_func.pref_overwrite,diff,order.reverse())); // reverse, but redundant.
        }
        return {
          verify: verify,
          run_test: run_test
        };
      })(),
    };
  })();

//if (pref.test_mode['18']) { // leak test about 8chan catalog in 4chan.
//  delete site2['8chan'].parse_funcs['catalog_html'].th_init;
//  delete site2['8chan'].parse_funcs['catalog_html'].th_destroy;
//}

//  for (var i in site2) {
//    if (i=='DEFAULT') continue;
//    for (var j in site2['DEFAULT'])
//      if (site2[i][j]===undefined) site2[i][j]=site2['DEFAULT'][j];
//  }
  for (var i in site2) {
    if (i==='dist') continue;
    for (var j in site2[i].parse_funcs) {
      if (typeof(site2[i].parse_funcs[j])==='string') {
        var tgt = site2[i].parse_funcs[j];
        site2[i].parse_funcs[j] = site2[i].parse_funcs[tgt];
        continue;
      }
      for (var k in site2[i].parse_funcs[j]) { // working code.
        if (k!=='proto' && typeof(Object.getOwnPropertyDescriptor(site2[i].parse_funcs[j],k).value)==='string') { // skip getter.
//        if (k!=='proto' && typeof(site2[i].parse_funcs[j][k])==='string') {
          tgt = site2[i].parse_funcs[j][k].split('.');
////          if (tgt.length>1) site2[i].parse_funcs[j][k] = (tgt.length===3)? site2[tgt[0]].parse_funcs[tgt[1]][tgt[2]] : // working code.
////                                                                           site2[i].parse_funcs[tgt[0]][tgt[1]];
          var tgt_obj = (tgt.length===1)? site2[i].parse_funcs[tgt[0]] && site2[i].parse_funcs[tgt[0]][k] : // catalog_json
                        (tgt.length===2 && site2[i].parse_funcs[tgt[0]] && site2[i].parse_funcs[tgt[0]][tgt[1]])? site2[i].parse_funcs[tgt[0]][tgt[1]] : // catalog_json.XXX
                        (tgt.length===2 && site2[tgt[0]] && site2[tgt[0]].parse_funcs[tgt[1]] && site2[tgt[0]].parse_funcs[tgt[1]][k])? site2[tgt[0]].parse_funcs[tgt[1]][k] : // vichan.catalog_json
                        (tgt.length===3 && site2[tgt[0]] && site2[tgt[0]].parse_funcs[tgt[1]])? site2[tgt[0]].parse_funcs[tgt[1]][tgt[2]] : // vichan.catalog_json.XXX
                        null;
          if (tgt_obj) site2[i].parse_funcs[j][k] = tgt_obj;
        }
      }
////      if (i!=='DEFAULT' || j!=='common') { // working code.
////        var proto = (site2[i].parse_funcs[j].hasOwnProperty('proto'))? site2[i].parse_funcs[j].proto : (site2['DEFAULT'].parse_funcs[j] && i!=='DEFAULT')? 'DEFAULT.'+j : 'DEFAULT.common';
//////        if (pref.debug_mode['0']) site2[i].parse_funcs[j].proto = proto; // debug
////        proto = (proto.indexOf('.')==-1)? site2[i].parse_funcs[proto] : site2[proto.substr(0,proto.indexOf('.'))].parse_funcs[proto.substr(proto.indexOf('.')+1)];
////        site2[i].parse_funcs[j].__proto__ = proto;
//////        if (!pref.debug_mode['0']) delete site2[i].parse_funcs[j].proto;
////        delete site2[i].parse_funcs[j].proto;
////      }
      if (i!=='DEFAULT' || j!=='common') {
        var tmp;
        var proto = (site2[i].parse_funcs[j].hasOwnProperty('proto'))? site2[i].parse_funcs[j].proto :
                    (site2[i].hasOwnProperty('proto') && i!=='DEFAULT')? site2[i].proto + '.' + j :
                    (site2['DEFAULT'].parse_funcs[j] && i!=='DEFAULT')? 'DEFAULT.'+j : 'DEFAULT.common';
                    (tmp = j.slice(j.indexOf('_')+1), site2['DEFAULT'].parse_funcs[tmp] && i!=='DEFAULT')? 'DEFAULT.'+tmp : 'DEFAULT.common'; // tmp is one of /(html|json)(_template)*/
//        var proto2= (site2[i].parse_funcs[j].hasOwnProperty('proto'))? site2[i].parse_funcs[j].proto : // old
//                    (site2['DEFAULT'].parse_funcs[j] && i!=='DEFAULT')? 'DEFAULT.'+j : 'DEFAULT.common';
//if (proto!==proto2) console.log(i+':'+j+', '+proto+', '+proto2+', '+(proto===proto2));
        proto = (j.slice(-9)==='_template' && proto==='DEFAULT.common')? site2['DEFAULT'].wrap_to_parse.get_getters(j.indexOf('html')!=-1) :
                (proto.indexOf('.')==-1)? site2[i].parse_funcs[proto] : site2[proto.substr(0,proto.indexOf('.'))].parse_funcs[proto.substr(proto.indexOf('.')+1)];
//        if (proto!==null) proto = (proto.indexOf('.')==-1)? site2[i].parse_funcs[proto] : site2[proto.substr(0,proto.indexOf('.'))].parse_funcs[proto.substr(proto.indexOf('.')+1)];
        site2[i].parse_funcs[j].__proto__ = proto;
        delete site2[i].parse_funcs[j].proto;
      }
    }
    if (i!=='DEFAULT') {
      proto = (site2[i].hasOwnProperty('proto'))? site2[i].proto : 'DEFAULT';
//      if (pref.debug_mode['0']) site2[i].proto = proto; // debug
      if (site2[i].parse_funcs) site2[i].parse_funcs.__proto__ = site2[proto].parse_funcs;
      if (site2[i].patch) site2[i].patch.__proto__ = site2[proto].patch;
      site2[i].__proto__ = site2[proto];
//      if (!pref.debug_mode['0']) delete site2[i].proto;
      delete site2[i].proto;
    }
  }
//  for (var i in site2) {
//    if (i==='DEFAULT' || i==='common') continue;
//    if (!site2[i].hasOwnProperty('prep_own_posts_event')) {
//      site2[i].prep_own_posts_event = site2[i].prep_own_posts_event.bind(site2[i]); // DOESN'T WORK, WHY??? MAY THIS BE CHROME'S BUG??? but I found a better solution.
//console.log('DEBUG: '+i);
//    }
//  }
//    site2['lain'].prep_own_posts_event = site2['lain'].prep_own_posts_event.bind(site2['lain']); // works correctly.
//    site2['KC'].prep_own_posts_event = site2['KC'].prep_own_posts_event.bind(site2['KC']);

//  for (var i in site2) if (i!=='DEFAULT' && i!=='common') {
//    var proto = site2[i].hasOwnProperty('proto')? site2[i].proto : false;
//    site2[i].__proto__ = (proto)? site2[proto] : site2['DEFAULT'];
//    if (proto) delete site2[i].proto;
//    for (var j in site2[i].parse_funcs) {
//      proto = site2[i].parse_funcs[j].hasOwnProperty('proto')? site2[i].parse_funcs[j].proto : false;
//      if (proto) proto = (proto.indexOf('.')==-1)? site2[i].parse_funcs[proto] : site2[proto.substr(0,proto.indexOf('.'))].parse_funcs[proto.substr(proto.indexOf('.')+1)];
//      site2[i].parse_funcs[j].__proto__ = (proto)? proto
//                                        : (site4.parse_funcs[j])? site4.parse_funcs[j] : site4.parse_funcs.common;
//      if (proto) delete site2[i].parse_funcs[j].proto;
//    }
//  }
//  for (var i in site4.parse_funcs) if (i!=='common') site4.parse_funcs[i].__proto__ = site4.parse_funcs.common;

//  if (  localStorage &&   localStorage[pref.script_prefix+'.pref']) pref_func.pref_overwrite(pref,JSON.parse(  localStorage[pref.script_prefix+'.pref']),true);
//  if (sessionStorage && sessionStorage.pref) pref_func.pref_overwrite(pref,JSON.parse(sessionStorage.pref),true);
//  pref_func.site2_json();
//  site2['ALL'] = {};

  var site3 = {setup: function(i){
    site3[i] = {boards: null, /*tags: {cs:[], ci:[]},*/ own_posts:{}};
    var href = window.location.href;
    if (site2[i].hasOwnProperty('check_func')) if (!site2[i].check_func(href)) delete site2[i].check_func; // reduce memory consumption. // keep myself for SPA
    site.nicknames.push(i);
//    site2['ALL'][site2[i].domain_url] = i;
  }};
  for (var i in site2) {
    site0.site2keys.push(i);
    if (i!=='DEFAULT' && i!=='common' && i!=='meguca1' && i!=='meguca2') site3.setup(i);
  }
  if (pref.test_mode['208'] && CCAPI.suppress) return;
  if (!site.embed_to['top']) site.embed_to['top'] = function(){return site.root_body && site.root_body.firstChild;};
  if (!site.embed_to['bottom']) site.embed_to['bottom'] = function(){return site.root_body && site.root_body.lastChild;};

  if (pref.cli.auto) pref_func.site2_json(['site2','pref_func','site3','site']);

  if (window.top != window.self) {
    if (site.nicknames.indexOf(window.name)==-1 && window.name!==site.embed_frame) return; //don't run on frames or iframes
    for (var i in site2) if (site.nicknames.indexOf(i)!=-1 && i!=site.nickname && site2[i].nickname!=site.nickname) delete site2[i]; // '&& site2[i].nickname!=site.nickname' for '4chan_i'
  }

//  pref_func.str2obj('catalog_board_list_str');
//  pref_func.str2obj2(pref.catalog,'style_general_list_obj2',pref.catalog.style_general_list_str);
//  pref_func.str2obj2(pref.catalog.board,'ex_list_obj2',pref.catalog.board.ex_list_str);
  if (!site0.isStep) pref_func.obj_init();
  pref3.stats.use = pref.stats.use;
  pref3.stats.estimate_posts = pref.stats.estimate_posts;


  var ports = {};
  var messages_to_send = {};
  var init_func = {};
  var receive_func = {};
  function make_port_parent(name,win){
    if (ports[name]) send_message(name,['CLOSE']);
    ports[name] = 'init';
    init_func[name] = function(e){initialize(e,name,win);};
    window.addEventListener('message', init_func[name], false);
    if (pref.debug_mode['0']) console.log(window.name + ': Waiting for connection from '+name+' ...');
    function initialize(e,name,win) {
      if (pref.debug_mode['0']) console.log(window.name + ': Connecting from '+e.data);
//      if (e.source==win) {
//      if (e.source==win && site2[e.data]) { // remove "{"name":"twttr:private:requestArticleUrl"}" from someone...
//      if (e.source==win && site.nicknames.indexOf(e.data)!=-1) { // remove "{"name":"twttr:private:requestArticleUrl"}" from someone... in Tampermonkey. // BUG.
//      if (e.source==win && new RegExp(site.nicknames.join('|')).test(e.data)) { // remove "{"name":"twttr:private:requestArticleUrl"}" from someone... in Tampermonkey.
      if ((e.source==win && new RegExp(site.nicknames.join('|')).test(e.data)) ||
          (e.source==site.embed_frame_win && site.whereami==='frame' && e.data.indexOf('CLOSE')==-1)) { // remove "{"name":"twttr:private:requestArticleUrl"}" from someone... in Tampermonkey.
        if (pref.debug_mode['0']) console.log(window.name + ': Connected successfully.');
        init_receive_port(name, brwsr.hasMessageChannel? e.ports[0] : win);
        window.removeEventListener('message', init_func[name], false);
        delete init_func[name];
        if (messages_to_send[name]) {
          for (var i=0;i<messages_to_send[name].length;i++) if (send_message(name,messages_to_send[name][i][0], null, messages_to_send[name][i][1])) break;
          delete messages_to_send[name];
        }
        if (name=='_blank') send_message(name,['CLOSE']);
      } else if (pref.debug_mode['0']) console.log(window.name + ': FAIL.');
    }
  }
  function make_port_child(parent){
    if (pref.debug_mode['0']) console.log(window.name + ': Try to connect to parent...');
    var channel = brwsr.hasMessageChannel? new MessageChannel() : null;
    init_receive_port('parent', channel? channel.port1 : parent);
    parent.postMessage(window.name, '*', channel? [channel.port2] : undefined); // Specification(order of arguments) was changed from chrome59.
//    if (brwsr.hasMessageChannel) { // working code
//      var channel = new MessageChannel();
//      init_receive_port('parent',channel.port1);
//      parent.postMessage(window.name, '*', [channel.port2]); // Specification(order of arguments) was changed.
////      parent.postMessage(window.name, [channel.port2], '*'); // chrome 59 doesn't work this
//    } else { // FF doesn't support channel messaging.
//      init_receive_port('parent',parent);
//      parent.postMessage(window.name, '*');
//    }
  }
  function init_receive_port(name,port){
    ports[name] = port;
    receive_func[name] = function(e){receive_message(e,name);};
    if (brwsr.hasMessageChannel) {
      port.addEventListener('message', receive_func[name], false);
      port.start();
    } else window.onmessage = receive_func[name];
  }
  function close_connection(name){
    if (brwsr.hasMessageChannel) {
      ports[name].close();
      ports[name].removeEventListener('message', receive_func[name], false);
    }
    delete receive_func[name];
    delete ports[name];
  }
  function send_message(name,message,win,list){
    if (Array.isArray(message) && Array.isArray(message[0])) {message = message[0]; console.log('WARNING, PATCHED: send_mesage');}// patch, ready for depatch and testing.
//    if (typeof(val[0])==='string') val = [val];
//    if (!ports[name]) make_port_parent(name,win);
    if (win) make_port_parent(name,win);
    if (ports[name]==='init') { // chrome works at ==.
      if (!messages_to_send[name]) messages_to_send[name] = [];
      messages_to_send[name].push([message, list]);
    } else {
      if (!ports[name]) {
        name='parent';
        if (!ports[name]) return; // for debug using direct connection
      }
      if (pref.debug_mode['0']) console.log(window.name + ': Sent to '+name+': '+ JSON.stringify(message).substr(0,80));
      if (brwsr.hasMessageChannel) ports[name].postMessage(message, list);
      else ports[name].postMessage(message,'*', list);
//      if (!brwsr.ff) ports[name].postMessage(JSON.stringify(val[i]));
//      else ports[name].postMessage(JSON.stringify(val[i]),'*');
      if (message[0]=='CLOSE') {close_connection(name); return true;} // fallback for chrome's removing extensions.
    }
  }
  var received_messages_404 = null;
  var send_message_emu = [];
  function receive_message_emu(name) {
    while (send_message_emu.length>0) receive_message({data:send_message_emu.shift()}, name || site.key);
  }
  function jump_to_time(th, time){
    if (pref.INST.testMode167 /*pref.test_mode['167']*/ && time<0x10000) time = th.posts[time-1] && th.posts[time-1].time || time;
    var idx = site2[th.domain].mark_newer_posts2(th.posts,time,pref.thread_reader.unmark_on_hover, true); //  || null;
//      var marked_first_post = (common_obj.thread_reader && pref.test_mode['16'])? common_obj.thread_reader.mark_newer_posts(val[1])
//                                                        : site2[site.nickname].mark_newer_posts(document,val[1],pref.thread_reader.unmark_on_hover, true);
    var tgt_post = gGEH.marked_first_post && th.posts[idx]? (gGEH.marked_first_post.time_tu < th.posts[idx].time_tu? gGEH.marked_first_post : th.posts[idx])
                 : gGEH.marked_first_post || th.posts[idx];
    if (tgt_post) {
      var tgt_top = tgt_post.pn.offsetTop;
      if (!tgt_top && pClg) {
        pClg.show_catalog_delayed(); // for lazy merge, force to draw.
        tgt_top = tgt_post.pn.offsetTop;
        if (!tgt_top) { // for deleted post
          while (idx>0 && !th.posts[idx].pn.parentNode) idx--;
          tgt_top = th.posts[idx].pn.offsetTop + th.posts[idx].pn.offsetHeight;
        }
      }
      scrollTo(0, tgt_top - site.header_height());
//      if (pClg) pClg.GEH.last_viewed = marked_first_post; // BUG!!! error at racing condition, messages may be earlier than creation of pClg.
//      else gGEH.last_viewed = marked_first_post; // avoids racing condition
    } else scrollTo(0,document.body.clientHeight - window.innerHeight); // to the last
    gGEH.marked_first_post = tgt_post;
  }
  function receive_message(e,name) {
    if (pref.debug_mode['0']) console.log(window.name + ': Received from '+name+': '+e.data.toString().substr(0,80));
    var val = e.data;
    var list = e.ports;
//    var val = JSON.parse(e.data);
    if (site.whereami==='404' && val[0]!=='CLOSE') {if (!received_messages_404) received_messages_404 = [e.data]; else received_messages_404.push(e.data); return;}
    if (typeof(val)=='string') val=JSON.parse(val); // patch for GM.
    if (val[0]=='HTTPD') httpd.sub_funcs(val[1]);
    else if (val[0]=='IDB' && IDB) IDB.sub_funcs(val[1], list);
    else if (val[0]=='ARCHIVER' && archiver) archiver.sub_funcs(val[1], val[2]);
    else if (val[0]=='CLOSE') close_connection(name);
    else if (val[0]=='MARK') {
      if (val[1]<0x10000) pref.INST.testMode167 = true;
      if (val[1]>0) {
        jump_to_time(site.myself, val[1]);
//      var marked_first_post = site.myself && site2[site.nickname].mark_newer_posts2(site.myself.posts,val[1],pref.thread_reader.unmark_on_hover, true) || null; // working code
////      var marked_first_post = (common_obj.thread_reader && pref.test_mode['16'])? common_obj.thread_reader.mark_newer_posts(val[1])
////                                                        : site2[site.nickname].mark_newer_posts(document,val[1],pref.thread_reader.unmark_on_hover, true);
//      if (marked_first_post) {
//        scrollTo(0,marked_first_post.offsetTop - site.header_height());
////        if (pClg) pClg.GEH.last_viewed = marked_first_post; // BUG!!! error at racing condition, messages may be earlier than creation of pClg.
////        else gGEH.last_viewed = marked_first_post; // avoids racing condition
//      } else scrollTo(0,document.body.clientHeight - window.innerHeight); // to the last
        gGEH.time_jumped = val[1];
        if (pClg && pClg.mode==='thread') for (var name in pClg.threads) if (name!==site.key && pClg.threads[name][1]) jump_to_time(pClg.threads[name][16], val[1]); // for racing condition
      }
      if ((val[1]>0 || pref.test_mode['205']) && common_obj.thread_reader) common_obj.thread_reader.mark_posts_from_parent(val[1]);
    }
////    else if (val[0]=='SUBFRAME_INIT') http_req.remote();
////    else if (val[0]=='SUB_GET') http_req.sub_get(val[1]);
////    else if (val[0]=='SUB_ACK') http_req.sub_ack(val[1]);
////    else if (val[0]=='SUB_DEST') http_req.sub_dest(val[1]);
    else if (val[0]=='OWN_POSTS') {
      if (site2[val[1]].prep_own_posts_event_received) site2[val[1]].prep_own_posts_event_received(val[2]);
      else site3[val[1]].own_posts = val[2];
    } else if (val[0]=='TRIAGE') {
//console.log('receive: TRIAGE, '+val[1]+', '+val[2]+', '+val[3]);
//      if (catalog_obj && catalog_obj.catalog_func()!=null) catalog_obj.catalog_func().triage_exe_0(val[1],val[2],'',true,val[3]);
      if (catalog_obj && catalog_obj.catalog_func()!=null) catalog_obj.catalog_func().triage_exe_pipe(val[1]);
    } else if (val[0]=='UIP') {
      if (uip_tracker) uip_tracker.annotate_from_catalog(val[1]);
    }
else if (pref.test_mode['34'] && val[0]==='ECHO') setTimeout(function(){send_message(window.name? 'parent' : 'meguca',['ECHO',val[1]])},5000);
  }
//  function receive_message(e,name) { // working code for old http_req
//    if (pref.debug_mode['0']) console.log(window.name + ': Received from '+name+': '+e.data.toString().substr(0,80));
//    var val = JSON.parse(e.data);
//    if (typeof(val)=='string') val=JSON.parse(val); // patch for GM.
//    if (val[0]=='CLOSE') close_connection(name);
//    else if (val[0]=='MARK' && val[1]>=0) {
//      var offset_top = site2[site.nickname].mark_newer_posts(document,val[1]);
//      scrollTo(0,offset_top);
//    } else if (val[0]=='SUBFRAME_INIT') {
//      http_req.remote();
//    } else if (val[0]=='SUB_GET') {
//      http_req.get(val[1][0],val[1][1],val[1][2],val[1][3],val[1][4],val[1][5],val[1][6]);
//    } else if (val[0]=='SUB_ACK') {
//      iframes[name][1]--;
//      http_req.onload_local(val[1],val[2]);
//    }
//  }
  function close_all(){
    for (var name in ports) send_message(name,['CLOSE']);
  }
  window.addEventListener('beforeunload', close_all, false);
  if (window.opener) {
//console.log('child');
    make_port_child(window.opener);
////    for (var i in site2) // moved to before.
////      if (site2[i].nickname===window.name) {
////        for (var i in site.features) site.features[i] = false;
////        brwsr.sw_cache = null;
////        pref.cloudflare.auto_reload = false;
////        break;
////      }
    if (site2[window.name] && site2[window.name].prep_own_posts_event) {
      window.addEventListener('storage', site2[window.name].prep_own_posts_event, false);
      site2[window.name].prep_own_posts();
      site2[window.name].prep_own_posts_event();
    }
  }
  if (pref.cloudflare.auto_reload) {
    var cf_error = document.getElementsByClassName('cf-error-code');
    if (cf_error.length>0 && parseInt(cf_error[0].textContent,10)>=500) setTimeout(function(){location.reload();},pref.cloudflare.auto_reload_time*60000);
  }

//  var ports = {}; // working code for test
//  function make_port_parent(name, val){
//    ports[name] = val;
//    var port;
//    if (pref.debug_mode['0']) console.log('parent: '+name+', '+val);
//    window.onmessage = function(e) {
//      if (pref.debug_mode['0']) console.log('from child #unkown: '+e.data);
//      port = e.ports[0];
//      port.postMessage(JSON.stringify(['MARK',val]));
//      port.postMessage(JSON.stringify('CLOSE'));
//      port.close();
////      port.onmessage = function(e) {
////        if (pref.debug_mode['0']) console.log(e.data);
////        port.postMessage('received : ('+Date.now()+')');
////      }
//    }
//  }
//
//  function make_port_child(prt){
//    if (pref.debug_mode['0']) console.log('child :');
//    var channel = new MessageChannel;
//    var port = channel.port1;
//    prt.postMessage('Connection: ', [channel.port2], '*');
////    prt.postMessage('start', [channel.port2], '*');
////    setInterval(function() {port.postMessage('sent : ' + (+new Date));}, 2000);
////    port.onmessage = function(e) {if (pref.debug_mode['0']) console.log(e.data);};
//    port.onmessage = function(e) {
//      if (pref.debug_mode['0']) console.log(e.data);
//      var val = JSON.parse(e.data);
//      if (val[0]=='CLOSE') port.close();
//      else if (val[0]=='MARK' && val[1]>=0) {
//        var offset_top = site2[site.nickname].mark_newer_posts(document,val[1]);
//        scrollTo(0,offset_top);
//      }
//    };
//  }
//  if (window.opener) make_port_child(window.opener);

  var httpd = (function(){
//    var cooldowns = {};
//    function cooldown_check(req){
//      var period = pref.scan.cooldowns[req.domain];
//      if (!period) return req;
//      var now = Date.now();
//      if (!cooldowns[req.domain]) {cooldowns[req.domain] = {domain:req.domain, urls:{}, queue:[], time:now}; return req;}
//      var cooldown = cooldowns[req.domain];
//      var rest = cooldown.time + period*1000 - now;
//      if (rest<=0 && !queue_get(cooldown.queue)) {cooldown.time = now; return req;}
//      queue_add(cooldown.queue, req);
//      if (rest<=0) {cooldown.time = now; return queue_get(cooldown.queue);}
//      setTimeout(cooldown_wakeup.bind(null,cooldown), rest+1000); // 1000 for safty
//      return null;
//    }
//    function cooldown_wakeup(cooldown){
//      queue_add(reqs, queue_get(cooldown.queue));
//      dispatch_if_idle();
//    }
//    function queue_add(queue, req){
//      var i=0
//      while (i<queue.length && queue[i].priority>req.priority) i++;
//      queue.splice(i,0,req);
//    }
//    function queue_get(queue){
//      while (queue[0] && queue[0].tgts.length===0) queue.shift();
//      return queue[0];
//    }
    var iframes = {};
    var iframes_timeout = {};
    var local = true;
//    var parser = new DOMParser();
//    var serializer = new XMLSerializer();
    var xhrs = new Map();
    var xhrs_free = [];
    var xhrs_count = 0;
//    var xhrs_count_by_domain = {};
    var remote_opened = {};
    var remote_count = 0;
    var remote_reqs = {};
    var reqs = [];
    var reqs_waiting_finish = [];
    var pause = 0;
    var crawler_spawn   = new Watchdog(dispatch, pref.scan.crawler_idle_time_to_spawn);
    var xhrs_reqs = [];
    var crawler_timeout = new Watchdog(xhr_timeout, 30000);
    var health_indicator = {shift:function(){return {report:function(){}};}}; // initial dummy indicator for 8chan, see tag.generate_caller, (and uip_tracker)
    function dispatch_if_idle(){
      if (!crawler_spawn.id) dispatch();
    }
    function dispatch(xhr, req_from){
//      var req = queue_get(reqs);
//      var i=0;
//      while (req && req.ERR_5xx && req_from!==req) req = reqs[++i];
//      if (!pause && req && (xhrs_count<pref.scan.crawler || req.priority>=8)) {
      if (!pause) for (var p=8;p>=0;p--) if (reqs[p] && (xhrs_count<pref.scan.crawler || p==8)) {
        while (reqs[p][0] && reqs[p][0].tgts.length===0) reqs[p].shift();
        var req = reqs[p][0];
        var i=0;
        while (req && req.ERR_5xx && req_from!==req) req = reqs[p][++i];
        if (!req) continue;
        if (!req.INDICATOR) {
          req.INDICATOR = (req.indicator || health_indicator).shift('limegreen', req.initiator[0], req.initiator, p);
          req.INDICATOR.report({start:Date.now(), prog:req}); // make reference loop.
          req.IDX = 0;
          req.RUNNING = 0;
          req.SUC = 0;
          if (!req.max) req.max = req.tgts.length;
        }
        var req_1 = (req.ABORT)? null : (req.get_tgt_func)? req.get_tgt_func(req) : {REQ:req, __proto__:req.tgts[req.IDX++]};
        if (req.IDX>=req.max || !req_1 && req.ABORT) reload_reqs(req,p);
//        if (req.IDX>=req.max) reload_reqs(req,p);
        xhrs_count++; // THIS CAN BE A SINK AND CAUSE DEADLOCK WHEN OVER X_FRAME_OPTIONS.
//        xhrs_count_by_domain[req_1.domain] = (xhrs_count_by_domain[req_1.domain] || 0) + 1;
        req.RUNNING++;
        if (req_1) {
          req.INDICATOR.report({tgt:req_1.tgt || url2tgt(req_1.url)});
          if (req_1.domain===site.nickname && !req_1.domain_xhr || pref.network.cross_domain!=='indirect' || req_1.xhr_local) download_local(xhr, req_1);
          else {
            if (xhr) xhrs_free[xhrs_free.length] = xhr;
            download_remote(req_1);
          }
          if (pref.scan.crawler_adaptive) crawler_spawn.restart(pref.scan.crawler_idle_time_to_spawn);
          else if (xhrs_count<pref.scan.crawler) dispatch();
        } else {
//          if (!(req.IDX<req.max && (req.refresh || req.found_threads<req.max_threads))) {
//            reload_reqs(req,p);
//            req.FINISHED += req.max - req.IDX;
//            req.IDX = req.max;
//          }
          setTimeout(function(){xhr_onload_1({type:'SKIPPED_TO_END'}, xhr, {REQ:req}, true, {date:Date.now(), status:200});},0);
        }
        return;
      }
      if (crawler_spawn.id) crawler_spawn.stop();
      if (xhr) xhrs_free[xhrs_free.length] = xhr;
    }
    function url2tgt(url){return url.substr(url.lastIndexOf('/')+1);}
    function reload_reqs(req,p){
      if (reqs[p] && reqs[p].length>1) reqs[p].shift();
      else reqs[p] = null;
      reqs_waiting_finish[reqs_waiting_finish.length] = req;
    }
    function download_remote(req){ // , prep_only){
      var domain = req.domain_xhr || req.domain;
      if (!iframes[domain]) {
        var func_timeout = function(req_in){xhr_onload_1({type:'SKIPPED'}, undefined, req_in || req, false, {date:Date.now(), status:499});};
        if (iframes[domain]===undefined) {
          cnst.make_iframe(domain);
          iframes[domain] = window.open(site2[domain] && site2[domain].home || req.url, domain);
          send_message(domain,['HTTPD',['SUB_FRAME_INIT']],iframes[domain]);
          iframes_timeout[domain] = [setTimeout(function(){
            iframes[domain] = false;
            iframes_timeout[domain].slice(1).forEach(function(v){func_timeout(v)});
            delete iframes_timeout[domain];
          }, pref.network.timeout*1000)];
        } else if (iframes[domain]===false) {setTimeout(func_timeout,0); return;}
      }
      remote_opened[domain] = null;
//      if (prep_only) return;
      remote_reqs[remote_count] = req;
      if (!req.archive) req.responseType = 'text';
      send_message(domain,['HTTPD',['SUB_GET',[{ID:remote_count++, url:req.url, responseType:req.responseType, domain:req.domain,
                                                board:req.board, no:req.no, key:req.key, archive:req.archive,
                                                to_file:req.to_file, to_idb:req.to_idb, kind:req.kind, timestamp:req.timestamp,// for archive
                                                domain_xhr:req.domain_xhr,
                                               }]]]);
      if (iframes_timeout[domain]) iframes_timeout[domain].push(req); 
    }
    function sub_frame_init_ack(args){
      clearTimeout(iframes_timeout[args[0]][0]);
      delete iframes_timeout[args[0]];
    }
    function sub_get(args){
      download_local(undefined,args[0]);
    }
    var GM_XMLHttpRequestWrapper = function(){
      this.onload  = this.onload.bind(this);
      this.onerror = this.onerror.bind(this);
      this.onabort = this.onabort.bind(this);
    };
    GM_XMLHttpRequestWrapper.prototype = {
      open: function(method, url){
        this.method = method;
        this.url = url;
      },
      send: function(){
        this.response = null;
        GM_xmlhttpRequest(this);
      },
      onload: function(e){
        this.response = e.response;
        if (!this.response) // GM has 'response', this is for emulation.
          this.response = (this.responseType==='document')? new DOMParser().parseFromString(e.responseText, 'text/xml')
                        : (this.responseType==='json')? JSON.parse(e.responseText)
//                   : (this.responseType==='arraybuffer')? new DOMParser().parseFromString(e.responseText, 'text/xml')
//                   : (this.responseType==='blob')? new DOMParser().parseFromString(e.responseText, 'text/xml')
                        : e.responseText;
// GM has 'arraybuffer' from 3.2, // http://www.greasespot.net/2015/05/greasemonkey-32-release.html
// TM has 'arraybuffer' from ? // https://github.com/Tampermonkey/tampermonkey/issues/279
//        if (this.responseType==='arraybuffer') console.log(this.url+', '+this.response.byteLength);
        this.onXXX('load', e, xhr_onload);
      },
      onerror: function(e){this.onXXX('error',e, xhr_onload);},
      onabort: function(e){this.onXXX('abort',e, xhr_onload);},
      onXXX: function(type, result, callback){
        this.type = type;
        this.result = result;
        callback(this);
      },
      onXXX_factory: function(type, func){
        return function(result){this.onXXX(type, result, func);}.bind(this);
      },
      set onreadystatechange(v){this._onreadystatechange = (v)? this.onXXX_factory('readystatechange',v) : v;},
      get onreadystatechange(){return this._onreadystatechange || this.dummy_func;},
      set onprogress(v){this._onprogress = (v)? this.onXXX_factory('progress',v) : v;},
      get onprogress(){return this._onprogress || this.dummy_func;},
      dummy_func: function(){}, // for chrome emulation, it uses existence of property, not a value.
      get status(){return this.result && this.result.status || 1404;},
      get responseText(){return this.result.responseText;},
      get target(){return this;},
      result: null,
    };
    function download_local(xhr, req){
      if (!xhr) { 
        if (xhrs_free.length>0) xhr = xhrs_free.pop();
        else {
          if (pref.network.cross_domain==='GM') xhr = new GM_XMLHttpRequestWrapper();
          else {
            xhr = new XMLHttpRequest();
            xhr.onload  = xhr_onload;
            xhr.onerror = xhr_onload;
            xhr.onabort = xhr_onload;
          }
        }
      }
      xhrs.set(xhr, req);
      xhr.open('GET', req.url);
      xhr.responseType = req.responseType;
      if (req.overrideMimeType) xhr.overrideMimeType(req.overrideMimeType); // effective eternally. requires to avoid this xhr to be recycled.
//      if (pref.test_mode['186'] && req.tgt.indexOf('/news24/')>=0) xhr.overrideMimeType('application/xml');
      xhr.send();
      xhr_timeout_start(xhr,Date.now(),req);
    }
    function xhr_timeout_start(xhr,date,req){
      req.TIMEOUT = ((req.TIMEOUT)? req.TIMEOUT : date) + pref.network.timeout*1000;
      xhrs_reqs[xhrs_reqs.length] = xhr;
      if (xhrs_reqs.length===1) crawler_timeout.restart(pref.network.timeout*1000);
    }
    function xhr_timeout_restart(xhr,date){
      var idx = xhrs_reqs.indexOf(xhr);
      if (idx<0) return true; // timeouted.
      xhrs_reqs.splice(idx,1);
      if (idx===0) {
        if (xhrs_reqs.length>0) {
          var timeout = xhrs.get(xhrs_reqs[0]).TIMEOUT - date;
          if (timeout>0) crawler_timeout.restart(timeout);
          else xhr_timeout();
        } else crawler_timeout.stop();
      }
    }
    function xhr_timeout(){
      var xhr = xhrs_reqs[0];
      if (pref.debug_mode['25']) console.log('xhr_timeout: '+xhr.readyState+', '+xhr.responseURL);
      if (!xhr.onreadystatechange) {
        xhr.onreadystatechange = xhr_onreadystatechange;
        xhr.onprogress = xhr_onreadystatechange;
        xhr_onreadystatechange({target:xhr});
      } else xhr_onload({target:xhr, type:'TIMEOUT'});
    }
    function xhr_onreadystatechange(e){
      var xhr = e.target;
      var req = xhrs.get(xhr);
      if (pref.debug_mode['25']) console.log('xhr_onreadystatechange: '+req.url+((e.type==='progress')? ': '+e.loaded+'/'+e.total:''));
      if (e.type==='progress') e.target.onreadystatechange = null;
      var date = Date.now();
      xhr_timeout_restart(xhr,date);
      xhr_timeout_start(xhr,date,req);
    }
    function xhr_onload(e){
      var xhr = e.target;
      var date = Date.now();
      if (xhr_timeout_restart(xhr,date)) return; // timeouted.
      xhr.onreadystatechange = null;
      xhr.onprogress = null;
      var req = xhrs.get(xhr);
      var valid = xhr.status===200 || xhr.status===304;
      var value = (xhr.responseType==='text')? {date: date, status: xhr.status, responseText: valid? (req.xhrPatchFunc? req.xhrPatchFunc(xhr.responseText) : xhr.responseText) : null}
                                             : {date: date, status: xhr.status, response:     valid? (req.xhrPatchFunc? req.xhrPatchFunc(xhr.response)     : xhr.response)     : null};
      if (req.overrideMimeType) {
        xhrs.delete(xhr);
        xhr = null; // avoid this xhr to be recycled.
      }
      xhr_onload_1(e, xhr, req, valid, value);
    }
    function xhr_onload_1(e, xhr, req, valid, value){ // CACHE HIT ENTRY HERE
      if (local) {
        xhrs_count--;
//        xhrs_count_by_domain[req.domain]--;
        var end = --req.REQ.RUNNING === 0 && (req.REQ.IDX >= req.REQ.max || req.ABORT);
//        var end = ++req.REQ.FINISHED === req.REQ.IDX && req.REQ.IDX >= req.REQ.max;
        dispatch(xhr, (value.status<500)? req : null);
        if (end) end_proc_httpd(req.REQ);
        // CACHE WRITE CODE HERE
        if (valid) {
          req.REQ.SUC++;
          onload_local(req, value);
        } else {
          if (value.status>=500 && !req.REQ.ERR_5xx) req.REQ.ERR_5xx = setTimeout(
            (function(req){
              return function(){
                delete req.ERR_5xx;
                dispatch_if_idle();
              }})(req.REQ),30000);
          req.REQ.INDICATOR.report({err:(req.tgt||url2tgt(req.url))+'('+value.status+((e.type!=='load')? ':'+e.type : '')+')'});
          if (req.REQ.callback_1_fail) req.REQ.callback_1_fail(req, value, req.REQ);
        }
        if (end) end_proc_user(req.REQ, value.date);
        req.REQ = null; // cut reference loop for GC.
      } else {
        send_message('parent',['HTTPD',['SUB_ACK',[req.ID, {type:(req.archive)? 'ARCHIVE' : e.type}, valid, (req.archive)? {date:value.date, status:value.status, responseText:'', response:{}} : value]]]);
        if (xhr) xhrs_free[xhrs_free.length] = xhr;
        if (valid && req.archive) onload_archive(req, value);
      }
    }
    function sub_ack(args){
      var req = remote_reqs[args[0]];
      delete remote_reqs[args[0]];
      xhr_onload_1(args[1], undefined, req, args[2], args[3]);
    }
    function end_proc_httpd(req){ // called local only
      reqs_waiting_finish.splice(reqs_waiting_finish.indexOf(req),1);
      if (req.archive) req.TAR_FLUSH = remote_opened;
      free_xhrs();
    }
    function free_xhrs(){
      if (xhrs_count===0) {
        xhrs.clear();
        if (local) for (var domain in remote_opened) send_message(domain,['HTTPD',['SUB_FREE']]);
        remote_count = 0;
        remote_opened = {};
      } else for (var i=0;i<xhrs_free.length;i++) xhrs.delete(xhrs_free[i]);
      xhrs_free = [];
    }
    function onload_local(req,value) {
      if (req.responseType==='text') {
        if (pref.test_mode['154'] && req.data_type==='html' && site2[req.domain].HTMLParser) { // DOMParser causes massive leak particular in BBC, see https://stackoverflow.com/questions/56451731/dom-parser-chrome-extension-memory-leak
          value.response = new site2[req.domain].HTMLParser(value.responseText);
//          for (var i=0;i<100;i++) value.response = new DOMParser().parseFromString(value.responseText, 'text/'+req.data_type); // for test
        } else if (pref.test_mode['154'] && req.data_type==='xml' && site2[req.domain].XMLParser) {
          value.response = site2[req.domain].XMLParser(value.responseText);
        } else if (req.data_type==='html' || req.data_type==='xml') value.response = new DOMParser().parseFromString(value.responseText, 'text/'+req.data_type); // causes massive leak on chrome50
        else if (req.data_type==='json') {
          if (brwsr.ff) value = Object.create(value); // avoid error in FF, Error: Not allowed to define cross-origin object as property on [Object] or [Array] XrayWrapper
          value.response = JSON.parse(value.responseText);
        }
      }
      req.REQ.callback_1(req, value, req.REQ);
//        req.REQ.callback(req.key, value, req.callback_arg);
      if (req.responseType==='text' && req.data_type==='html') value.response = null; // doesn't effect at all.
    }
    function onload_archive(req, value){
      if (typeof(req.archive)==='string') {
        archiver.download_url4(value.response, req.archive);
        return;
      }
      if (req.from && local) delete archiver.list_all_obj_downloading[req.from];
      var ignore_domain = pref.network.cross_domain!=='indirect';
      if (req.domain===site.nickname && (!local || !req.domain_xhr) || ignore_domain) {
//      if ((req.domain_xhr||req.domain)===site.nickname || ignore_domain) {
        var suffix = req.kind + '_'+ req.url.replace(/.*\//g,'');
        if (req.to_file) archiver.download_url3(value.response, req, suffix, req.timestamp);
        if (req.to_idb) if (pref.features.IDB) IDB.req(req.domain, req.board, req.no, suffix, value.response, 'put');
      }
      if (local && req.REQ.TAR_FLUSH) { // remote doesn't have 'req.REQ'
//        if (check_timestamp_and_flush(req.timestamp)) // flush local
        archiver.tar.flush((xhrs_count===0)? undefined : req.timestamp);
        for (var domain in req.REQ.TAR_FLUSH) send_message(domain,['HTTPD',['SUB_TAR_FLUSH',[(xhrs_count===0)? undefined : req.timestamp]]]); // flush remote
      }
    }
    function onload_archive_fail(req, value){
      if (req.from) delete archiver.list_all_obj_downloading[req.from]; // patch, retry code should be here.
    }
    function check_timestamp_and_flush(timestamp){
      if (reqs[0] && reqs[0][0] && reqs[0][0].timestamp === timestamp && reqs[0][0].tgts.length>0) return false;
//      if (reqs[0] && reqs[0][0] && reqs[0][0].timestamp === timestamp) return false;
      for (var i=0;i<reqs_waiting_finish.length;i++) if (reqs_waiting_finish[i].timestamp===timestamp) return false;
      archiver.tar.flush((xhrs_count===0)? undefined : timestamp);
      return true;
    }
    function end_proc_user(req, date){
      if (req.callback) req.callback(req); // this must be prior to report({end:}), because somefunc reports err to indicator.
      req.INDICATOR.report({end:date});
    }
    function sub_tar_flush(args){
      archiver.tar.flush(args[0]);
    }
    function sub_frame_init(){
      local=false;
      send_message('parent',['HTTPD',['SUB_FRAME_INIT_ACK', [window.name]]]);
    }
    function pause_cancel(){
      if (pause>0) {
        if (--pause>0) pause_timeout.delayed_do();
      } else {
        pause_timeout.cancel();
        dispatch_if_idle();
      }
    }
    var pause_timeout = new DelayBuffer(pause_cancel,10000);
    var sub_funcs = {SUB_GET:sub_get, SUB_ACK:sub_ack, SUB_FREE:free_xhrs, SUB_TAR_FLUSH:sub_tar_flush, SUB_FRAME_INIT:sub_frame_init, SUB_FRAME_INIT_ACK:sub_frame_init_ack};
    return {
      req: function(reqs_in, priority){
        if (!reqs[priority]) reqs[priority] = [reqs_in];
        else reqs[priority].push(reqs_in);
        if (reqs_in.tgts.length!==0) dispatch();
      },
      req_add: function(reqs_in, priority){
        if (!reqs[priority] || reqs[priority].length===0) reqs[priority] = [reqs_in]; // BUG, destroy prototype.
        else reqs[priority][reqs[priority].length-1].tgts = reqs[priority][reqs[priority].length-1].tgts.concat(reqs_in.tgts);
        dispatch_if_idle();
      },
      check_timestamp_and_flush: check_timestamp_and_flush,
      onload_archive: onload_archive,
      onload_archive_fail: onload_archive_fail,
      sub_funcs: function(args){
        sub_funcs[args[0]](args[1]);
      },
      pause_req: function(){
        pause++;
        pause_timeout.delayed_do();
      },
      pause_cancel: pause_cancel,
      set_health_indicator: function(val){health_indicator = val;},
      get isLocal(){return local;},
//      prep_iframe: function(domain){download_remote({domain_xhr:domain},true);},
      req_serial: function(reqs_in, priority){ // not implemented yet
        this.req(reqs_in, priority);
      },
//      xhr_patch: function(url, val){
//        if (!xhr_patch) xhr_patch = {};
////        if (Array.isArray(url)) for (var i=0;i<url.length;i++) xhr_patch[url[i]] = true;
//        xhr_patch[url] = val;
//      }
    };
  })();

  if (site0.isStep && pref.test_mode['142']) return;
  
  var http_req = { // emulation of old interface for uip tracker.
    get: function (sender,key,url,callback,sw_cache,sw_cache_write,callback_arg, archived){
      var dbt = (key.indexOf(',')==-1)? comf.name2dbt(key): key.split(',');
      if (url==='') url = site2[dbt[0]].make_url4(dbt);
      else if (typeof(url)==='string') url = [ url, 'raw'];
      key = dbt.join(',');
      httpd.req({initiator:sender||key,
                 tgts:[{url:url[0], responseType:(url[1]==='html')? 'document' : (url[1]==='raw')? 'text' : url[1], tgt:key, data_type:url[1], domain:dbt[0]}],
                 callback_1:function(req,val){callback(key,val,callback_arg);},
//                 INDICATOR: {shift:function(){}, report:function(){}},
//                 IDX:0, RUNNING:0, SUC:0, max:1,
                },6);
    },
  };

////  var http_req = (function(){ // working code.
////    var iframes = {};
//////    var caches = {}; // prevent occuring multiple access to the same url in short time.
////    var local = true;
////    var parser = new DOMParser();
////    var serializer = new XMLSerializer();
//////    var doc;
//////    var pool; // object pool
////    var reqs = {};
////    var Req = function(sender){
////      this.sender = sender;
////      this.req = null;
////      this.httpd = new XMLHttpRequest();
////      this.httpd.onload  = Req.prototype.onload.bind(this);
////      this.httpd.onerror = this.httpd.onload;
////      this.httpd.onabort = this.httpd.onload;
////    }
////    Req.prototype.onload = function(){
//////      if (local) onload_local(this.sender,{date: Date.now(), __proto__:this.httpd},false); // cause illegal invocation, I don't know why.
////      if (local) {
////        if (this.httpd.responseType==='text') onload_text(this.sender,{date: Date.now(), status: this.httpd.status, responseText: this.httpd.responseText},false); // temporaly patch.
////        else onload_local(this.sender,{date: Date.now(), status: this.httpd.status, response: this.httpd.response},false);
////      } else send_message('parent',[['SUB_ACK',[this.sender,Date.now(),this.httpd.status,this.httpd.responseText]]]);
////    };
////    function get_make_Req(sender, req){
////      if (reqs[sender]===undefined) reqs[sender] = new Req(sender);
////      if (req) reqs[sender].req = req;
////      return reqs[sender];
////    }
////    function destroy_httpd(sender){
////      delete reqs[sender];
////    }
////////    function make_httpd(sender){ // working code.
////////      var httpd = new XMLHttpRequest();
////////      function httpd_events(){
//////////        if (local) onload_local(sender,{date: Date.now(), status: httpd.status, responseText: httpd.responseText},false);
////////        if (local) {
////////          if (httpd.responseType==='text') onload_local(sender,{date: Date.now(), status: httpd.status, responseText: httpd.responseText},false); // temporaly patch.
////////          else onload_local(sender,{date: Date.now(), status: httpd.status, response: httpd.response},false);
////////        } else send_message('parent',[['SUB_ACK',[sender,Date.now(),httpd.status,httpd.responseText]]]);
////////      }
////////      httpd.addEventListener('load',  httpd_events, false);
////////      httpd.addEventListener('error', httpd_events, false);
////////      httpd.addEventListener('abort', httpd_events, false);
////////      httpds[sender] = [httpd, httpd_events];
////////    }
////////    function destroy_httpd(sender){
////////      if (sender in httpds) {
////////        var httpd = httpds[sender][0];
////////        var httpd_events = httpds[sender][1];
////////        httpd.removeEventListener('load',  httpd_events, false);
////////        httpd.removeEventListener('error', httpd_events, false);
////////        httpd.removeEventListener('abort', httpd_events, false);
////////        delete httpds[sender];
//////////if (pref.test_mode['30']) console.log('destroyed: '+site.nickname+'/'+sender)
////////      }
////////    }
////    function make_iframe(domain,url){
//////      var ifrm = cnst.init('left:200px:bottom:200px:display:none:Show'); // working code.
////////      var ifrm = cnst.init('left:200px:bottom:200px:' + ((pref.debug_mode['0'])? '' : 'display:none:') + 'Show');
////////      var ifrm = cnst.init('left:200px:bottom:200px:Show');
//////      ifrm.innerHTML = '<iframe name=' + domain + '></iframe>';
////      cnst.make_iframe(domain);
//////      try {
////        iframes[domain] = window.open((site2[domain].home!=='')? site2[domain].home : url, domain);
////        send_message(domain,[['SUBFRAME_INIT']],iframes[domain]);
//////      } catch (e) {
//////        console.log('IFRAME OPEN ERROR'); // not debugged yet.
//////      }
//////      iframes[domain].onerror = function(){console.log('IFRAME OPEN ERROR');}; // can't catch
////    }
////    function onload_from_sw_cache_check(key,value,args) {
////      if (value!==null) {
//////        var date = value[0];
//////        var req_date = Date.now() - args[4]*1000;
//////        if (date>req_date) {onload_from_sw_cache(key,value,args);return;}
////        if (args[4]===true || value.date > Date.now() - args[4]*1000) {onload_text(args[0],value,true);return;} // date check.
////      }
////      get_req(args[0],args[1],args[2],args[3],false,args[5]);
////    }
////    function onload_text(sender,value,from_cache) {
////      if (reqs[sender]) { // for 8chan's 524 error(Origin Time-out)
////        if (reqs[sender].req.data_type==='html') {
////          value.response = parser.parseFromString(value.responseText, 'text/html');
//////          delete value.responseText;
////        } else if (reqs[sender].req.data_type==='json') {
////          value.response = (value.status==200)? JSON.parse(value.responseText) : value.responseText;
////        }
////        onload_local(sender,value,from_cache);
////      }
//////      value.response = null;
////    }
////    function onload_local(sender,value,from_cache) {
////      if (!reqs[sender]) return;
////      var req = reqs[sender].req;
//////      if (!from_cache && data_type==='html') {
//////        doc = parser.parseFromString(value.responseText, 'text/html');
//////        site2.common.remove_by_tagname(doc,'script');
//////        doc.getElementsByTagName('head')[0].innerHTML = '';
////////        value = {date:value.date, status:value.status, responseText:serializer.serializeToString(doc)};
//////      }
//////      if (!from_cache && cache_write) {
////////        caches[key] = [date, value.status, response_txt];
////////        setTimeout(function(){delete caches[key];},10000);
////////        if (pref.info_server && brwsr.sw_cache && value.status==200) brwsr.sw_cache.setItem(key,[date, response_txt]);
////////        if (pref.info_server && brwsr.sw_cache && value.status==200) brwsr.sw_cache.setItem(key,[date, value.status, response_txt]);
//////        if (pref.info_server && brwsr.sw_cache && value.status==200) {
//////          if (data_type==='html') value = {date:value.date, status:value.status, responseText:serializer.serializeToString(doc)};
//////          brwsr.sw_cache.setItem(key,value);
//////        }
//////      }
//////      if (data_type==='html') {value.response = doc; delete value.responseText;} // trial patch.
//////      else if (data_type==='json') {value.response = JSON.parse(value.responseText); delete value.responseText;}
//////      callback(key, value, callback_arg);
////
////      if (!from_cache && req.data_type==='html' && value.response) {
//////        site2.common.remove_by_tagname(value.response,'script');
//////        var dbt = comf.name2domainboardthread(key,true);
////        var dbt = req.key.split(',');
////if (pref.test_mode['28']) {
////        site2[dbt[0]].preprocess_doc(value.response);
////}
////if (pref.test_mode['50'] && site2[dbt[0]].preprocess_doc2 && site2[dbt[0]].preprocess_doc2[dbt[3]]) site2[dbt[0]].preprocess_doc2[dbt[3]](value.response);
////if (pref.test_mode['4']) {
////  site2[dbt[0]].remove_posts(value.response,pref.test_mode.num);
////  site2.common.remove_double_br(value.response);
////}
////        if (value.response.getElementsByTagName('head')[0]) value.response.getElementsByTagName('head')[0].innerHTML = '';
////      }
//////var check_perf = ['http_req :', performance.now()];
////      if (!from_cache && req.cache_write) {
////        if (pref.info_server && brwsr.sw_cache && value.status==200) {
////          if (req.data_type==='html') site2.common.remove_by_tagname(value.response,'script');
////          var value_sw_cache = (req.data_type==='html')? {date:value.date, status:value.status, responseText:serializer.serializeToString(value.response)}
////                                                       : {date:value.date, status:value.status, responseText:JSON.stringify(value.response)};
//////check_perf.push(performance.now());
////          brwsr.sw_cache.setItem(req.key,value_sw_cache);
//////check_perf.push(performance.now());
////        }
////      }
////// https://github.com/rtomayko/rack-cache/issues/111 Cache hit gives 200 instead of 304 when behind Nginx, Nginx + StringEtag + chrome.
////if (pref.test_mode['48'] && value.status===304) console.log('onload_local: 304, '+req.key);
////      req.callback(req.key, value, req.callback_arg);
//////if (sender==='catalog') comf.perf_out(check_perf);
////    }
////    function get_req(sender,domain,url,key,sw_cache,data_type, archived){
//////      if (caches[key]) setTimeout(function(){onload_local(sender,{date: caches[key][0], status: caches[key][1], responseText: caches[key][2]},true);},0); // this make racing condition at checking page in catalog.
////      if ((sw_cache===true || (typeof(sw_cache)==='number' && sw_cache!=0)) && brwsr.sw_cache)
////        brwsr.sw_cache.trygetItem(key,onload_from_sw_cache_check,[sender,domain,url,key,sw_cache,data_type]);
////      else {
////        if (domain==site.nickname || pref.catalog_cross_domain_connection=='direct') {
////          var httpd = get_make_Req(sender).httpd;
////          httpd.open('GET', url, true);
////          httpd.responseType = (archived)? 'text' : (data_type==='html')? 'document' : (data_type==='json')? 'json' : 'text';
////          httpd.send(null);
////        } else {
////          if (!iframes[domain]) make_iframe(domain,url);
////          send_message(domain,[['SUB_GET',[sender,domain,url,key,sw_cache,'text']]]);
////        }
////      }
////    }
////    return {
////      get: function (sender,key,url,callback,sw_cache,sw_cache_write,callback_arg, archived){
////        var dbt = (key.indexOf(',')==-1)? comf.name2dbt(key): key.split(',');
////        if (url==='') url = site2[dbt[0]].make_url4(dbt);
////        else if (typeof(url)==='string') url = [ url, 'raw'];
////        key = dbt.join(',');
////        get_make_Req(sender, {url:url[0], callback:callback, key:key, sw_cache:sw_cache, sw_cache_write:sw_cache_write, callback_arg:callback_arg, data_type:url[1]});
////        get_req(sender,dbt[0],url[0],key,sw_cache,url[1], archived);
////      },
////////////      get: function (sender,key,url,callback,sw_cache,sw_cache_write,callback_arg){ // working code.
////////////        var dbt = cnst.name2domainboardthread(key,true);
////////////        key = dbt[0]+dbt[1]+dbt[2];
////////////        if (url==='')
////////////          if (dbt[2][0]==='c' || dbt[2][0]==='p' || dbt[2][0]==='j') url = site2[dbt[0]].make_url(dbt[1],parseInt(dbt[2].substr(1),10),dbt[2][0]);
////////////          else url = [site2[dbt[0]].make_url3(dbt[1],dbt[2]), (dbt[2][0]!=='t')? 'html' : 'json'];
////////////        if (typeof(url)==='string') url = [ url, 'raw'];
////////////        req[sender] = {url:url[0], callback:callback, key:key, sw_cache:sw_cache, sw_cache_write:sw_cache_write, callback_arg:callback_arg, data_type:url[1]};
////////////        get_req(sender,dbt[0],url[0],key,sw_cache,url[1]);
////////////      },
////      close:   function(sender){
////        destroy_httpd(sender);
////        if (!pref.test_mode['30']) for (var domain in iframes) send_message(domain,[['SUB_DEST',[sender]]]);
////      },
////      sub_dest: function(args){destroy_httpd(args[0]);},
////      sub_get: function(arg){get_req(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5]);},
////      sub_ack: function(arg){
////        var value =  {date: arg[1], status: arg[2], responseText: arg[3]};
////        onload_text(arg[0], value, false);
////if (pref.test_mode['34']) send_message('meguca',[['ECHO',arg]]);
////      },
////      remote : function(){local=false;}
////    };
////  })();

//  var http_req = (function(){ // working code, but chokes when and err occur.
//    var reqs = [];
//    var caches = {}; // prevent occuring multiple access to the same url in short time.
//    var httpd = new XMLHttpRequest();
//    var httpd_events = function(){onload_local(httpd.status,httpd.responseText);}; // for local
//    httpd.addEventListener('load',  httpd_events, false);
//    httpd.addEventListener('error', httpd_events, false);
//    httpd.addEventListener('abort', httpd_events, false);
//    
//    function onload_local(status,response_txt) {
//      var req = reqs[0];
//
//      if (req[1]!=null) req[1](status, response_txt);
//        if (pref.info_server && brwsr.sw_cache)
//          brwsr.sw_cache.setItem(req[2]+req[3]+req[4],JSON.stringify([Date.now(), response_txt]));
//
//      prep_next();
//    }
//    function prep_next() {
//      reqs.shift();
//      if (reqs.length!=0) req_get(true); // javascript doesn't allow multiphe threads in a program, so this works well.
//    }
//    function req_get(force) {
//      if (!force && reqs.length>=2) return;
//      var url = reqs[0][0];
//      if (reqs[0][2]==site.nickname || pref.catalog_cross_domain_connection=='direct') {
//        httpd.open('GET', url, true);
//        httpd.send(null);
//      } else {
//        var domain = reqs[0][2];
//        if (!iframes[domain]) {
////          iframes[domain] = [window.open(url,domain), 0];
////          var ifrm = cnst.init('left:300px:bottom:300px:Show');
//          var ifrm = cnst.init('left:300px:bottom:300px:display:none:Show');
//          ifrm.innerHTML = '<iframe name=' + domain + '></iframe>';
//          iframes[domain] = [window.open(url,domain), 0];
//          send_message(domain,[['SUBFRAME_INIT']]);
//        }
//        send_message(domain,[['SUB_GET',reqs[0]]]);
//        iframes[domain][1]++;
//      }
//    }
//    return {
//      get: function(url,callback,domain,board,page,sw_cache,sender){
//        reqs.push([url,callback,domain,board,page,sw_cache,sender]);
//        req_get(false);
//      },
//      remote: function(){
//        httpd.removeEventListener('load',  httpd_events, false);
//        httpd.removeEventListener('error', httpd_events, false);
//        httpd.removeEventListener('abort', httpd_events, false);
//        httpd_events = function(){
//          send_message('parent',[['SUB_ACK',httpd.status, httpd.responseText]]);
//          prep_next();
//        };
//        httpd.addEventListener('load',  httpd_events, false);
//        httpd.addEventListener('error', httpd_events, false);
//        httpd.addEventListener('abort', httpd_events, false);
//      },
//      onload_local: onload_local
//    };
//  })();

  var notifier = (function(){
    var sound = (pref.features.notify.sound)? (function(){
      var audio = null
      function make_beep(){
        var wav_str = [];
        var dl = header_prep_monaural8(pref.notify.sound.beep_length_f);
        data_prep_monaural8(pref.notify.sound.beep_freq,dl,pref.notify.sound.beep_volume_f);
        for (var i=0;i<wav_str.length;i++) if (wav_str[i].length==1) wav_str[i] = '0'+wav_str[i];
        return new Audio('data:audio/wav,%'+wav_str.join('%'));

        function header_prep_monaural8(period) {
          var dl=Math.floor(period*44100);
          var header_size=46;
          header_add(0x52494646,4,1);     //riff
          header_add(dl+header_size,4,0); //size
          header_add(0x57415645,4,1);     //WAVE
          header_add(0x666d7420,4,1);     //fmt
          header_add(0x12,4,0);           //size of fmt
          header_add(0x01,2,0);           //linearPCM
          header_add(0x01,2,0);           //Stereo
          header_add(44100,4,0);          //44.1kHz
          header_add(44100,4,0);          //176400 bit/sec
          header_add(1,2,0);              //block size, 4Byte
          header_add(8,2,0);              //bit/sample
          header_add(0,2,0);              //size of ext
    //      header_add(0x66616374,4,1);     //fact
    //      header_add(4,4,0);              //size of fact
    //      header_add(0xdbcd5a00,4,1);     //data of fact
          header_add(0x64617461,4,1);     //data
          header_add(dl,4,0);             //size of data
          return dl;
  
          function header_add(val,num,big_endian) {
            if (big_endian==1) for (var i=0;i<num;i++) wav_str.push('');
            for (var i=0;i<num;i++) {
              if (big_endian==1) wav_str[wav_str.length-1-i] = (val%0x100).toString(16);
              else wav_str.push((val%0x100).toString(16));
              val = Math.floor(val/0x100);
            }
          }
        }
        function data_prep_monaural8(freq,period,vol){
          var pi2 = 3.141592654*2;
          vol *=127;
          for (var i=0;i<period;i++) {
            var val = Math.floor(Math.sin(pi2*freq*i/44100)*vol+0x80)%0x100;
            wav_str.push(val.toString(16));
          }
        }
      }
      function make_audio(){
        if (pref.notify.sound.src==='file') {
          var file = pref.notify.sound.file && pref.notify.sound.file.files[0];
          if (file) return new Audio(URL.createObjectURL(file));
        }
        return make_beep();
      }
      function src_changed(e,src){
        if (audio) audio.pause();
        src = src.slice(13); // remove 'notify.sound.'
        if (src==='pause') return;
        audio = null;
        if (pref.notify.sound.notify && (src==='notify' || src==='src' || src.indexOf(pref.notify.sound.src)===0)) play();
      }
      function play(){  // pref.notify.sound.notify is checked in caller
        if (!audio) audio = make_audio();
        audio.play();
      }
      pref_func.settings.onchange_funcs['notify.sound.*w/'] = src_changed;
      return {play: play};
    })() : {play:function(){}};

    var favicon = (pref.features.notify.favicon)? (function(){
      var favicon = site2[site.nickname].favicon.get_favicon();
      var title = site2[site.nickname].favicon.get_title();
      var title_org = title.innerHTML;
      var favicon_current = 'none';
      var title_current = '';
      function favicon_set(favicon_next){
        if (favicon_current!==favicon_next) {
          var str = site2[site.nickname].favicon[favicon_next];
          if (str[0]!=='/') str = 'data:image/'+str;
          favicon.setAttribute('href',str);
          favicon_current = favicon_next;
        }
      }
//      var func_debug = function(e){for (var i=0;i<e.length;i++) console.log(e[i].target.href);};
//      new MutationObserver(func_debug).observe(favicon, {attributes:true});
//      function favicon_set(str){ // working code.
//        if (!favicon) favicon = site2[site.nickname].favicon.get_favicon();
//        if (str[0]!=='/') str = 'data:image/'+str;
//        favicon.setAttribute('href',str);
//      }

      function set(){
        var sum = new liveTag.CountNR();
        if (pref.liveTag.watch_all) sum.count_all();
//        if (pref.liveTag.watch_all) {
//          for (var d in liveTag.mems) {
//            for (var b in liveTag.mems[d]) {
//              if (liveTag.mems[d][b].nr<0) {
//                var sum_sub = new liveTag.CountNR();
//                for (var t in liveTag.mems[d][b]) sum_sub.count(liveTag.mems[d][b][t].wt);
//                liveTag.mems[d][b].nrtm = sum_sub.nrtm;
//                liveTag.mems[d][b].nr   = sum_sub.nr;
//              }
////              sum.count([0x000c0000, (liveTag.mems[d][b].nrtm<<16) + liveTag.mems[d][b].nr]); // BUG, nr overflowed in 4chan.
//              sum.nrtm += liveTag.mems[d][b].nrtm;
//              sum.nr   += liveTag.mems[d][b].nr;
//        }}} else {
        else {
          var threads = cataLog.threads;
          if (threads) for (var name in threads) sum.count(threads[name][19]);
        }
        if (pref.notify.favicon) favicon_set((sum.nrtm!=0)? 'reply_to_me' : (sum.nr!=0)? 'reply' : 'none');
        if (pref.notify.title.notify)
          title_set((sum.nr==0 && pref.notify.title.hide_zero)? '' : (pref.catalog.footer.nrtm && sum.nrtm? sum.nrtm + '/' : '') + sum.nr + ' - ');
      }
      function title_set(str){
        if (title_current!==str) {
          title.innerHTML = str + title_org;
          title_current = str;
        }
      }
      return {
        set : new DelayBuffer(set, 200).get_bound_func(),
      } 
    })() : {set:function(){}};

    var desktop = (Notification && pref.features.notify.desktop)? (function(){
      function get_permission(){
        if (Notification.permission !== 'granted') {
          Notification.requestPermission(function (status) {
            if (Notification.permission !== status) {
              Notification.permission = status;
            }
          });
        }
        return Notification.permission==='granted';
      }
//      get_permission(); // this cause a redundant confirmation.

//      var dtns = {};
      var dtns = [];
      var dtns_queue = [];
      function show(th, post, tagKey, new_thread){
        if (pref.test_mode['94'] && site.nickname==='dist') return site2[site.nickname].testPoster(post, th.no);
        if (dtns.length + dtns_queue.length < pref.notify.desktop.limit)
          if (Notification.permission === 'granted' || get_permission()) show_1(th, post, tagKey, new_thread);
      }
//      function show(name,obj,num){ // working code.
//        if (dtns.length>pref.notify.desktop.limit) return;
//        if (Notification.permission === 'granted') show_1(name,obj,num);
//        else if (Notification && Notification.permission !== 'denied') {
//          Notification.requestPermission(function (status) {
//            if (Notification.permission !== status) {
//              Notification.permission = status;
//            }
//            if (status === 'granted') show_1(name,obj,num);
//          })
//        }
//      }
      var pn = document.createElement('div');
      function show_1(th, post, tagKey, new_thread){
        if (post.reply_of_mine && pref.catalog_footer_ignore_my_own_posts) return;
        var obj = {
          icon: post.op_img_url,
          body: post.com || '',
          title: (new_thread? 'New Thread ':'') + (post.reply_to_me && pref.notify.desktop.reply_to_me? '(You) in ' : '') + (th.sub || th.key),
          tag: pref.script_prefix+'/' + th.key + ', '+tagKey,
          __proto__: post};
////        if (obj.tag) obj.tag = 'CatChan/' + obj.tag;
//        var obj = (num>=0)? objs[num] : objs; // working code
//        obj = {icon: obj.op_img_url,
//               body: obj.com || '',
//               __proto__: obj};
//        if (obj.reply_of_mine && pref.catalog_footer_ignore_my_own_posts) return;
//        obj.title = ( // (obj.appear_thread)? 'Appear thread ' :
//                     (obj.new_thread)? 'New Thread ' :
//                     (obj.reply_to_me && pref.notify.desktop.reply_to_me)? '(You) in ' :
//                      '') + (th.sub || th.key);
////        if (obj.tag) obj.tag = 'CatChan/' + obj.tag;
//        obj.tag = pref.script_prefix+'/' + th.key + ', '+(objs.length-num-1);
        pn.innerHTML = obj.body;
//        var dtn = new Notification(obj.title,{tag:obj.tag, body:pn[brwsr.innerText], icon:obj.icon});
//        var dtn = new Notification(obj.title,{tag:obj.tag, body:pn[brwsr.innerText].trim(), icon:obj.icon}); // trim for KC
        obj.body = pn[brwsr.innerText].trim(); // trim for KC
        if (pref.notify.desktop.delay===0) show_1_queued(obj);
        else {
          dtns_queue[dtns_queue.length] = obj;
          show_1_queued_delayed(); // FF denies multiple notifications at a time.
        }
      }
      var show_1_queued_delayed = new DelayBuffer(show_1_queued, 200).get_bound_func();
      function show_1_queued(obj) {
        if (!obj) obj = dtns_queue.shift();
        var dtn = new Notification(obj.title, obj); // trim for KC
        dtn.onclick = win_focus;
//        dtn.onclose = close_1;
        var id = (pref.notify.desktop.lifetime!=0)? setTimeout(close_1, pref.notify.desktop.lifetime*1000) : null;
        dtns[dtns.length] = [dtn, id];
//console.log(dtns.length);
//        dtns[obj.tag] = dtn;
//        if (pref.notify.desktop.lifetime!=0) setTimeout(function(){dtn.close();delete dtns[obj.tag];},pref.notify.desktop.lifetime*1000); // can't call this in setTimeout without 'use strict'
        if (dtns_queue.length>0) show_1_queued_delayed();
      }
      function close_1(){dtns.shift()[0].close();}
      function win_focus(){window.focus();for (var i=dtns.length-1;i>=0;i--) if (dtns[i][0]===this || i==0) {if (dtns[i][1]) clearTimeout(dtns[i][1]);dtns.splice(i,1);break;}}
      function close_all(){while (dtns.length>0) {if (dtns[0][1]) clearTimeout(dtns[0][1]);close_1();}}
//      function win_focus(){window.focus();clearTimeout(dtns.shift()[1]);};
//      function close_all(){while (dtns.length>0) close_1();}
////      function win_focus(){window.focus()};
////      function close_all(){
////        for (var i in dtns) dtns[i].close();
////      }
      window.addEventListener('beforeunload', close_all, false);
      return {
        show : show,
      }
    })() : {show:function(){}};
    return {
      sound:   sound,
      favicon: favicon,
      desktop: desktop,
//      changed: function(th, posts_r, new_thread){ // posts_r contains posts by reverse order. // working code, but failed, because not watched thread can't be tracked by this.
//        if (pref.notify.favicon || pref.notify.title.notify) favicon.set();
//        var sound_flag = false;
//        var init = pref4.refresh.count<=1;
//        var pfs = pref.notify.sound;
//        var pfd = pref.notify.desktop;
//        var dt_start = ((pfd.reply || pfd.reply_to_me) && pfd.show_last)? 0 : posts_r.length-1;
//        var dt_end   = (pfd.reply || pfd.reply_to_me)? 0 : posts_r.length-1;
//        for (var i=posts_r.length-1;i>=0;i--) {
//          if (pfs.reply || (pfs.reply_to_me && posts_r[i].reply_to_me) || (pfs.new_thread && new_thread===1)) sound_flag = true; // 1 means new thread
//          if ((i<=dt_start && i>=dt_end) && pfd.notify)
//            if (pfd.reply || (pfd.reply_to_me && posts_r[i].reply_to_me) || (pfd.new_thread && new_thread===1))
//              if (!init || !pfd.supp_init) desktop.show(th,posts_r[i],posts_r.length-1-i, new_thread===1);
//        }
//        if (pfs.notify && sound_flag) if (!init || !pfs.supp_init) notifier.sound.play();
//      },
      changed: function(th, tgt_th19_4){
        if (pref.notify.favicon || pref.notify.title.notify) favicon.set();
//        if (!threads[name][19][5] && tgt_th19_4 && tgt_th19_4.length!=0) { // I think that tgt_th19_4 is redundant, but error occured, so patched.
          var sound_flag = false;
          var init = pref4.refresh.count<=1;
          var pfs = pref.notify.sound;
          var pfd = pref.notify.desktop;
          var dt = (pfd.show_last)? 0 : tgt_th19_4.length-1;
          for (var i=tgt_th19_4.length-1;i>=0;i--) {
            if (pfs.reply || (pfs.reply_to_me && tgt_th19_4[i].reply_to_me)) sound_flag = true;
            if (i<=dt && pfd.notify)
              if (pfd.reply || (pfd.reply_to_me && tgt_th19_4[i].reply_to_me)) if (!init || !pfd.supp_init) desktop.show(th,tgt_th19_4[i],i);
          }
          if (pfs.notify && sound_flag) if (!init || !pfs.supp_init) notifier.sound.play();
//        }
//console.log(tgt_th19_4.length);
      },
////      changed: function(name,threads){
////        if (pref.notify.favicon) favicon.set(threads);
////        var sound_flag = false;
//////threads[name][19][5] = false; // for debug.
////        if (!threads[name][19][5] && threads[name][19][4] && threads[name][19][4].length!=0) { // I think that threads[name][19][4] is redundant, but error occured, so patched.
////          if (pref.notify.desktop.notify) {
////            var i = (pref.notify.desktop.show_last)? 0 : threads[name][19][4].length-1;
////            while (i>=0) {
////              if (i==0 || !pref.notify.desktop.show_last)
////                if (pref.notify.desktop.reply || (pref.notify.desktop.reply_to_me && threads[name][19][4][i].to_me)) desktop.show(name,threads[name][19][4],i);
////              i--;
////            }
////          }
////          for (var i=0;i<threads[name][19][4].length;i++) if (pref.notify.sound.reply || (pref.notify.sound.reply_to_me && threads[name][19][4][i].to_me)) sound_flag = true;
////        }
////        if (pref.notify.sound.notify && sound_flag) notifier.sound.play();
//////console.log(threads[name][19][4].length);
////      },
////      appeared: function(names,threads){ // working code.
//////        if (pref.notify.sound.notify) notifier.sound.play();
////        if (pref.notify.favicon || pref.notify.title.notify) favicon.set(threads);
////        var sound_flag = false;
////        for (var i=0;i<names.length;i++) {
////          var name = names[i];
//////          if (threads[name][19][0]<0) { // 'changed' shall not be issued.
//////            if (pref.notify.desktop.new_thread && threads[name][19][8]) desktop.show(name,[{new_thread:true, body:threads[name][0].innerHTML}],0);
////            var dbt = comf.name2domainboardthread(name,true);
//////            if (pref.notify.desktop.notify && (pref.notify.desktop.appear || (pref.notify.desktop.new_thread && threads[name][19][8])))
////            if (pref.notify.desktop.notify &&   ((pref.notify.desktop.appear && !(threads[name][19][0]!==0 && (pref.notify.desktop.reply || pref.notify.desktop.reply_to_me)))
////                                              || (pref.notify.desktop.new_thread && threads[name][19][8])))
//////              desktop.show(name,[{new_thread:threads[name][19][8], body:threads[name][0].innerHTML, icon:site2[dbt[0]].get_op_image_url(threads[name][0],threads[name][18])}],0);
////              desktop.show(name,{new_thread:threads[name][19][8], body:threads[name][0].innerHTML, icon:threads[name][26]},-1);
////            if (pref.notify.sound.appear || (pref.notify.sound.new_thread && threads[name][19][8])) sound_flag = true;
////        }
////        if (pref.notify.sound.notify && sound_flag) notifier.sound.play();
////      }
      appeared: function(th, new_thread){
//        var threads = cataLog.threads;
//        if (pref.notify.favicon || pref.notify.title.notify) favicon.set(threads);
        var watch = liveTag.mems[th.domain][th.board][th.no].wt;
        if (pref.notify.desktop.notify && ((pref.test_mode['133'] && pref.notify.desktop.appear && !(watch[0]!==0 && (pref.notify.desktop.reply || pref.notify.desktop.reply_to_me)))
                                            || (pref.notify.desktop.new_thread && new_thread)))
          desktop.show(th,th.posts && th.posts[0] || th, 0, true);
//          desktop.show(th,{new_thread:new_thread, __proto__:th},-1);
        if (pref.notify.sound.notify && (pref.notify.sound.appear || (pref.notify.sound.new_thread && new_thread))) notifier.sound.play();
      }
    }
  })();

  var cnst = (function(){
    var TbInfoStore = new WeakMap();
    var tb_funcs = {
      INHERITED: 'tb_funcs',
      op_p: function(e){
        var s = e.currentTarget.parentNode.style;
        var op = parseFloat(s.opacity) || 1;
        s.opacity = (e.target.name==='op_p')? ((op+0.1>=1)? 1 : op+0.1)
                                            : ((op-0.1<=0.1)? 0.1: op-0.1);
      },
      roll_toggle: function(e, options){
        if (options.tb.classList.contains(pref.cpfx+'autoTp')) {
          options.cn.style.height = !parseInt(options.cn.style.height,10)? options.tb.offsetHeight + 'px' : null; // 'auto'; 'auto' doesn't work for youtube iframe
        } else {
          if (options.cn.style.display=='none') this.rolldown(options);
          else this.rollup(options);
        }
      },
      roll_if_auto: function(e, from_start, on_tb){
        var options = TbInfoStore.get(e.currentTarget);
        if (!options) return; // for popup
        if (from_start) {
          if (options.auto_roll && options.cn.style.display!=='none' && options.cn.offsetHeight>window.innerHeight) {
            if (!on_tb) {
              options.auto_unroll = [options.cn.parentNode.style.height];
              options.cn.parentNode.style.height = options.cn.parentNode.offsetHeight+'px';
            } else options.auto_unroll = true;
            this.rollup(options);
          }
        } else if (options.auto_unroll && options.cn.style.display==='none') {
          this.rolldown(options);
          if (Array.isArray(options.auto_unroll)) options.cn.parentNode.style.height = options.auto_unroll[0];
          options.auto_unroll = false;
        }
      },
      rolldown: function(tbInfo){
        tbInfo.tb.style.width = '';
        tbInfo.cn.style.display = ''; // for dollchan
        if (tbInfo.onrolldown) tbInfo.onrolldown.call(tbInfo.this_obj);
      },
      rollup: function(tbInfo){
        tbInfo.tb.style.width = tbInfo.tb.offsetWidth + 'px'; // for dollchan
        tbInfo.cn.style.display = 'none'; // for dollchan
        if (tbInfo.onrollup) tbInfo.onrollup.call(tbInfo.this_obj);
      },
      top: function(e, options){
        this.maximize_internal(options.pn, options.tb, options.cn, e.target.name, options, e.target);
      },
//    var state_arr = ['top','bottom','float','max']; // ['top','bottom','max','float']; // changed 2018.03.30, probably a bug.
      maximize_internal: function(pn,tb,cn, req, options, button_pressed){
        var state = options.maximize_state;
        var prev = state.now;
        var cns = (options.maximize_tgt || cn).style; 
// if (!pref.test_mode['130']) {
        var pns = pn.style;
        state.btn.style.display = null;
        if (req==='max') state.before_max = {pns:{width:pns.width, height:pns.height}, cns:{width:cns.width, height:cns.height}};
        if (state.now==='max') {
          comf.overwrite_prop(pns, state.before_max.pns);
          comf.overwrite_prop(cns, state.before_max.cns);
        } else if (state.now==='float') state.float = {left:pns.left, top:pns.top, right:pns.right, bottom:pns.bottom};
        if (req==='max') {
          var header_height = site.header_height();
          var w = document.documentElement.clientWidth;
          var h = document.documentElement.clientHeight - header_height;
          comf.overwrite_prop(pns, {left:'0px', top:header_height + 'px', position:'fixed', width:w+'px', height:h+'px', zIndex:pref.style.zIndex, right:null, bottom:null, float:null});
          comf.overwrite_prop(cns, {width: (w-6)+'px', height: (h-tb.offsetHeight-3)+'px'}); // 6,3 are for margin
        } else if (req==='float'){
          comf.overwrite_prop(pns, {position:'fixed', float:null, zIndex:pref.style.zIndex, __proto__:state.float});
        } else { // 'top' or 'bottom'
          var ref = site.embed_to[req](); // lazy evaluation
          if (ref){
            comf.overwrite_prop(pns, {position:'relative', float:'left', zIndex:null, top:null, left:null, right:null, bottom:null});
            ref.parentNode.insertBefore(pn,ref);
          }
        }
        state.now = req;
        state.btn = button_pressed;
//} else {
//        var now = state[0];
//  //      if (options.before_maximize) options.before_maximize(state_str, now);
//        state[5].style.display = null;
//        if (now!=='max') state = [req, pn.style.left, pn.style.top, pn.style.width, pn.style.height, button_pressed, pn.style.right, pn.style.bottom, cns.width, cns.height];
//        else {
//          comf.overwrite_prop(pn.style, {left:state[1], top:state[2], width:state[3], height:state[4], right:state[6], bottom:state[7]});
//          comf.overwrite_prop(cns, {width: state[8], height:state[9]});
//          state[0] = req;
//          state[5] = button_pressed;
//        }
//        if (req==='max') {
//          var header_height = site.header_height();
//          var w = document.documentElement.clientWidth;
//          var h = document.documentElement.clientHeight - header_height;
//          comf.overwrite_prop(pn.style, {left:'0px', top:header_height + 'px', position:'fixed', width:w+'px', height:h+'px', zIndex:pref.style.zIndex});
//          comf.overwrite_prop(cns, {width: (w-6)+'px', height: (h-tb.offsetHeight-3)+'px'}); // 6,3 are for margin
//        } else if (req==='float'){
//          comf.overwrite_prop(pn.style, {position:'fixed', zIndex:pref.style.zIndex});
//        } else { // 'top' or 'bottom'
//          var ref = site.embed_to[req](); // lazy evaluation
//          if (ref){
//            comf.overwrite_prop(pn.style, {position:'static', zIndex:null});
//            ref.parentNode.insertBefore(pn,ref);
//            tb.classList.remove(pref.cpfx+'autoTp');
//          }
//        }
//        options.maximize_state = state;
//}
        button_pressed.style.display = 'none';
        if (options.maximize) options.maximize.call(options.this_obj, req, prev);
      },
      exit: function(e, options){
        if (options.exit) options.exit.call(options.this_obj,e);
        else cnst.div_destroy(options.pn, true);
      },
      settings: function(e, options){
//        if (!pref.test_mode['102'] && !options) return; // patch for embed
        var btn = (e.target.tagName==='BUTTON')? e.target : e.target.parentNode;
        var tgt = this.this_obj && this.this_obj.components && this.this_obj.components[btn.getAttribute('name')] || options.cn.childNodes[0];
        tgt.style.display = (tgt.style.display==='none')? null : 'none';
        cnst.toggleButton(btn);
        if (this.settings_after) this.settings_after();
      },
      evfunc_factory: function(obj, funcName){
        return function(e){
          cnst.tb_funcs[funcName](e, TbInfoStore.get(obj.pn));
        };
      },
      autoTp: function(e){
        var tp = e.currentTarget.classList.toggle(pref.cpfx+'autoTp');
//        e.currentTarget.style.width = tp? '100%' : null;
        e.currentTarget.style.top = tp && e.shiftKey? 'auto' : null;
        e.currentTarget.style.bottom = tp && e.shiftKey? 0 : null;
        e.currentTarget.style.zIndex = tp? 1 : null;
      },
    };
    tb_funcs.op_m = tb_funcs.op_p;
    tb_funcs.bottom = tb_funcs.top;
    tb_funcs.float = tb_funcs.top;
    tb_funcs.max = tb_funcs.top;
    tb_funcs.filter = tb_funcs.settings;
    function tb_clicks(e){ // tb_funcs itself or obj which inherits tb_funcs are bound
      var et = e.target;
      var func = (e.type==='click')? et.tagName==='BUTTON' && this[et.name] || et.parentNode.tagName==='BUTTON' && this[et.parentNode.name]
                                   : (et.tagName==='DIV' || et.tagName==='SPAN') && this['roll_toggle']; // double click
      if (func) {
        e.stopPropagation();
        func.call(this, e, TbInfoStore.get(e.currentTarget.parentNode)); // TbInfoStore.get(e.currentTarget.parentNode) will be redundant.
      }
    }
//    function ev_to_close(e){
//      e.stopPropagation();
//      if (e.target.tagname!=='BUTTON') tb_funcs.exit.call(tb_funcs, e, TbInfoStore.get(e.currentTarget.parentNode));
//    }
//    function tb_dblclick(e){
//      if (e.target.tagName==='DIV' || e.target.tagName==='SPAN') tb_funcs['roll_toggle'](e);
//    }
    function tack_float(e){
      var obj = TbInfoStore.get(e.currentTarget);
      (obj && obj.float || cnst.tack_float_nSblgs)(e);
    }
    function tack_dock(e){
      var obj = TbInfoStore.get(TbInfoStore.get(e.currentTarget.parentNode).tack);
      (obj && obj.embed || cnst.tack_dock_nSblgs)(e);
    }

//    function flip_top(s){
//      return bottom2top(s,{offsetHeight:0});
//    }
    function bottom2top(s,pn){ // can be used as top2bottom also.
      return (document.compatMode==='BackCompat'? window.innerHeight : document.documentElement.clientHeight) - parseInt(s, 10) - pn.offsetHeight; // BackCompat for 5ch
    }
    function right2left(s,pn){
      return document.documentElement.clientWidth - parseInt(s, 10) - pn.offsetWidth;
    }
    function div_mousedown(e){
      if ((!pref.proto.popup2_resize && !gGEH.pns_resize.has(e.currentTarget)) || !div_resize_on_border(e))
        if (!(pref.proto.popup2_sel==='sel' || mouse_cursor_on_text(e)))
          /*if (!pref[cataLog.embed_mode].thumbnail.hover.dragfloat || !cataLog.DIH || !cataLog.DIH.hover_ex)*/ prep_drag(e, e.currentTarget);
    }
    var resize_tlbr = null;
    function div_resize_on_border(e){
      var ecT = e.currentTarget;
      var et = e.target;
      var s = ecT.style;
      var pos_fixed = s.position==='fixed';
      var bb_et = pos_fixed? null : et.getBoundingClientRect(); // returns questionable value if e.target is label, this can't apply to 'pos_fixed'
      var bb_ecT = pos_fixed? null : ecT.getBoundingClientRect();
      var top = pos_fixed? e.clientY - ((s.top)? parseInt(s.top, 10) : bottom2top(s.bottom,ecT)) : e.offsetY + bb_et.top - bb_ecT.top;
      var left = pos_fixed? e.clientX - ((s.left)? parseInt(s.left, 10) : right2left(s.right,ecT)) : e.offsetX + bb_et.left - bb_ecT.left;
//      var top = pos_fixed? e.clientY - ((s.top)? parseInt(s.top, 10) : bottom2top(s.bottom,ecT)) : e.offsetY + (ecT===et? 0 : et.offsetTop); //  - ecT.offsetTop);
//      var left = pos_fixed? e.clientX - ((s.left)? parseInt(s.left, 10) : right2left(s.right,ecT)) : e.offsetX + (ecT===et? 0 : et.offsetLeft); //  - ecT.offsetLeft);
      var bottom = ecT.offsetHeight - top;
      var right = ecT.offsetWidth - left;
      var tlbr = [top<bottom? top : null, left<right? left : null, bottom<=top? bottom : null, right<=left? right : null];
      for (var i=0;i<4;i++) if (tlbr[i]!==null && tlbr[i]<=pref.proto.popup2_resize_bw) {
        for (var j=0;j<4;j++) tlbr[j] = (i===j || tlbr[j]!==null && tlbr[j]<=pref.proto.popup2_resize_cw)? ecT[j%2?'offsetWidth':'offsetHeight'] : null;
        document.body.addEventListener('mouseup', div_mouseup_resize, false);
if (pref.test_mode['209']) {
        document.body.addEventListener('mousemove', div_mousemove_resize, false); // NOT STABLE, causes mouseout even if eventListener is added to body.
} else {
        document.body.addEventListener('dragend', div_mouseup_resize, false);
        document.body.addEventListener('drag', div_mousemove_resize, false);
        ecT.draggable = true;
}
        drag_sx = e.screenX;
        drag_sy = e.screenY;
        tlbr.push(parseInt(s.top,10), parseInt(s.left,10), parseInt(s.bottom,10), parseInt(s.right,10), ecT, s.width, s.height, ecT.offsetWidth, ecT.offsetHeight);
        resize_tlbr = tlbr;
        e.stopPropagation();
        ecT.style.cursor = (tlbr[0] && tlbr[1] || tlbr[2] && tlbr[3])? 'nwse-resize'
                         : (tlbr[0] && tlbr[3] || tlbr[2] && tlbr[1])? 'nesw-resize'
                         : (tlbr[0] || tlbr[2])? 'ns-resize': 'ew-resize';
        return true;
      }
      return false;
    }
    function div_mousemove_resize(e){
      if (e.screenX===0 && e.type==='drag') return; // BUG of chome??? Last event contains error value.
      if (e.type==='mousemove' && e.buttons===0) return; // for racing condition
      var tlbr = resize_tlbr;
      var pn = tlbr[8];
      var s = pn.style;
      var dx = e.screenX - drag_sx;
      var dy = e.screenY - drag_sy;
      if (tlbr[0]!==null) {s.height = tlbr[0] - dy + 'px'; if (s.top)    s.top    = tlbr[4] + dy + 'px';}
      if (tlbr[1]!==null) {s.width  = tlbr[1] - dx + 'px'; if (s.left)   s.left   = tlbr[5] + dx + 'px';}
      if (tlbr[2]!==null) {s.height = tlbr[2] + dy + 'px'; if (s.bottom) s.bottom = tlbr[6] - dy + 'px';}
      if (tlbr[3]!==null) {s.width  = tlbr[3] + dx + 'px'; if (s.right)  s.right  = tlbr[7] - dx + 'px';}
//      e.stopPropagation(); // redundant, because e.currentTarget===document.body
    }
    function div_mouseup_resize(e){
      var pn = resize_tlbr[8];
      var ecT = e.currentTarget; // ecT===document.body
if (!pref.test_mode['209']) {
      pn.draggable = false;
      ecT.removeEventListener('drag', div_mousemove_resize, false);
      ecT.removeEventListener('dragend', div_mouseup_resize, false);
} else {
      ecT.removeEventListener('mousemove', div_mousemove_resize, false);
}
      pn.style.cursor = null;
      ecT.removeEventListener('mouseup', div_mouseup_resize, false);
//      e.stopPropagation(); // redundant, because e.currentTarget===document.body
      var resize_func = gGEH.pns_resize.get(pn) || null;
      if (resize_func) {
        var cn = pn.childNodes[1];
        cn.style.width = (cn.offsetWidth + parseInt(pn.style.width,10) - resize_tlbr[11]) + 'px';
        cn.style.height = (cn.offsetHeight + parseInt(pn.style.height,10) - resize_tlbr[12]) + 'px';
//        cn.style.width = (parseInt(pn.style.width,10)-6) + 'px'; // 6 for margin
//        cn.style.height = (parseInt(pn.style.height,10)-pn.childNodes[0].offsetHeight-3) + 'px'; // 3 for margin
        if (!resize_tlbr[9]) pn.style.width = null;
        if (!resize_tlbr[10]) pn.style.height = null;
        resize_func();
      }
//      pn.style.cursor = null;
      resize_tlbr = null;
    }
    function tb_mousedown(e){
      var pn = e.currentTarget.parentNode;
      if (prep_drag(e, pn)!==false) tb_funcs.roll_if_auto({currentTarget:pn}, true, true);
    }
    function prep_drag(e, pn){
      e.stopPropagation();
      if (pn.style.position!=='fixed') return false;
      pn.draggable = true;
      pn.addEventListener('dragstart', div_dragstart_dynamic, false);
      pn.addEventListener('mouseup', div_mouseup, false);
    }
    function div_mouseup(e){
      e.currentTarget.removeEventListener('dragstart', div_dragstart_dynamic, false);
      e.currentTarget.removeEventListener('mouseup', div_mouseup, false);
      e.currentTarget.draggable = false;
      tb_funcs.roll_if_auto(e, false);
    }
    function mouse_cursor_on_text(e){
      if (e.target.tagName==='INPUT' || e.target.tagName==='TEXTAREA' || e.target.tagName==='SELECT') return true;
      if (pref.proto.popup2_sel==='move') return false;
      if (e.currentTarget===e.target) return false;
      var et_cn = e.target.childNodes;
//      if (et_cn.length===1) return et_cn[0].nodeType===3; // textNode
      var range = new Range();
      for (var i=0;i<et_cn.length;i++) {
        var tx = (et_cn[i].tagName==='BUTTON' || et_cn[i].tagName==='SELECT')? 0 : pref.proto.popup2_sel_tolerance;
        range.selectNode(et_cn[i]);
        var rects = range.getClientRects();
        for (var j=0;j<rects.length;j++) {
          var rect = rects[j];
          if (rect.bottom>e.clientY && e.clientY>=rect.top)
            if (rect.left-tx < e.clientX && e.clientX < rect.right+tx) return true;
        }
      }
      return false;
    }
    var drag_sx;
    var drag_sy;
//    var drag_opacity_back;
//    var dragStopProp = new WeakSet();
//    var drag_cursor_style;
    var root_body = document.body;
    function div_dragstart_dynamic(e){ // with tb or popup
      tb_funcs.roll_if_auto(e, true);
      div_dragstart(e, true)
    }
    function div_dragstart(e, dynamic){ // static
//      var cT = e.currentTarget;
////      if (this.pn) this.pn.removeEventListener('dragstart', this.event_funcs.dragstart, false);
//      drag_sx = e.screenX;
//      drag_sy = e.screenY;
////      drag_cursor_style = pn.style.cursor;
////      pn.style.cursor = 'move';
      e.dataTransfer.setData('text/plain', ''); // for FF. CH doesn't require this.
//      e.dataTransfer.effectAllowed = 'move';
//      e.dataTransfer.dropEffect = 'move';
////      e.preventDefault();
//      if (dragStopProp.has(cT)) e.stopPropagation(); // prevent from invoking twice when legend on chart is moved.
//      drag_opacity_back = cT.style.opacity;
//      cT.style.opacity = 0.4;
////      div_dragend_caller = this;
      gGEH.drag.started(e, (dynamic)? div_mouseup : null, 'move'); // mouseup isn't called after dragend
//      gGEH.drag.started(e, (dynamic)? div_dragend_dynamic : null, 'move', true);
//      cT.addEventListener('dragend', (dynamic)? div_dragend_dynamic : div_dragend, false);
//      root_body.addEventListener('dragover',div_dragover,false);
    }
////    function div_dragover(e){ // http://www.html5rocks.com/ja/tutorials/dnd/basics/
////      e.preventDefault();
////      e.dataTransfer.dropEffect = 'move';
////    }
//////    var div_dragend_caller;
//    function div_dragend_dynamic(e){
////      div_dragend(e, true);
//      tb_funcs.roll_if_auto(e, false);
//      div_mouseup(e); // mouseup isn't called after dragend
//    }
////    function div_dragend(e, dynamic){
////      root_body.removeEventListener('dragover',div_dragover,false);
////      var cT = e.currentTarget;
////      var s = cT.style;
////      cT.removeEventListener('dragend', (dynamic)? div_dragend_dynamic : div_dragend, false);
////      if (s.left) s.left  = (parseInt(s.left ,10) + e.screenX - drag_sx) + 'px';
////      else        s.right = (parseInt(s.right,10) - e.screenX + drag_sx) + 'px';
////      if (s.bottom) s.bottom = (parseInt(s.bottom,10) - e.screenY + drag_sy) + 'px';
////      else          s.top =    (parseInt(s.top   ,10) + e.screenY - drag_sy) + 'px';
//////      if (div_dragend_caller.dragend) div_dragend_caller.dragend.call(div_dragend_caller.this_obj,e); // isn't used anymore.
//////      div_dragend_caller = null;
////      s.opacity = drag_opacity_back;
////      if (dragStopProp.has(cT)) e.stopPropagation();
////    }
    function div_scroll(e){
      var val = (!brwsr.ff)? e.wheelDelta : -e.detail*40;
      var s = e.currentTarget.style;
      if (s.bottom) s.bottom = (parseInt(s.bottom,10) - val) + 'px'; // from bottom.
      else s.top = (parseInt(s.top,10) + val) + 'px';
      e.preventDefault();
    }
    var tile = {
      left   : 0,
      bottom : 0,
      last_pn: null, // CAUTION. grep last pn permanently.
    };
    var pn_tmp = document.createElement('div');
    return {
      init3: function(obj){
        return this.init(obj.func_str,null,null,null,null, obj);
      },
//      init2: function(func_str, site_settings){
//        if (!site_settings) return this.init(func_str);
//        var pn = cnst.dom('<span class="'+pref.cpfx+'button">'+func_str.replace(/.*:button:/,'').replace(/:.*/,'')+'</span>');
//        site_settings.insertBefore(pn, site_settings.lastChild);
//        site_settings.insertBefore(document.createTextNode(' '), site_settings.lastChild);
//        return pn;
//      },
      init: function(func_str,rolldown_func,rollup_func,exit_func,maximize_func, optsobj_in){
        var optsobj = optsobj_in || {
//          rolldown: rolldown_func, // changed to onXXX
//          rollup: rollup_func,
          exit: exit_func,
          maximize: maximize_func,
        };
        var pn = optsobj.pn || document.createElement('span');
        optsobj.pn = pn; // must be prior to 'tb_press'
//        var pn = document.createElement('div');
        if (!pn.style) pn.style = {};
        pn.style.position = 'fixed';
        pn.draggable = true;
        pn.style.padding = '0px';
        pn.style.zIndex = pref.style.zIndex;
        pn.style.float = 'left'; // for embed_to_top/bottom
        pn.style.display = 'inline-block';
        var funcs = func_str.split(':');
        var i=0;
        if (funcs[0]!='pop') {
//          pn.style.background = '#e5ecf9';
//          pn.style.color = '#000000';
//          pn.style.fontWeight = 'normal';
//          pn.style.border = '1px solid blue';
//          pn.style.border = 'none';
        } else {
          var pop = true;
          pn.addEventListener(brwsr.mousewheel, div_scroll, false);
          pn.name = 'catalog_pop';
          i=1;
          pn.draggable = false;
        }
        pn.classList.add(pref.cpfx+'window');
//        var rollup_func_tb = null;
        var tgt = pn;
        while (i<funcs.length) {
          var arg = funcs[i++];
          if (arg==='etb' || arg==='tb' || arg==='ftb' || arg==='fitb') {
            pn.classList.remove(pref.cpfx+'window');
            pn.classList.add(pref.cpfx+'titleBar');
            if (arg!=='fitb') pn.innerHTML = '<div class="' + pref.cpfx+'window" style="margin:0px 3px 3px"></div>';
            var cn = pn.firstChild;
            var btns_html = pref_func.format_html_str('<div style="'+(arg==='etb'?'':'width:100%;')+'overflow:hidden">' + // height:auto;overflow:hidden">' +
                '<div style="float: left">'+(arg==='etb'?'': (pref.test_mode['204']?'<BTN"autoTp,/">':'')+'<BTN"roll_toggle,-"><BTN"op_m,&lt;"><BTN"op_p,&gt;"><span style="width:16px;display:inline-block"></span>')+'</div>' +
                '<div style="float: right">'+(arg==='etb'?'':'<BTN"top,^"><BTN"bottom,v"><BTN"float,o" style="display:none"><BTN"max,[]">'+(arg==='fitb'? '<BTN"exit,x">':'')+'<BTN"exit,X">')+'</div>' +
                '<span></span>'+ // this will contain healthIndicators.
//                '<div style="clear:both"></div>'+
              '</div>');
            var tb = pn.insertBefore(cnst.dom(btns_html), arg==='tb' || arg==='etb'? cn : cn.nextSibling); // must be latter for 'ftb', otherwise titleBar doesn't appear at the top if 'cn' has <video> tag on chrome51
            tgt = cn;
//            tb.childNodes[2].style.height = tb.childNodes[0].offsetHeight + 'px';
//            tb.childNodes[0].style.cursor = 'move';
//            tb.childNodes[2].style.cursor = 'move';
//            rollup_func_tb = function(){rollup(pn,pn.childNodes[1], optsobj);};
//            tb.childNodes[0].childNodes[0].onclick = rollup_func_tb;
//            tb.childNodes[0].childNodes[1].onclick = function(){opacity(pn, false);};
//            tb.childNodes[0].childNodes[2].onclick = function(){opacity(pn, true);};
//            tb.childNodes[0].childNodes[3].ondblclick = rollup_func_tb;
//            tb.childNodes[2].ondblclick = rollup_func_tb;
            optsobj.maximize_state = {now:'float', btn:tb.childNodes[1].childNodes[2]};
//            optsobj.maximize_state = (!pref.test_mode['130'])? {now:'float', btn:tb.childNodes[1].childNodes[2]} : ['float',null,null,null,null,tb.childNodes[1].childNodes[2]];
//            optsobj.maximize_state_str = 'float';
//            tb.childNodes[1].childNodes[0].onclick = function(){maximize(pn,tb,0, optsobj);}; // can reduce footprint if 'prototype' is used.
//            tb.childNodes[1].childNodes[1].onclick = function(){maximize(pn,tb,1, optsobj);};
//            tb.childNodes[1].childNodes[2].onclick = function(){maximize(pn,tb,2, optsobj);};
//            tb.childNodes[1].childNodes[3].onclick = function(){maximize(pn,tb,3, optsobj);};
//            tb.childNodes[1].childNodes[4].onclick = (optsobj.this_obj)? optsobj.exit.bind(optsobj.this_obj) : optsobj.exit;
//            if (brwsr.ff) {
            pn.draggable = false;
//            tb.childNodes[0].draggable = true;
//            tb.childNodes[2].draggable = true;
//            }
            var onclick_entry = tb_clicks.bind(optsobj.INHERITED==='tb_funcs'? optsobj : tb_funcs);
            tb.onclick = onclick_entry;
            if (arg!=='etb') tb.ondblclick = onclick_entry;
////            tb.ondblclick = tb_dblclick;
            tb.onmousedown = tb_mousedown;
            optsobj.tb = tb;
            optsobj.cn = cn;
            TbInfoStore.set(pn,optsobj);
            if (arg=='ftb' || arg=='fitb') {
//            tb.childNodes[0].removeChild(tb.childNodes[0].childNodes[0]);
//            pn.insertBefore(cn,tb);
//              tb.style.width = '100%';
              tb.classList.add(pref.cpfx+'autoTp');
              cn.style.margin = '3px';
            }
//            if (arg==='ftb') {
//              var pf = pref[cataLog.embed_mode].thumbnail.hover;
//              if (pf.df_dblC) cn.ondblclick = ev_to_close;
////            tb.ondblclick = ev_to_close;
//              if (pf.df_mW) pn['on'+brwsr.mousewheel] = div_scroll;
//            }
          } else if (arg=='tb_press') { // must be used at initial time because of using onclick_entry
            var btn = funcs[i++];
            if (btn!=='float') onclick_entry({target:tb.getElementsByTagName('button')[btn], currentTarget:tb, type:'click', stopPropagation:function(){}});
          } else if (arg=='embed') {
            tb.onmousedown = null;
          } else if (arg=='bottom_top') {
            this.bottom_top(pn);
          } else if (arg=='txt' || (brwsr.ff && arg=='button')) {
            tgt.appendChild(document.createTextNode(funcs[i++]));
            tgt.style.cursor = 'pointer';
            tgt.style.padding = '2px 5px 2px 5px';
          } else if (arg=='button') {
            tgt.innerHTML = '<input type="button" value="' + funcs[i++] + '">';
//            tgt.innerHTML = '<button draggable="true">' + funcs[++] + '</button>';
            tgt.style.padding = '0';
            tgt.style.border = '0';
            tgt.style.background = 'none';
          } else if (arg=='Show2') {
            if (site.root_body!==site.root_body2 && site.root_body2) {
              pn.style.position = 'static';
              site.root_body2.appendChild(pn);
            } else site.root_body.appendChild(pn);
          } else if (arg=='Show') site.script_body.appendChild(pn);
//          } else if (arg=='Show') site.root_body.appendChild(pn);
          else if (arg=='tile' || arg=='tile2') {
            if (funcs[i++]=='set') {
              if (funcs[i]=='left') {tile[funcs[i]] = parseInt(pn.style[funcs[i]].replace(/px/,''),10) + pn.offsetWidth;i++;}
              else {tile[funcs[i]] = parseInt(pn.style[funcs[i]].replace(/px/,''),10) + pn.offsetHeight;i++;}
            } else if (arg==='tile') {pn.style[funcs[i]] = tile[funcs[i]]+'px';i++;} // tile:get
            else {pn.style[funcs[i]] = tile[funcs[i]]+parseInt(funcs[i+1],10)+'px';i+=2;} // tile2:get
            
//            console.log(tile);
//          } else if (arg=='dragStopProp') dragStopProp.add(tgt);
          } else tgt.style[arg] = funcs[i++];
        }
//        optsobj.event_funcs = {};
        if (tb || pop) pn.addEventListener('mousedown', div_mousedown, false);
        else pn.addEventListener('dragstart', div_dragstart, false);
//        optsobj.event_funcs.dragstart = div_dragstart.bind(optsobj);
//        pn.addEventListener('dragstart', optsobj.event_funcs.dragstart, false);
        tile.last_pn = pn;
//        optsobj.rollup_func_tb = rollup_func_tb;
        return optsobj_in? optsobj : pn;
//        return (rollup_func_tb)? [pn,rollup_func_tb] : pn;
      },
      tile_set_bottom: function(){if (tile.last_pn) tile.bottom = tile.last_pn.offsetHeight;},
//      tile_set_left: function(){if (tile.last_pn) tile.left = tile.last_pn.offsetWidth;},
//      top2bottom: bottom2top,
      left2right: right2left,
//      flip_top: flip_top,
      bottom_top: function(pn){
        var s = pn.style;
        if (s.bottom) {
          s.top = bottom2top(s.bottom,pn) + 'px';
          s.bottom = null;
        }
        if (s.right) {
          s.left = right2left(s.right,pn) + 'px';
          s.right = null;
        }
      },
      void_func: function(){},
      div_mousedown: div_mousedown,
      div_dragstart: div_dragstart,
      div_scroll: div_scroll,
      div_destroy: function(pn,child_of_body, options){
//        pref_func.tooltips.remove_hier(pn);
//        pref_func.tooltips.remove_root(pn);
        if (child_of_body) pn.parentNode.removeChild(pn);
        if (pn.name || TbInfoStore.has(pn)) pn.removeEventListener('mousedown', div_mousedown, false);
        else pn.removeEventListener('dragstart', div_dragstart, false);
//        pn.removeEventListener('dragstart', (options)? options.event_funcs.dragstart : div_dragstart, false);
        if (pn.name) pn.removeEventListener(brwsr.mousewheel, div_scroll, false);
        return null;
      },
//      tb_prep_for_embed: function(tb){
//        tb.childNodes[0].innerHTML = '';
//        tb.childNodes[1].innerHTML = '';
////        tb.childNodes[0].draggable = false;
////        tb.childNodes[3].ondblclick = null;
////        tb.childNodes[3].draggable = false;
//        tb.childNodes[3].removeAttribute('style');
//        tb.ondblclick = null;
////        tb.onmousedown = null;
////        tb.childNodes[2].onmousedown = null;
//      },
      add_to_tb: function(pn,str){
        var pn_2 = document.createElement('div');
        pn_2.style.float = 'right'; // doesn't work on FF
        pn_2.style.resize = 'none';
        pn_2.style.overflow = 'visible';
        pn_2.innerHTML = str;
        pn.childNodes[0].insertBefore(pn_2,pn.childNodes[0].childNodes[pn.childNodes[0].childNodes.length-1]);
//        if (brwsr.ff) pn.childNodes[0].childNodes[2].setAttribute('style','float: right');
        if (brwsr.ff) pn_2.setAttribute('style','float: right');
        return pn_2;
      },
      show_hide: function(pn,pn2, pn3){
        if (pn.style.display=='none') {
          pn.style.display='';
          if (pn2) pn2.style.height = parseInt(pn2.style.height.replace(/px/,''),10) - pn.offsetHeight + 'px'; // May not work? It depends on the timing of invoking rendering engine. Chrome is ok.
          if (pn3) pn3.style.top    = parseInt(pn3.style.top.replace(/px/,''),10)    + pn.offsetHeight + 'px';
        } else {
          if (pn2) pn2.style.height = parseInt(pn2.style.height.replace(/px/,''),10) + pn.offsetHeight + 'px';
          if (pn3) pn3.style.top    = parseInt(pn3.style.top.replace(/px/,''),10)    - pn.offsetHeight + 'px';
          pn.style.display='none';
        }
      },
//      rollup: function(tb,pn){rollup(tb,pn,cnst.void_func,cnst.void_func);},
      name2domainboardthread: comf.name2domainboardthread,
//      name2domainboardthread: function(name,fill){
//        var thread = name.replace(/[^\/]*\//g,'');
//        var domain = name.replace(/\/.*/,'');
//        var board  = name.replace(new RegExp('^'+domain),'').replace(new RegExp(thread+'$'),'');
//        if (thread==domain)
//          if (thread.search(/[^0-9]/)!=-1) thread ='';
//          else domain = '';
//        if (fill) {
//          if (domain==='') domain = site.nickname;
//          if (board==='') board = site.board;
//        }
//        return [domain,board,thread];
//      },
      get_time: function(){
        var now = new Date();
        var hour = now.getHours();
        var min = now.getMinutes();
        var sec = now.getSeconds();
        if(hour<10) hour = '0' + hour;
        if(min<10)  min  = '0' + min;
        if(sec<10)  sec  = '0' + sec;
        return hour + ':' + min + ':' + sec;
      },
      make_destroy: function(parent,key,func_make,func_destroy){
        if (!parent[key]) parent[key] = func_make();
        else parent[key] = func_destroy();
      },
      make_popup: function(parent,tgt,html,onchange_func){
        parent[tgt] = cnst.init('left:0px:tile:get:bottom:Show:tb',cnst.void_func,cnst.void_func, function(){
            parent[tgt] = cnst.div_destroy(parent[tgt], true);
          },cnst.void_func);
        parent[tgt].childNodes[1].innerHTML = html;
        pref_func.add_onchange(parent[tgt].childNodes[1],onchange_func);
      },
      make_iframe: function(name){
        if (site2[name] && site2[name].X_FRAME_OPTIONS && pref.network.overXFO || site2[site.nickname].CONTENT_SECURITY_POLICY_FRAME && pref.network.overCSPF) {
//           setTimeout(function(){window.focus();},2000); // this doesn't effect.
          return;
        }
        var ifrm = document.createElement('iframe');
        ifrm.name = name;
        ifrm.setAttribute('style','display:none');
        site.script_body.appendChild(ifrm);
        return ifrm;
      },


      subscribe: function(container, tgt){ // for [] container.
        container.push(tgt);
      },
      unsubscribe: function(container, tgt){
        var idx = container.indexOf(tgt);
        if (idx!=-1) container.splice(idx,1);
      },
      foreach: function(container, func){
        for (var i=0;i<container.length;i++) func(container[i]);
      },
      auto_shrink_board_selector: (function(){
        var sels = [];
        function focus(){
          this.removeAttribute('style');
          for (var j=0;j<this.length;j++) {
            this.options[j].text = pref.catalog_board_list_obj[j][0].key;
            color(this.options[j],j);
          }
        }
        function blur(){
          for (var j=0;j<this.length;j++)
            if (j!=this.selectedIndex) this.options[j].text = '';
          color(this,this.selectedIndex);
        }
        function prep(sel, idx){
          if (idx===undefined) idx = sel.selectedIndex;
          sel.length=0;
          for (var j=0;j<pref.catalog_board_list_obj.length;j++) {
            sel.length++; // this is required to add
            sel.options[sel.length-1].text = pref.catalog_board_list_obj[j][0].key;
          }
          sel.selectedIndex = (idx>=sel.length)? 0 : idx;
          sel.onblur();
          return sel.selectedIndex;
        }
        function color(pn,idx){
          if (pref3.stats.use && (pref.stats.auto_acquisition || pref.stats.auto_acquisition_all) && stats.query_auto_acquisition(idx)) pn.setAttribute('style','background:#b5fbda');
          else pn.removeAttribute('style');
        }
        var obj_old = null;
        function search_and_copy_ts(obj, dic){
          for (var i=0;i<obj.length;i++)
            if (i===0 || obj_old[i] && JSON.stringify(obj_old[i])===JSON.stringify(obj[i])) copy_ts(dic, obj, i, obj_old, i);
            else break;
          var j = obj_old.length-1;
          for (var i=obj.length-1;i>=0;i--)
            if (!(j in dic) && obj_old[j] && JSON.stringify(obj_old[j])===JSON.stringify(obj[i])) copy_ts(dic, obj, i, obj_old, j--);
            else break;
          return dic;
        }
        function search_my_entry(sel, obj, dic){
          var idx = sel.selectedIndex;
          if (idx in dic) return dic[idx];
          var s_before = JSON.stringify(obj_old[idx]);
          for (var i=0;i<obj.length;i++) if (s_before===JSON.stringify(obj[i])) return copy_ts(dic, obj, i, obj_old, idx);
          return idx;
        }
        function copy_ts(dic, obj, i, obl_old, idx){
          obj[i].ts = obj_old[idx].ts;
          return dic[idx] = i;
        }
        return {
          setup: function(sel, idx, callback){
            obj_old = pref.catalog_board_list_obj;
            cnst.subscribe(sels,{sel, callback});
            sel.onfocus = focus;
            sel.onblur = blur;
            return prep(sel, idx);
          },
          str_changed: function(){
            var obj = pref.catalog_board_list_obj;
            var dic = search_and_copy_ts(obj, {});
            cnst.foreach(sels,function(s){s.callback(prep(s.sel, search_my_entry(s.sel, obj, dic)));});
            obj_old = obj;
//            cnst.foreach(sels,prep);
          },
          destroy: function(sel){
            for (var i=sels.length-1;i>=0;i--) if (sels[i].sel===sel) {sels.splice(i,1); return;}
//            cnst.unsubscribe(sels,sel);
          },
          color: function(){
            cnst.foreach(sels,function(s){color(s.sel,s.sel.selectedIndex);});
//            cnst.foreach(sels,function(inst){color(inst,inst.selectedIndex);});
          },
        };
      })(),
      tb_funcs: tb_funcs,
      auto_shrink_selector: (function(){
        var options = Object.create(null);
        function focus(){
          var opts = options[this.getAttribute('name')];
          for (var j=0;j<this.length;j++) if (this.options[j].text==='') this.options[j].text = opts[j];
        }
        function blur(){
          var opts = [];
          for (var j=0;j<this.length;j++) {
            opts[j] = this.options[j].text;
            if (j!=this.selectedIndex) this.options[j].text = '';
          }
          options[this.getAttribute('name')] = opts;
        }
        return function(sel){
          sel.onfocus = focus;
          sel.onblur = blur;
          sel.onblur();
        };
      })(),
      config_expander: (function(){
        function make_opts(){
          var num = pref[cataLog.embed_mode].t2h_num_of_posts;
          return ['Same as the page', pref[cataLog.embed_mode].t2h_L + ' posts', pref[cataLog.embed_mode].t2h_M + ' posts',
                  num + ' posts', num + ' + all unread posts', 'All unread posts', 'All'];
        }
        function focus(){
          var opts = make_opts();
          for (var j=0;j<this.length;j++) if (this.options[j].text==='') this.options[j].text = opts[j];
        }
        function blur(){
          for (var j=0;j<this.length;j++) if (j!=this.selectedIndex) this.options[j].text = '';
        }
        return function(key, idx){
          var sel = document.createElement('select');
          sel.className = pref.cpfx+'expander';
          sel.setAttribute('data-key',key);
          sel.length = 7;
          if (idx===undefined) idx = 0;
          sel.selectedIndex = idx;
          sel.options[idx].text = (idx===0)? 'Expand...' : make_opts()[idx];
          sel.onfocus = focus;
          sel.onblur = blur;
//          sel.onblur();
          return sel;
        };
      })(),
      icons: {
        filter: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcAQAAAABaduI5AAAAAnRSTlMAAQGU/a4AAABUSURBVAjXY/j///8HBjAx////CQz8//8zMLD//8fAwPz/DwND4/8fAgwH/n8wYHhQ/6CA4YP9gQ8MP+QbPzD84Wf+wPCPnx2ol5+fLKJfHmovnAAAn79HiYOE2q8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTAtMjlUMDI6MTM6MzkrMDk6MDBxKtFiAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE1LTEwLTI5VDAyOjEzOjM5KzA5OjAwAHdp3gAAAABJRU5ErkJggg==',
        settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcAQAAAABaduI5AAAAAnRSTlMAAQGU/a4AAABWSURBVAjXdY6xDcAgDAQvGzAKG2QllykiwWgexWxgKQ2dI4yULs1X9/dPWHMyHrudYZegvVQ6FBQqdqrgzRwLF0ZM4YjJfySS8K4tQaq2dOlzKCe/By9fz0MDWLKIZgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMC0yOVQwMjowMzowNiswOTowMMhjeagAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTUtMTAtMjlUMDI6MDM6MDYrMDk6MDC5PsEUAAAAAElFTkSuQmCC',
        refresh: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcAQAAAABaduI5AAAAAnRSTlMAAQGU/a4AAABmSURBVAjXbY6xDYAwDASfijLegBUYLRuAlCIljITEAowQNnCZIspjQ0tzkvX6e4NKBREudIw7KgaBTqugLEVwUwWnI9HOzMNh6UarhR+8QWaXD4nNBW02VY0ujdAA9SH1yXfcYG88J0BH+yT9vZAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTAtMjhUMjM6NDg6MDYrMDk6MDASyn1kAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE1LTEwLTI4VDIzOjQ4OjA2KzA5OjAwY5fF2AAAAABJRU5ErkJggg==',
        img: function(src){return '<img src="' + src + '" style="width:14px;height:14px">';},
        button_settings: function(){return '<button type="button" name="settings">'+this.img(this.settings)+'</button>';},
      },
      toggleButton: function(pn){
        if (pn.style && pn.style.border) pn.style.border = '';
        else pn.style.border = 'inset 2px';
      },
//      toggleButton: (function(){
//        var style_str = 'width:14px;height:14px';
//        return function(name,icon){
//          var button = document.createElement('button');
//          button.src = cnst.icons[icon];
//          button.setAttribute('style',style_str);
//          return button;
//        };
//      })(),
      createElement: function(html){
        var pn = document.createElement('div');
        pn.innerHTML = html;
        return (pn.childNodes.length===1)? pn.childNodes[0] : pn.querySelectorAll('*'); // return static array.
      },
      set_tack_float2: function(pn, func_float, func_embed){
        var tack = site2[site.nickname].make_tack();
        tack.setAttribute('style','float:right;font-size:2em');
        pn.parentNode.insertBefore(tack,pn);
        return this.set_tack_float(tack, func_float, func_embed);
      },
      set_tack_float: function(pn, func_float, func_embed){
        if (func_float || func_embed) TbInfoStore.set(pn, {float: func_float, embed: func_embed});
        pn.onclick = tack_float;
        return pn;
      },
      tack_float_nSblgs: function(e, all, itself){
        var ecT = e.currentTarget;
        var tgt = ecT.nextSibling;
        var bcr = tgt.getBoundingClientRect();
        var obj = cnst.init3({func_str:'left:'+bcr.left+'px:top:'+(bcr.top>0?bcr.top:0)+'px:overflow:hidden:'+(itself? 'fitb':'Show:tb'), exit:tack_dock, tack:ecT, pn:itself? tgt : null});
//        var left = tgt.offsetLeft - tgt.scrollLeft;
//        var tgt_p = tgt.parentNode;
//        var scrollTop;
//        while (tgt_p && !scrollTop) {scrollTop = tgt_p.scrollTop; tgt_p = tgt_p.parentNode;}
//        var top = tgt.offsetTop - (scrollTop||0);
//        var obj = cnst.init3({func_str:'left:'+left+'px:top:'+top+'px:overflow:hidden:'+(itself? 'fitb':'Show:tb'), exit:tack_dock, tack:ecT, pn:itself? tgt : null});
        if (!itself) {
          obj.cn.innerHTML = '<div style="display: none"></div><div></div>';
          var dst = obj.cn.childNodes[1];
          tgt.parentNode.insertBefore(obj.pn,tgt);
          dst.appendChild(tgt);
          if (all) while (obj.pn.nextSibling) dst.appendChild(obj.pn.nextSibling);
        }
        ecT.style.display = 'none';
        return obj;
      },
      tack_dock_nSblgs: function(e, itself){
        var pn = e.currentTarget.parentNode;
        var obj = TbInfoStore.get(pn);
        var tack_p = obj.tack.parentNode;
        var ref = obj.tack.nextSibling;
        if (!itself) {
          var src = obj.cn.childNodes[1];
          while (src.firstChild) tack_p.insertBefore(src.firstChild,ref); // all childrens are retrieved always.
        } else {
          pn.style.position = 'static';
          pn.removeChild(obj.tb);
          if (obj.tack.nextSibling!==pn) tack_p.insertBefore(pn,obj.tack.nextSibling);
        }
        cnst.div_destroy(pn, !itself);
        obj.tack.style.display = null;
        return obj;
      },
      tack_dock_e2tack: function(e){
        var pn = e.currentTarget.parentNode;
        return TbInfoStore.get(pn).tack;
      },
//      set_target_of_button: function(button, tgt){
//        TbInfoStore.set(button, tgt);
//      },
      dom: function(html){
        pn_tmp.innerHTML = html;
        return pn_tmp.removeChild(pn_tmp.firstChild);
      },
      doms_insertBefore: function(parent, html, ref){
        pn_tmp.innerHTML = html;
        if (!parent) parent = ref.parentNode;
        var pn;
        while (pn_tmp.firstChild) pn = parent.insertBefore(pn_tmp.firstChild,ref);
        return pn;
      },
      tack_for_popup: function(pn, func_onclick){
        var btn = document.createElement('button');
        btn.classList.add(pref.cpfx+'autoTp');
        btn.appendChild(site2[site.nickname].make_tack());
        btn.onclick = func_onclick;
        return pn.appendChild(btn);
      },
      tack_for_popup_swap: function(pn, ref, dont_overwrite){
        var pn2all = cnst.init3({func_str:'Show:tb'});
        if (ref) cnst.tack_for_popup_swap_pos(pn2all.pn, ref, dont_overwrite);
        pn2all.cn.appendChild(pn);
        return pn2all.pn;
      },
      tack_for_popup_swap_pos: function(pn2, pn, dont_overwrite){
        cnst.bottom_top(pn);
        pn2.style.top = parseInt(pn.style.top,10) - pn2.offsetHeight + 'px';
        pn2.style.left = pn.style.left;
        if (!dont_overwrite) comf.overwrite_prop(pn.style, {position:'relative', border:null, top:null, left:null});
      },
      swapAdd(pn, src){
//        var pn = src.cloneNode(true);
//        if (src.id) {pn.id = src.id; src.removeAttribute('id');}
        if (src.tagName==='SELECT') pn.selectedIndex = src.selectedIndex;
        src.style.display = 'none';
        return src.parentNode.insertBefore(pn,src.nextSibling);
      },
    };
  })();

////  function Cnst2(func_str,funcs_args){ // rolldown, rollup, exit, maximize // test for prototype base coding.
////    var pn = document.createElement('div');
////    if (!pn.style) pn.style = {};
////    pn.style.position = 'fixed';
////    pn.draggable = true;
////    pn.style.padding = '0px';
////    var funcs = func_str.split(':');
////    var i=0;
////    if (funcs[0]!='pop') {
////      pn.style.background = '#e5ecf9';
////      pn.style.color = '#000000';
////      pn.style.fontWeight = 'normal';
////      pn.style.border = '1px solid blue';
//////      pn.style.border = 'none';
////    } else {
////      pn.addEventListener(brwsr.mousewheel, div_scroll, false);
////      pn.name = 'catalog_pop';
////      i=1;
////    }
////    this.pn = pn;
////
////    var tgt = pn;
////    var funcs = func_str.split(':');
////    while (i<funcs.length) {
////      var arg = funcs[i++];
////      if (arg=='tb') {
////        pn.style.background = '#b5ccf9';
////        pn.innerHTML = '<div>' +
////          '<div style="float: left"><button name="rollup">-</button><button name="opacity,-0.1"><</button><button name="opacity,0.1">></button><span>&emsp;</span></div>' +
////          '<div style="float: right"><button name="embed_top" style="display:inline">^</button><button name="embed_bottom" style="display:inline">_</button><button name="float" style="display:none">o</button><button name="maximize" style="display:inline">[]</button><button name="exit">X</button></div>' +
////          '<div></div>' +
////          '</div>' +
////          '<div style="background: #e5ecf9; margin: 0px 3px 3px 3px"></div>';
////        var tb = pn.childNodes[0];
////        tb.childNodes[2].style.height = tb.childNodes[0].offsetHeight + 'px';
////        tb.childNodes[2].ondblclick = (function(myself){return function(){myself.button_click("rollup");};})(this);
//////        tb.childNodes[0].childNodes[3].ondblclick = tb.childNodes[2].ondblclick;
////        pn.draggable = false;
////        tb.childNodes[0].draggable = true;
////        tb.childNodes[2].draggable = true;
////        tb.childNodes[0].style.cursor = 'move';
////        tb.childNodes[2].style.cursor = 'move';
////        tgt = pn.childNodes[1];
////        this.tb = tb;
////        this.opacity = 1;
////        this.rolluped = false;
////        this.state = ['float'];
////        this.button_click_event = (function(myself){return function(){myself.button_click.call(myself,this.name);};})(this);
////        this.funcs = {};
////        comf.overwrite_prop(this.funcs,funcs_args);
////        var buttons = pn.getElementsByTagName('button');
////        for (var j=0;j<buttons.length;j++) buttons[j].onclick = this.button_click_event;
////        this.pn_1 = pn.childNodes[1];
////      } else if (arg=='txt' || (brwsr.ff && arg=='button')) {
////        tgt.appendChild(document.createTextNode(funcs[i++]));
////        tgt.style.cursor = 'pointer';
////        tgt.style.padding = '2px 5px 2px 5px';
////      } else if (arg=='button') {
////        tgt.innerHTML = '<input type="button" value="' + funcs[i++] + '">';
//////        tgt.innerHTML = '<button draggable="true">' + funcs[++] + '</button>';
////        tgt.style.padding = '0';
////        tgt.style.border = '0';
////        tgt.style.background = 'none';
////      } else if (arg=='Show2') {
////        if (site.root_body!==site.root_body2 && site.root_body2) {
////          pn.style.position = 'static';
////          site.root_body2.appendChild(pn);
////        } else site.root_body.appendChild(pn);
////      } else if (arg=='Show') site.root_body.appendChild(pn);
////      else if (arg=='tile') {
////        if (funcs[i++]=='set') {
////          if (funcs[i]=='left') {this.tile[funcs[i]] = parseInt(pn.style[funcs[i]].replace(/px/,''),10) + pn.offsetWidth;i++;}
////          else {this.tile[funcs[i]] = parseInt(pn.style[funcs[i]].replace(/px/,''),10) + pn.offsetHeight;i++;}
////        } else {pn.style[funcs[i]] = this.tile[funcs[i]]+'px';i++;}
////      } else tgt.style[arg] = funcs[i++];
////    }
////    pn.addEventListener('dragstart', this.div_dragstart, false);
//////    pn.addEventListener('dragstart', this.div_dragstart, true);
////    pn.addEventListener('dragend', this.div_dragend, false);
////  }
////
////  Cnst2.prototype = {
////    tile : {lef: 0, bottom: 0},
////    button_click : function(name){
////      if (name.indexOf('opacity')!=-1) {
////        this.opacity += parseFloat(name.substr(name.indexOf(',')+1),10);
////        if (this.opacity>1) this.opacity = 1;
////        else if (this.opacity<0.1) this.opacity = 0.1;
////        this.pn.style.opacity = this.opacity;
////      } else if (name.indexOf('rollup')!=-1) {
////        if (this.rolluped) {
////          this.tb.style.width = ''; // means 'auto'
////          this.pn_1.style.display = ''; // for dollchan
////          if (this.funcs.rolldown) this.funcs.rolldown();
////        } else {
////          this.tb.style.width = this.tb.offsetWidth + 'px'; // for dollchan
////          this.pn_1.style.display = 'none'; // for dollchan
////          if (this.funcs.rollup) this.funcs.rollup();
////        }
////        this.rolluped = !this.rolluped;
////      } else if (['embed_top','embed_bottom','float','maximize'].indexOf(name)!=-1) {
////        comf.overwrite_prop(this.tb.childNodes[1].getElementsByTagName('*')[this.state[0]].style,{display:'inline'});
////        if (this.state[0]=='float') this.state = [name, this.pn.style.left, this.pn.style.top, this.pn.childNodes[1].style.width, this.pn.childNodes[1].style.height];
////        else this.state[0] = name;
////        if (name=='maximize') {
////          var header_height = site.header_height();
////          comf.overwrite_prop(this.pn.style, {left:'0px', top:header_height + 'px', position:'fixed', resize:'none'});
////          comf.overwrite_prop(this.pn.childNodes[1].style,
////            {width: document.documentElement.clientWidth + 'px', height: document.documentElement.clientHeight - this.tb.offsetHeight - header_height + 'px', resize:'none'});
////        } else if (name=='float'){
////          comf.overwrite_prop(this.pn.style, {left:this.state[1], top:this.state[2], position:'fixed', resize:'both'});
////          comf.overwrite_prop(this.pn.childNodes[1].style, {width:this.state[3], height:this.state[4], resize:'both'});
////        } else {
////          var ref = site.embed_to[(name==='embed_top')?'top':'bottom'];
////          if (ref){
////            comf.overwrite_prop(this.pn.style, {left:'auto', top:'auto', position:'static', resize:'none'});
////            comf.overwrite_prop(this.pn.childNodes[1].style, {width:'auto', height:'auto', resize:'none'});
////            ref.parentNode.insertBefore(this.pn,ref);
////          }
////        }
////        comf.overwrite_prop(this.tb.childNodes[1].getElementsByTagName('*')[this.state[0]].style,{display:'none'});
////        if (this.funcs.maximize) this.funcs.maximize();
////      } else if (name==='exit') this.funcs.exit();
////    },
////    drag_sx: null,
////    drag_sy: null,
////    div_dragstart: function(e){
//////    var drag_cursor_style;
////      this.drag_sx = e.screenX;
////      this.drag_sy = e.screenY;
//////      drag_cursor_style = pn.style.cursor;
//////      pn.style.cursor = 'move';
////      e.dataTransfer.setData('text/plain', ''); // for FF. CH doesn't require this.
//////      e.preventDefault();
//////      e.stopPropagation();
////    },
////    div_dragend: function(e){
////      if (e.currentTarget.style.left!='') e.currentTarget.style.left   = (parseInt(e.currentTarget.style.left.replace(/px/,''))   + e.screenX - this.drag_sx) + 'px';
////      else e.currentTarget.style.right = (parseInt(e.currentTarget.style.right.replace(/px/,''))   - e.screenX + this.drag_sx) + 'px';
////      if (e.currentTarget.style.bottom!='') e.currentTarget.style.bottom = (parseInt(e.currentTarget.style.bottom.replace(/px/,'')) - e.screenY + this.drag_sy) + 'px'; // from bottom.
////      else e.currentTarget.style.top = (parseInt(e.currentTarget.style.top.replace(/px/,'')) + e.screenY - this.drag_sy) + 'px';
//////      pn.style.cursor = drag_cursor_style;
////    }
////  }

  var styleSheet = (site0.isStep)? {} : (function(){
    var StyleSheet;
//    function replace_rule(rule_str,idx){
//      if (ss.cssRules[idx]) ss.deleteRule(idx);
//      ss.insertRule(rule_str,idx);
//    }
//    function make_rule_click_area(){
//      replace_rule('.'+pref.cpfx+((pref.catalog.click_area==='entire')? 'thread' : 'thumbnail')+' {cursor: pointer}',2);
//    }
    function make_styles(){
      var pf = pref.style.design;
      var tgts = ['titleBar','window','popUp','post_new','post_editing'];
      var rules = tgts.map(v=>pf[v].base);
      var dic = new Map();
      tgts.forEach((v,i)=>{var s = pf[v].ov && pf[v].sel; if (s)
        if (pf[v].comp) {
          var n = document.querySelector(s);
          var sl = n && window.getComputedStyle(n);
          if (sl) rules[i] = overwrite_rules(rules2Map(rules[i]).set('color',sl['color']).set('background-color',sl['background-color']));
        } else if (dic.has(s)) dic.get(s).push(i); else dic.set(s,[i]);});
      if (dic.size>0) copy_styles_and_overwrite(rules, dic, dic.size);
      tgts.forEach(function(v,i){register_cc(v, rules[i]);});
    }
    function copy_styles_and_overwrite(rules, dic, count){
      for (var i=document.styleSheets.length-1;i>=0;i--) {
        var doc_rules = document.styleSheets[i].cssRules;
        if (doc_rules) for (var j=doc_rules.length-1;j>=0;j--) {
          var t = doc_rules[j].selectorText;
          if (dic.has(t)) {
            dic.get(t).forEach(k=>rules[k] = overwrite_rules(rules2Map(rules[k]),rules2Map(doc_rules[j].cssText)));
            if (--count===0) return;
          }}}
    }
    function rules2Map(rules){
      var m = new Map();
      rules.replace(/.*\{/,'').replace(/\}.*/,'').split(';').forEach(function(v){var a = v.split(':');m.set(a[0],a[1]);});
      return m;
    }
    function overwrite_rules(dst, src){
      if (src) for (var rule of src.keys()) if (rule.search(/\s*(background|color)/)>=0) dst.set(rule,src.get(rule));
      return Array.from(dst.entries()).map(function(v){return v.join(':');}).join(';')
    }
//    function make_styles(){
//      var rules = [pref.style.fix.titleBar_str, pref.style.fix.window_str, pref.style.fix.popUp_str];
//      if (pref.style.sel==='copy') {
//        var rules_str = [pref.style.copy.titleBar_str, pref.style.copy.window_str, pref.style.copy.popUp_str];
//        for (var i=document.styleSheets.length-1;i>=0;i--) {
//          var doc_rules = document.styleSheets[i].cssRules;
//          if (!doc_rules) continue;
//          for (var j=doc_rules.length-1;j>=0;j--) {
//            for (var k=0;k<3;k++) {
//              if (rules_str[k] && doc_rules[j].selectorText===rules_str[k]) {
//                rules[k] = trim_rules(doc_rules[j].cssText);
//                rules_str[k] = null;
//              }
//            }
//            if (!rules[0] && !rules[1] && !rules[2]) break;
//          }
//          if (!rules[0] && !rules[1] && !rules[2]) break;
//        }
//      }
//      replace_rule('.'+pref.cpfx+'titleBar'+' {' + rules[0] + '}',4);
//      replace_rule('.'+pref.cpfx+'window'  +' {' + rules[1] + '}',5);
//      replace_rule('.'+pref.cpfx+'popUp'   +' {' + rules[2] + '}',6);
//    }
//    function trim_rules(str){
//      var rules = str.replace(/.*\{/,'').replace(/\}.*/,'').split(';');
//      for (var i=rules.length-1;i>=0;i--) if (rules[i].split(':')[0].search(/\s*(background|color)/)==-1) rules.splice(i,1);
//      return rules.join(';');
//    }
    function add_to_watch(elem){ // http://stackoverflow.com/questions/2635814/javascript-capturing-load-event-on-link
//      elem.addEventListener('change', dummy_add, false); // doesn't work.
      new MutationObserver(dummy_add).observe(elem, {attributes:true});
    }
    function dummy_add(){
      if (!StyleSheet) return;
      var dummy = document.createElement('img');
      dummy.src = this.href;
      dummy.onerror = dummy_remove;
      document.getElementsByTagName('body')[0].appendChild(dummy);
    }
    function dummy_remove(){
      this.parentNode.removeChild(this);
      make_styles();
    }
    var spoiler_org = null;
    var spoiler_open = null;
    function open_spoiler_text(){
      if (!cataLog.embed_mode) return;
      var desc = site2[site.nickname].spoiler_text;
      if (desc.spoiler) {
        if (!spoiler_org && !spoiler_open) {
          if (desc && pref[cataLog.embed_mode].open_spoiler_text) {
            for (var j=0;j<document.styleSheets.length;j++)
              if (document.styleSheets[j].cssRules)
                for (var i=0;i<document.styleSheets[j].cssRules.length;i++) {
                  var sText = document.styleSheets[j].cssRules[i].selectorText;
                  if (sText) {
                    if (sText.search(desc.spoiler)!=-1) spoiler_org  = [j,i,document.styleSheets[j].cssRules[i].style.cssText];
                    if (sText.search(desc.open   )!=-1) spoiler_open = [j,i,document.styleSheets[j].cssRules[i].style.cssText];
                  }
                }
          }
        }
        if (spoiler_org && spoiler_open)
          document.styleSheets[spoiler_org[0]].cssRules[spoiler_org[1]].style.cssText = spoiler_org[2] + ((pref[cataLog.embed_mode].open_spoiler_text)? spoiler_open[2] : '');
      } else { // cssRules is null when stylesheet is placed in other domain. http://hakuhin.jp/js/style_sheet.html#STYLE_SHEET_04
        if (pref[cataLog.embed_mode].open_spoiler_text) {
          spoiler_open = [document.styleSheets.length-1, document.styleSheets[document.styleSheets.length-1].cssRules.length, desc.open_rule];
          document.styleSheets[spoiler_open[0]].insertRule(spoiler_open[2],spoiler_open[1]);
        } else if (spoiler_open) document.styleSheets[spoiler_open[0]].deleteRule(spoiler_open[1]);
      }
    }
    var registry = []; // 'dummy','dummy','dummy','dummy']; // ,'dummy','dummy','dummy'];
    function register(selector, rule){
      if (rule===undefined) { // for raw format
        var idx_s = selector.indexOf('{');
        rule = selector.slice(idx_s+1,selector.lastIndexOf('}'));
        selector = selector.slice(0,idx_s);
      }
      var idx = registry.indexOf(selector);
      if (idx!=-1) StyleSheet.deleteRule(idx);
      if (rule===null || rule==='') {
        if (idx!=-1) registry.splice(idx,1);
      } else {
        if (idx===-1) {
          idx = registry.length;
          registry[idx] = selector;
        }
        StyleSheet.insertRule(selector+' {'+rule+'}', idx); // (rule[0]===':'? rule : ' {'+rule+'}'), idx);
      }
    }
    var cpf = '.'+pref.cpfx;
    function register_cc(cls,rule){ // class is a reserved keyword
      register(cpf+cls, rule);
    }
    function register_ccAll(cls,rule){
      register(cls.replace(/\./g,cpf), rule);
    }
    var addSS;
    var ss_cc = null;
    function addSS_changed(){ // additional StyleSheets
      if (addSS) addSS.forEach(function(v){v.parentNode.removeChild(v);});
      addSS = null;
      var pf = pref.style.addSS;
      if (pf.use) addSS = pf.str.split('\n').filter(function(v){return v;}).map(function(v){
        var l = document.createElement('link');
        l.rel = 'stylesheet';
        l.href = v;
        return document.head.insertBefore(l,ss_cc);});
    }
    addSS_changed(); // init
    var uss = null;
    function userCSS_changed(){
      var pf = pref.style.userCSS;
      if (pf.use) {
        var s = uss || document.createElement('style');
        s.textContent = pf.str;
        if (!uss) uss = document.head.appendChild(s);
      } else {
        if (uss) {
          document.head.removeChild(uss);
          uss = null;
        }
      }
    }
    userCSS_changed(); // init
    function catalog_customSize(pf){
      return (pf.width==0? '' : 'width:'+pf.width+'px;') + (pf.height==0? '' : (pf.max? 'max-':'')+'height:'+pf.height+'px');
    }
    return {
      init: function(embed_mode){
        ss_cc = document.createElement('style');
        ss_cc.textContent = cpf + 'tag{cursor:pointer;}\n'+
          cpf + 'autoTp{opacity:0;position:absolute;top:0px;left:0px;}\n'+
          cpf + 'autoTp:hover{opacity:1;}\n'+
          cpf + 'embeddedTriage{display:inline-block;cursor:pointer}\n'+ // must be block for rotation, see https://stackoverflow.com/questions/31504419/div-can-rotate-span-cant-rotate-but-it-can-animate-the-transformation
          cpf + 'button{cursor:pointer;}\n'+
          (site2['RSS']? cpf+'hr_text, '+cpf+'hr_text2{display:flex;align-items:center}\n'+
            cpf+'no_text '+cpf+'hr_text, '+cpf+'no_text '+cpf+'hr_text2{font-size:0}\n'+
            cpf+'hidden{display:none}\n'+
            cpf+'hr_text::after{content:"";border-top:solid gray 1px;flex-grow:1}\n'+
            cpf+'hr_text2::after{content:"";border-top:double gray 3px;flex-grow:1}\n'+
            cpf+'disabled{position:relative}\n'+
            cpf+'disabled::after{content:"";border-top:double gray 3px;position:absolute;top:0.5em;left:0;width:100%;}\n':'')+
//        register_cc('hr_text','display:flex;align-items:center');
//        register_cc('hr_grow','border-top:solid gray 1px;flex-grow:1');
//        register_cc('hr_grow2','border-top:double gray 3px;flex-grow:1');
          (site2[site.nickname].styles||'');
        document.head.insertBefore(ss_cc,uss);
        StyleSheet = document.head.insertBefore(document.createElement('style'),uss).sheet;
        register_cc('deleted', 'opacity:0.4;');
        make_styles(); // rule4,5,6
if (site.nickname==='4chan' || site.nickname==='rssn') {
        register('.extended-custom .teaser, .extended-custom2 .teaser','display:block');
        register('.extended-custom .thread',catalog_customSize(pref.float.format.ec));
        register('.custom .thread',catalog_customSize(pref.float.format.nc));
        register('.extended-custom2 .thread',catalog_customSize(pref.float.format.ec2));
        register('.custom2 .thread',catalog_customSize(pref.float.format.nc2));
}
////        register(cpf+'hMenu', 'display:inline-block;');
////        register(cpf+'hMCnt button', 'display:block;width:100%');
////        register(cpf+'hMenu '+cpf+'hMCnt', 'display:none;');
////        register(cpf+'hMenu:hover '+cpf+'hMCnt', 'display:block;position:absolute;');
//        for (var prop in pref.style.tips) if (prop.slice(-4)!=='_str' && pref.style.tips[prop]) this.register_cc(pref.style.tips[prop+'_str']);
        var onchange_funcs = {
          'style.tips.*': function(){
            for (var i=1;i<11;i++) register_cc('merged'+i+'::before', pref.style.tips.merged? 'float:left;content:"'+Array(i).fill(pref.style.tips.merged_cnt).join('')+'"' : null);
          },
//          'style.tips.*w/': function(e, name){
//            var prop = name.split('.')[2];
//            var str = pref.style.tips[prop+'_str'];
//            register_cc(pref.style.tips[prop]? str : str.slice(0,str.indexOf('{')), pref.style.tips[prop]? undefined : null);
//          },
////          'catalog.click_area': make_rule_click_area,
          'style.design.*.*W/': make_styles,
//          'style.sel': make_styles,
//          'style.fix.*': make_styles,
//          'style.copy.*': make_styles,
//          'style.reset.*w/': function(e,name){
//            var pref_def = pref_default();
//            var tgt = name.split('.')[2];
//            pref_func.pref_overwrite(pref.style[tgt], pref_def.style[tgt]);
//            pref_func.settings.apply_pn13_1();
//            make_styles();
//          },
          'style.addSS.*': addSS_changed,
          'style.userCSS.*': userCSS_changed,
//          'style.post_editing': function(){register_cc('post_editing', pref.style.post_editing);},
          '*.open_spoiler_text': open_spoiler_text,
          '*.posts_search_op': function(e,embed_mode){
            var pf = pref[(embed_mode || cataLog.embed_mode)];
            register_cc('search_miss', (pf.posts_search_op==='opaque')? 'opacity:'+pf.posts_search_op_opacity/100+' !important' :
                                       (pf.posts_search_op==='hide')? 'display:none !important' : null);},
          'MODEVIEW.format.*.*W/': function(e,[mode,,tgt]){
            if (tgt==='thumb') return;
            register_cc((tgt[0]=='e'?'extended-':'')+'custom'+(tgt[2]||'')+' .thread',catalog_customSize(pref[mode].format[tgt]));
          },
        };
        onchange_funcs['*.posts_search_op_opacity'] = onchange_funcs['*.posts_search_op'];
        for (var name in onchange_funcs) pref_func.settings.onchange_funcs[name] = onchange_funcs[name];
        onchange_funcs['*.posts_search_op'](null,embed_mode);
//        onchange_funcs['style.post_editing']();
        if (pref[embed_mode].open_spoiler_text) open_spoiler_text();
        if (!pref.test_mode['98'] && embed_mode==='catalog' && !site.hasViewSel) register('.backlink','display:inline;font-size:0.8em;');
        if (pref.style.tips.merged) onchange_funcs['style.tips.*']();
      },
      add_to_watch: add_to_watch,
      register_cc: register_cc,
      register_ccAll: register_ccAll,
      make_styles: make_styles, 
    };
  })();

  var Tooltips = (site0.isStep)? null : (function(){
        var tooltip = document.createElement('div');
        tooltip.style.position = 'fixed';
////        tooltip.style.background = '#e5f4f9';
////        tooltip.style.color = '#000000';
////        tooltip.style.border = '2px solid blue';
//          tooltip.style.fontWeight = 'normal';
        tooltip.setAttribute('class', pref.cpfx+'popUp');
        tooltip.style.zIndex = pref.tooltips.zIndex;
        tooltip.innerHTML = '<div></div>';
        tooltip.onmouseover = keep_shown;
        tooltip.onmouseleave = hide2;
        var tack = cnst.tack_for_popup(tooltip, function(e){
          cnst.tack_for_popup_swap(tooltip.replaceChild(document.createElement('div'),tooltip.firstChild), tooltip, true);
          hide_exe2();
        });
        var tack_displayed = true;
        var tooltip_on = null;
//        var tooltip_shown = false;
        var tooltip_kind = 'help';
//        var func = {};
        var show_timer = null;
        var hide_timer = null;
//        var str_tooltip = '';

//        function add_hier(pn){ // working code
//          var all = pn.getElementsByTagName('*');
////          if (pref.test_mode.tips) for (var i=0;i<all.length;i++) if (!str[all[i].name]) str[all[i].name] = 'Name: ' + all[i].name;
////          for (var i=0;i<all.length;i++) if (all[i].name) add(all[i]); // span can't return if .name
//          for (var i=0;i<all.length;i++) {
//            var name = all[i].getAttribute('name');
//            if (name && (str[name] || (str[name]!==false && pref.test_mode.tips))) add(all[i]);
//          }
//        }
//        function remove_hier(pn){
//          var all = pn.getElementsByTagName('*');
////          for (var i=0;i<all.length;i++) if (all[i].name) remove(all[i]);
//          for (var i=0;i<all.length;i++) if (all[i].hasAttribute('name')) remove(all[i]);
//        }
//        function add(elem){
//////          if (str.hasOwnProperty(elem.name)) {
//            if (!func.hasOwnProperty(elem.name)) func[elem.name] = [];
//            func[elem.name].push(elem);
//            elem.addEventListener('mouseover', show, false);
//////          }
//        }
//        function remove(elem){
//          if (func.hasOwnProperty(elem.name)) {
//            var elems = func[elem.name];
//            for (var i=0;i<elems.length;i++) {
//              if (elem===elems[i]) {
//                elem.removeEventListener('mouseover', show, false);
//                elems.splice(i,1);
//                if (elems.length==0) delete func[elem.name];
//                break;
//              }
//            }
////            for (var i=0;i<elems.length;i++) {
////              var elem = func[elem.name][i];
////              elem.removeEventListener('mouseover', show, false);
////            }
////            delete func[elem.name];
//          }
//        }
        function show(e){
//          if (!pref.tooltips.help.show) return;
          var et = e.target.tagName==='LABEL'? e.target.childNodes[0]: e.target;
          var name = et.name || et.getAttribute('name') || et.dataset && (et.dataset.name || et.dataset.show_value);
//          if (name && et.type==='radio') name += ', '+et.value;
          if (name==='SHOW2_I') name = e.target.previousSibling.childNodes[1].dataset['show_value'];
          if (name==='SHOW2') name = e.target.childNodes[1].dataset['show_value'];
          if (!name) return; // for add_root
//          var name = this.name || this.getAttribute('name')); // for add_hier
////          for (var i in str_func) if (this.name.indexOf(i)==0) {
////            str_tooltip = str_func[i](this,e);
////            break;
////          }
          var str_tooltip = name[0]==='#' && str['#*'] || str[name] ||
//                str[this.parentNode.name] ||
                str[name.slice(0,name.lastIndexOf('.')+1)+'*'] ||
                str_hier(str,name,'') || '';
          if (typeof(str_tooltip)==='function') str_tooltip = str_tooltip.call(str,e);
          if (pref.test_mode.tips) str_tooltip = 'Name: ' + name + (et.type==='radio'? ', '+et.value : '') + '\n' + str_tooltip;
          str_tooltip = str_tooltip.replace(/\n/mg,'<br>').replace(/  /g,'&ensp;&ensp;'); //  .replace(/^\s|<br>\s/g,'$&&ensp;'); // keep white space at its head, // https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Whitespace

//          if (str_tooltip) show_1.call(this,e,str_tooltip);
          return str_tooltip;
        }
//        function show_1(e,str_func, callback, info_tips){ // working code
//          tooltip_kind = (info_tips)? 'info' : 'help';
//          if (!pref.tooltips[tooltip_kind].show) return;
//          tooltip.innerHTML = str_func;
//          if (callback) callback(tooltip);
//          if (hide_timer!==null) {clearTimeout(hide_timer);hide_timer=null;} // test
//          if (!e || tooltip_on===e.target) return;
//          tooltip.style.left = e.clientX + 20 + 'px';
//          tooltip.style.top  = e.clientY + 20 + 'px';
////          if (hide_timer!==null) {clearTimeout(hide_timer);hide_timer=null;}
//          if (tooltip_on) tooltip_on.removeEventListener('mouseout' ,hide, false);
//          else {
//            if (show_timer!==null) clearTimeout(show_timer);
//            show_timer = setTimeout(show_exe,pref.tooltips[tooltip_kind].popup_delay);
//          }
//          tooltip_on = e.target;
//          tooltip_on.addEventListener('mouseout' , hide, false);
//        }
//        function show_exe(){
//          show_timer = null;
////          tooltip_set(tooltip_contents.str_func, tooltip_contents.e, tooltip_contents.callback);
//          site.script_body.appendChild(tooltip);
//          tooltip_shown = true;
//        }
//        function hide(){ // called from both event and external function.
//          if (hide_timer===null) hide_timer = setTimeout(hide_exe,pref.tooltips[tooltip_kind].popdown_delay);
//        }
//        function hide_exe(){
//          hide_timer = null;
//          if (tooltip_shown) {
//            tooltip.parentNode.removeChild(tooltip);
//            tooltip_shown = false;
//          }
//          if (show_timer!==null) {clearTimeout(show_timer);show_timer=null;}
//          if (tooltip_on) {
//            tooltip_on.removeEventListener('mouseout' ,hide, false);
//            tooltip_on = null;
//          }
//        }

        var tooltip_e = null; // valid while show_timer is running or the tooltip is shown.
        function show_0(e){
          var name = e.target.getAttribute('name');
          var kind = (name && name[0]==='#')? 'info' : 'help'
          if (pref.tooltips[kind].show) req_show(kind, e, show, kind==='help');
        }
        function req_show(kind, e, func, tack_req){
          if (tack_displayed^tack_req) {tack_displayed = tack_req; tack.style.display = (tack_req)? null : 'none';}
          tooltip_kind = kind;
          var e_obj = {target:e.target, currentTarget:e.currentTarget, clientX:e.clientX, clientY:e.clientY, func:func};
          if (!tooltip_on) {
            if (show_timer!==null) clearTimeout(show_timer);
            show_timer = setTimeout(show_exe2,pref.tooltips[kind].popup_delay);
            tooltip_e = e_obj; // e;
          } else if (show_2(e, func(e))) tooltip_e = e_obj;
        }
        function show_exe2(){
          show_timer = null;
          if (show_2(tooltip_e, tooltip_e.func(tooltip_e))) site.script_body.appendChild(tooltip);
          else tooltip_e = null;
        }
        function show_2(e,contents){
          if (!contents) {hide2();return false;}
          if (typeof(contents)==='string') tooltip.firstChild.innerHTML = contents;
          else {
            tooltip.firstChild.innerHTML = contents.html;
            if (contents.callback) contents.callback(tooltip.firstChild);
          }
          if (hide_timer!==null) {clearTimeout(hide_timer);hide_timer=null;}
          if (e && tooltip_on!==e.target) {
            tooltip.style.left = e.clientX + 20 + 'px';
            tooltip.style.top  = e.clientY + 20 + 'px';
            tooltip_on = e.target;
          }
          return true;
        }
//        function hide3(e){
//          if (e.target===e.currentTarget) hide2(); // out from root
//        }
        function hide_if(pn){ // check the invoker
          if (tooltip_e && tooltip_e.currentTarget===pn) hide2();
        }
        function hide_if2(e){ // check if event procedure is done
          if (tooltip_e && tooltip_e.target!==e.target) hide2();
        }
        function hide2(){ // called from both event and external function.
          if (tooltip_e) {
//          if (tooltip_e && (!pref.test_mode.tips || hide_timer===null)) { // '&& hide_timer===null' leaves popups sometimes, but it's needed for test_mode.tips because of laziness.(patch)
////          if ((tooltip_on || tooltip_e) && hide_timer===null) {
            if (show_timer!==null) {clearTimeout(show_timer);show_timer=null;tooltip_e=null;}
            if (hide_timer===null) hide_timer = setTimeout(hide_exe2,pref.tooltips[tooltip_kind].popdown_delay);
          }
        }
        function hide_exe2(){
          hide_timer = null;
          if (tooltip_on) {
            tooltip.parentNode.removeChild(tooltip);
            tooltip_on = null;
//            tooltip_e = null;
          }
        }
        function keep_shown(e){
          if (hide_timer!==null) {clearTimeout(hide_timer);hide_timer=null;}
          tooltip_e.currentTarget = e.currentTarget;
          tooltip_e.target = e.target;
          e.stopPropagation();
        }
        
        function str_hier(root,name){
          if (!root) return null; // cancel if miss
          var idx;
          return root[name] || root['*'] || ((idx=name.indexOf('.'))===-1? null : str_hier(root[name.slice(0,idx)],name.slice(idx+1)));
        }
        var str = {
//          'pn_catalog_triage': 'hide forever, hide until new replies, watch, unwatch, undo.\n'+
//                               'You can customize this in settings -> Catalog: Appearance (in top selector) -> Style:',
////          'pn_catalog_triage': '1st row: Hide it forever.\n2nd row: Hide it now, but it will appear again when the thread gets new replies,\n' +
////            '  and new replies are marked.\n3rd row: Don\'t hide it, just change its appearance.\n' +
////            'Each column shows appearance the thread will get.\n' +
////            'Don\'t forget checking \'Exclusive list\' and \'Attribute list\' for using this function.\n' +
////            'In other words, you can appear it again by unckecking them.\nAnd you can configure these appearance in \'Attribute list\' and \'Triage styles\'.',
          'catalog_promiscuous': 'Gather information whatever.',
          filter: {
              kwd: {
                ew: 'Exact word match.\n \'index\' matches \'index\' but \'indexing\' if checked.\n \'index*\' maches \'index\' and \'indexing\'.',
                re: 'Regular Expression.',
                ci: 'Case insensitive. Don\'t distinguish upper and lower case.',
                sentence: '"XXXX YYYY" matches itself as a whole instead of matching "XXXX" and/or "YYYY"',
                inPost: 'In a post restriction.\n all keywords must be in a post if checked.\n No difference if one keyword.',
              },
//              time: 'Time : Hide threads which have no posts after the time.',
//              time_creation: 'Time(Creation Time) : Hide threads which were created before the time.',
//              time_watch: 'Watch: watch threads which have posts after the time.',
//              time_watch_creation: 'Watch(Creation Time): watch threads which were created after the time.',
          },
//          'filter.time_mark': 'Mark: mark newer posts and scrool to them when it\'s opened.',
          'filter.tag_search.str': 'String for searching tags.\n[CAUTION] this function is very heavy, keep blank here when you don\'t use.',
          'filter.tag_search.showActives': 'Always show active tags.\n[CAUTION] this function is very heavy, keep unchecked if you don\'t use.',
          'filter.tag_list': false,
          'network.cross_domain': 'Use \'direct\' if you could, because it\'s quite light, but it doesn\'t work in almost of all environments.\n\'indirect\' usually works well and gives full functionality.\n\Or you can use \'GreaseMonkeyM\'s extension if your browser tend to close steps for cross domain when you connect over X-Frame-Options, but this doesn\'t provide localStorage/IDB features in remote domains.',
          'debug_mode' : 'Debug mode.',
          'show_tooltip' : 'Show this tooltip.',
          get GeneralRules(){return this.CommentDS + this.Identifier;},
          'CommentDS': 'Comment:\n  Double slash(//) starts comment to end of the line.\n',
          'Identifier': 'Identifier:\n'+
            '  Identifier is a string which can contain domain, board and/or thread.\n'+
            '  Any parts of identifier can be omitted.\n'+
            '  If all parts of identifier are omitted, it means \'for all\'.\n'+
            '  Board identifier must be enclosed by slashes, like /a/.\n'+
            '  Thread identifier must be numeric.\n'+
            '  Otherwise, it is taken to be a domain identifier.\n'+
            '  Domain identifier must be one of '+site0.domains.slice(0,-1).join(', ')+' and '+site0.domains.slice(-1)+'.\n'+
            '  When some identifiers are omitted, it\'ll be interpreted as follows. Top has the priority.\n'+
            '    domain/board/thread\n'+
            '    /board/thread\n'+
            '    domain/thread\n'+
            '    thread\n'+
            '    domain/board/\n'+
            '    /board/\n'+
            '    domain\n',
          'StyleString': 'StyleString:\n  StyleString is set to object.style.XXXX directly.\n  You can use all of styles in CSS, which has \'background\', \'border\', \'fontSize\', \'opacity\', \'width\' and \'height\'.\n  Delimiter is a semicolon as well as CSS, and you can use \'!important\' also.\n',
          'catalog.style_general_list_str': function(){return '[Identifier][;StyleString][;StyleCommand]\n  (NOTE: There must be a delimiter ";" even if whole identifier is omitted)\n\n'+this.GeneralRules+this.StyleString+this.StyleCommand;},
          'StyleCommand': 'StyleCommand:\n  STICKY[:false] // sticky or unsticky.\n  SHOW // the thread is shown always when keyword filter is not active. (ignore tag/time/list filters)\n',
//            'About Identifier, see \'board group\'.',
          'catalog.board.recommendation' : 'Scans \'board announcement\' and gets owner\'s recommendation. This recommendation must be start \'Recommendation: \', and the following part of the line is treated as a line of board groups. The recommendation can be seen in a last line of board groups.',
          'cli.eval_str' : 'For developers. This function is killed by security reason. If you want to use, you must uncomment the function \'pref_func.site2_eval\' by yourself.',
//          'thread_reader.buttons.B' : 'Bookmark this thread to current catalog.',
//          'thread_reader.buttons.UB': 'Unbookmark this thread from current catalog.',
//          'thread_reader.buttons.X' : function(){return 'Kill this thread from parent catalog' + ((pref.thread_reader.triage_close)? ', and close.' : '.');},
//          'thread_reader.buttons.v' : function(){return 'Hide this thread from parent catalog untill it gets new posts' + ((pref.thread_reader.triage_close)? ', and close.' : '.');},
          'catalog_open_where' : 'this tab: open the thread in this tab, and catalog will be closed.\nnew tab always: this enables you to open a thread in multiple tabs.\nnamed tab: this prevents you from opening a thread in multiple tabs.\na fixed tab: this enables you to use a catalog window as a dashboard.',
          'virtualBoard.scan_domains.*': 'None: Don\'t scan.\nBoard: Read boards.json and set them to boardlist, not scan threads at startup. Threads are scanned when you read them.\nThread: Scan all threads at startup.',
          'scanBoard': 'Scan selected virtual boards.\nScan this physical board if no virtual board is selected.',
          'scanSite': 'Scan all virtual boards.\nScan all boards in this site if no virtual board is selected.',
          'style.fix.*': 'Write style strings.',
          'style.copy.*': 'Write exact selectorText in CSS, then its styles are copied.',
          debug_mode:['pipe','','removed threads from catalog'],
          dummy: '',
          '#*': function(e){return liveTag.tooltip(e);},
          'ex': function(e){var pname = e.target.parentNode.getAttribute('name'); if (pname[0]==='#') return this['#*'](e);},
          'virtualBoard.bl.dumb': 'Hide virtual boards in boardlist if search is inactive.',
          'virtualBoard.bl.max': 'Maximum number of virtual boards in boardlist.',
          'virtualBoard.bl.showActives': 'Always show active virtual boards in boardlist.',
          'virtualBoard.search.str': 'Filter virtual boards in boardlist.',
          'dashboard':{'rss':{'*':function(e){var et = e.target;
                                              var val = et.tagName==='INPUT' && et.type==='text' && et.value || '';
                                              return pref_func.sanitize(val && val[0]==='['? val.replace(/,/g,',\n') : val);
                                             }}},
          'top':'Embed this window to top.',
          'bottom':'Embed this window to bottom.',
          'max':'Maximize this window.',
          'float':'Float this window.',
          'exit':'Close this window.',
          'op_p':'Increase opacity.',
          'op_m':'Decrease opacity.',
          'roll_toggle':'Roll up/down.',
          'autoTp':'Hide this title bar.\nHide this title bar to bottom with shiftKey.',
          'autoHeight':'Auto Height.',
        };
        str.archive = {kwd:str.filter.kwd};
        str['filter.tag_search.re'] = str.filter.kwd.re;
        str['virtualBoard.search.re'] = str.filter.kwd.re;
        str['pk'] = str['in'] = str['ex'];
//        Object.defineProperty(str,'catalog_triage_str',{value:str['catalog_triage_str'], enumerable:true, configurable:true, writable:false});
        return {
          add_root: function(elem){
            elem.addEventListener('mouseover', show_0, false);
            elem.addEventListener('mouseleave', hide2, false);
          },
          remove_root: function(elem){
            elem.removeEventListener('mouseover', show_0, false);
            elem.removeEventListener('mouseleave', hide2, false);
          },
          show: show,
//          show_1: show_1,
//          hide: hide,
          str: str,
          req_show:req_show,
//          hide2: hide2,
          show_2: show_2,
          hide: hide2,
          hide_if: hide_if,
          hide_if2: hide_if2,
        };
  })();

  var Triage = site0.isStep? null : (function(){ // pref_func.tooltips for iframe
      var tooltips = {
        KILL: 'Hide this thread permanently, and set style',
        TIME: 'Hide this thread until it gets new replies, and set style',
        NONE: 'Delete "hidden" mark and set style',
        KILL_N: 'Hide this thread permanently',
        TIME_N: 'Hide this thread until it gets new replies',
        ATTR: 'Set style additionally',
        WATCH: 'Watch and mark as I\'ve read all posts so far',
        UNWATCH: 'Delete "watched" mark',
        SHOW: 'Show always without regard to filter',
        UNSHOW: 'Remove "show" mark',
        STICKY: 'Sticky this thread',
        UNSTICKY: 'Unsticky this thread',
        GO: 'Open this thread',
        UNDO: 'Undo last modification', // . Effective to commands above',
        ARC1: 'Take an archive (one-shot archive)',
        ARC: 'Start archiving this thread',
        UNARC: 'Stop archiving this thread',
        MERGEB: 'Mark as marge base',
        MERGE: 'Merge this thread with merge base',
//        MERGEOP: 'Merge all threads in OP',
//        MERGEN: 'Merge with near thread',
//        MERGEC: 'Issue merge command',
//        MERGECR: 'Issue merge command (reverse merge)',
//        MERGECU: 'Issue unmerge and merge command',
        UNMERGE: 'Unmerge this thread',
        UNDOMERGE: 'UNDO last merge operation',
//        UNMERGELV: 'Unmerge level this thread',
        SPC: 'Spacer for caption. Nothing happen',
        MENU: 'Open menu',
        PMENU: 'Open post menu',
        EXPAND: 'Expand following triages',
        PWATCH: 'Mark as I\'ve read all posts before this',
        PHIDE: 'Hide this post',
        PHIDEAFTER: 'Hide this post and posts which replies to this post',
        PUNHIDE: 'Unhide this post',
        PDISABLEHIDE: 'Disable hide function; Shows all posts temporarily',
        PENABLEHIDE: 'Enable hide function; Hide hidden marked posts again',
        PCLEARHIDE: 'Clear hide list and show all posts',
//        '*m': 'apply style to members at headline view',
//        '*s': 'force single target',
      };
      Tooltips.str['triage'] = function(e){
        var cmd = e.target.getAttribute('data-cmd');
        var attr = e.target.getAttribute('data-attr');
        var style = ['NONE','ATTR','KILL','TIME'].indexOf(cmd)!==-1;
        return (tooltips[cmd]+'.') + (style? '\nSet style '+(attr || 'default') + '.' : '');
      };
      var Triage = function(str,args){
        var func_onclick = (args.onclick)? this.prep_func_onclick(args.onclick) : null;
        var format_tgt = [];
//        var brs = [];
        var tag = args.embed? 'span' : 'div';
        var toggles = ['WATCH','UNWATCH','SHOW','UNSHOW','STICKY','UNSTICKY','ARC','ARC1','UNARC'];
        var pn = cnst.dom('<'+tag+(args.style? ' style="'+args.style+'"':'')+' class="'+pref.cpfx+(args.class || 'triage')+'"></'+tag+'>');
        this.pn  = pn;
        Tooltips.add_root(pn);
        var lines = str.replace(/\s*\/\/.*/mg,'').split('\n').filter(function(v){return v;});
//        for (var i=lines.length-1;i>=0;i--) if (lines[i]==='') lines.splice(i,1);
//        var triage_style_replace_list = (!brwsr.ff)? ['background','background-color'] : [];
        for (var i=0;i<lines.length;i++) {
          if (i!=0 && !args.menu) /*brs[brs.length] =*/ pn.appendChild(document.createElement('br'));
          var str = lines[i].replace(/,\s*$/,'').split(','); // allow last comma
          for (var j=0;j<str.length;j+=3) {
            if (str[j]==='SPC') {pn.appendChild(document.createTextNode(str[j+1])); continue;}
            if (str[j]==='EXPAND') {pn = cnst.doms_insertBefore(pn,'<a class="'+pref.cpfx+'button" name="SHOW" data-str="'+str[j+2]+'">'+str[j+1]+'</a><span style="display:none"></span>',null); continue;}
            var btn = args.embed? document.createElement('a') : cnst.dom('<button name="triage" type="button"></button>');
            btn.textContent = str[j+1];
            if (!args.embed && str[j]!=='MERGEC') btn.setAttribute('style','pointer-events:auto;'+(args.child_style || '')+str[j+2]);
            if (args.child_class) btn.setAttribute('class',pref.cpfx+args.child_class);
            btn.setAttribute('data-cmd',str[j]);
            if (str[j+2]) btn.setAttribute('data-attr',str[j+2]);
//            var triage_styles = (str[j+2])? str[j+2].split(';') : [];
//            for (var k=0;k<triage_styles.length;k++) {
//              var style_str = triage_styles[k].replace(/:.*/,'');
//              for (var m=0;m<triage_style_replace_list.length;m+=2) style_str = style_str.replace(triage_style_replace_list[m],triage_style_replace_list[m+1]);
//              btn.style[style_str] = triage_styles[k].replace(/[^:]*:/,'');
//            }
//            btn.onclick = triage_factory(i,j);
            if (func_onclick) btn.onclick = func_onclick;
            if (args.onmousewheel) btn['on'+brwsr.mousewheel] = args.onmousewheel;
//            if (pn.name) pref_func.tooltips.str[btn.name] = ((tooltips[str[j]])? tooltips[str[j]][0] +
//                                                                       ((tooltips[str[j]][1])? '\nSet style ' + ((str[j+2])? str[j+2] : 'default') + '.' : '') : '');
            if (args.menu) {
              var menu = document.createElement('div');
              menu.appendChild(btn);
              menu.appendChild(document.createElement('br'));
              btn = menu;
            }
            pn.appendChild(btn);
            if (toggles.indexOf(str[j])!=-1) format_tgt[format_tgt.length] = [btn, str[j]];
          }
        }
////        pn.onclick = function(e){  // also works, but CSS is the better.
////          e.preventDefault();
////          var evt = document.createEvent('MouseEvents');
////          evt.initUIEvent('click', false, true, window, 1);
////          threads[pn12_triage_thread][0].dispatchEvent(evt);
////        };
////        this.str = str;
//        this.pn  = pn;
        this.format_tgt = (format_tgt.length!==0)? format_tgt : null;
////        this.brs = brs;
//        Tooltips.add_root(pn);
      };
      Triage.prototype.destroy = function(){Tooltips.remove_root(this.pn);};
      Triage.prototype.tooltips = tooltips;
      Triage.prototype.et2tr = function(et){
        return {cmd: et.getAttribute('data-cmd'), attr: et.getAttribute('data-attr')||''};
      };
      Triage.prototype.prep_func_onclick = function(func){
        return function(e){
          e.stopPropagation();
          var tr = Triage.prototype.et2tr(e.target);
          if (tr.cmd && tr.cmd!=='SPC') func(tr.cmd, tr.attr, e.target);
        };
      };
      Triage.prototype.howto = 'How to describe triage:\n\n'+
        'Each line will be each line of triage.\n'+
        '1st column: command\n'+
      ['KILL','TIME','NONE','KILL_N','TIME_N','ATTR','WATCH','UNWATCH','STICKY','UNSTICKY','SHOW','UNSHOW','UNDO','GO','ARC1','ARC','UNARC','MERGEB','MERGE',/*'MERGEOP',*/'UNMERGE','UNDOMERGE','SPC','MENU','EXPAND'].map(
            function(v){return '  '+v+': '+tooltips[v]+(v==='UNDO'? '. Effective to commands above' : '') + '.\n';}).join('')+
        '2nd column: caption on the triage button.\n'+
        '3rd column: style applied to the thread.\n'+
        'Repeat these as you wish.\n'+
        'Sample loader will help you to understand.\n';
      Triage.prototype.MERGEE_button_html = ' <a class="'+pref.cpfx+'button" data-type="triage" data-cmd="MERGEE">[M]</a>';
      Triage.prototype.doms = null;
      return Triage;
//      var format_tgt = []; // working code
//      var toggles = ['WATCH','UNWATCH','SHOW','UNSHOW','STICKY','UNSTICKY','ARC','ARC1','UNARC'];
//      var pn = document.createElement('div');
//      pn.name = (args.name)? args.name : '';
////        pn_triage.style.position = 'absolute';
////        pn_triage.name = 'pn_catalog_triage';
//      pn.className = 'catalog_triage_parent';
//      var lines = str.replace(/\s*\/\/.*/mg,'').split('\n');
//      str = [];
//      for (var i=lines.length-1;i>=0;i--) if (lines[i]==='') lines.splice(i,1);
//      var triage_style_replace_list = (!brwsr.ff)? ['background','background-color'] : [];
//      for (var i=0;i<lines.length;i++) str[i] = lines[i].split(',');
//      for (var i=0;i<str.length;i++) {
//        for (var j=0;j<str[i].length;j+=3) {
//          var triage_button = document.createElement('button');
//          triage_button.innerHTML = str[i][j+1];
//          triage_button.name = pn.name + '('+i+','+j+')';
//          triage_button.className = 'catalog_triage_button';
//          triage_button.type = 'button';
//          var triage_styles = (str[i][j+2])? str[i][j+2].split(';') : [];
//          for (var k=0;k<triage_styles.length;k++) {
//            var style_str = triage_styles[k].replace(/:.*/,'');
//            for (var m=0;m<triage_style_replace_list.length;m+=2) style_str = style_str.replace(triage_style_replace_list[m],triage_style_replace_list[m+1]);
//            triage_button.style[style_str] = triage_styles[k].replace(/[^:]*:/,'');
//          }
////            triage_button.onclick = triage_factory(i,j);
//          triage_button.onclick = args.onclick;
//          pn.appendChild(triage_button);
//          if (args.wheelpatch) triage_button.onmousewheel = args.wheelpatch;
//          if (pn.name) pref_func.tooltips.str[triage_button.name] = ((tooltips[str[i][j]])? tooltips[str[i][j]][0] +
//                                                                     ((tooltips[str[i][j]][1])? '\nSet style ' + ((str[i][j+2])? str[i][j+2] : 'default') + '.' : '') : '');
//          if (toggles.indexOf(str[i][j])!=-1) format_tgt[format_tgt.length] = [triage_button, str[i][j]];
//        }
//        pn.appendChild(document.createElement('br'));
//      }
////        pn.onclick = function(e){  // also works, but CSS is the better.
////          e.preventDefault();
////          var evt = document.createEvent('MouseEvents');
////          evt.initUIEvent('click', false, true, window, 1);
////          threads[pn12_triage_thread][0].dispatchEvent(evt);
////        };
//      this.str = str;
//      this.pn  = pn;
//      this.format_tgt = (format_tgt.length!==0)? format_tgt : null;
  })();

  var timer_obj;
  var chart_obj;
//  var setting_obj;
//  var wafd = null;
  var catalog_obj;
  var cnst_obj = (function(){
    site.script_body = document.createElement('div');
    site.script_body.className = pref.script_prefix;
    site.script_body.innerHTML = '<div style="display:none"></div><div></div>';
    site.popup_body = site.script_body.childNodes[1];
    site.root_body.appendChild(site.script_body);
    var sf = site.features;
    var pf = pref.features;
    if (sf.postform && pf.postform && !(site.postform!=null || site2[site.nickname].postform.activation)) {
      var draft = document.createElement('div');
      draft.innerHTML = '<div><textarea style="height:5em;display:none" cols="20" name="' + pref.cpfx+'draft"></textarea><button name="' + pref.cpfx+'send" style="display:none">send</button></div>'; // <div> was added for tack_float
      site.script_body.appendChild(draft);
      draft = draft.childNodes[0];
      site.components['postform_comment'] = draft.childNodes[0];
      site.postform = site.components['postform_comment']; // redundant. need to be cleaned up.
      site.components['postform_submit'] = draft.childNodes[1];
    }
    var onclick_funcs = {
      '\u25c0CC': pref_func.settings.onchange_funcs.SHOW4,
      '\u25b6CC': pref_func.settings.onchange_funcs.HIDE4,
//      CC: function(e){cnst.show_hide(e.target.previousSibling);},
      entry: function(e){if (e.target.className===pref.cpfx+'button') onclick_funcs[e.target.textContent](e); e.stopPropagation();},
//      Settings: pref_func.settings.show_hide,
//      Watcher: catalog_obj && catalog_obj.show_hide,
//      Frame: func_frame,
//      Graph: chart_obj && chart_obj.make,
//      Draft: post_form_func,
//      PostForm: post_form_func,
//      debug: debug_func,
    };
    var stat_active = sf.graph && pf.graph && pref3.stats.use;
    var html = '[<span style="display:none">'+
      (sf.setting && pf.setting? make_button2('Settings', pref_func.settings.show_hide):'')+
      (sf.catalog && pf.catalog? make_button2('Watcher', (catalog_obj = make_catalog_obj()).show_hide)+
      (pref.catalog.embed && site.whereami==='catalog'? make_button2('Frame', site2[site.nickname].catalog_native_frame_prep()):''):'')+
      (chart_obj = make_chart_obj(stat_active), stat_active? make_button2('Graph', chart_obj.make):'')+
      (sf.postform && pf.postform && (site.postform!=null || site2[site.nickname].postform.activation)? make_button2(draft?'Draft':'PostForm', make_post_form_obj()):'')+
      (sf.debug && pf.debug? make_button2('debug', make_debug_obj()):'')+
      (sf.setting2 && pf.setting2? make_button2('settings2', make_setting_obj()):'')+
      make_button2('\u25b6CC') + '</span>'+make_button2('\u25c0CC')+']';
    if (site.settings) make_settings(site.settings, html);
    if (site.settings3) make_settings(site.settings3, html);
//    var pn_st = site.settings? site.settings.childNodes[1] : undefined;

    function make_button2(cap, func){
      if (func) onclick_funcs[cap] = func;
      if (site.settings || !func) return '<span class="'+pref.cpfx+'button">'+cap+'</span>'+(!func?'':' ');
      cnst.init('tile:get:left:tile:get:bottom:button:'+cap+':Show2:tile:set:left').onclick = func;
    }
    function make_settings(pn, html){
      pn.innerHTML = html;
      pn.onclick = onclick_funcs.entry;
    }
//    function make_button(str){
//      return cnst.init2('tile:get:left:tile:get:bottom:button:'+str+':Show2:tile:set:left',pn_st);
//    }
//    if (sf.setting && pf.setting) make_button('settings').addEventListener('click', pref_func.settings.show_hide, false);
//    if (sf.catalog && pf.catalog) {
//      catalog_obj = make_catalog_obj(pref[site.whereami] && pref[site.whereami].embed && pref.test_mode['114']? null : make_button('Watcher'));
////      catalog_obj = make_catalog_obj(pref[site.whereami] && pref[site.whereami].embed? (!pref.test_mode['114']? make_button('Watcher') : null) : make_button('Catalog'));
//      if (pref.catalog.embed && site.whereami==='catalog') var func_frame = site2[site.nickname].catalog_native_frame_prep(make_button('Frame'));
//    }
//    if (!pref.test_mode['94']) cnst.tile_set_left();
//    chart_obj = make_chart_obj((sf.graph && pf.graph && pref3.stats.use)? make_button('Graph') : null);
//    if (sf.setting2 && pf.setting2) setting_obj = make_setting_obj(make_button('settings2'));
//    if (sf.postform && pf.postform && (site.postform!=null || site2[site.nickname].postform.activation)) {
////      wafd = make_wafd();
//      var post_form_func = make_post_form_obj(make_button(draft?'Draft':'PostForm'));
//    }
//    if (sf.debug && pf.debug) {
//      var debug_func = make_debug_obj(make_button('debug'));
////  var pn_debug_out    = cnst.init('left:200px:bottom:50px:txt:debug_out');
//    }
    if (sf.page && pf.page) {
//      var pn0 = cnst.init('tile:get:left:tile:get:bottom:txt:init:Show2:tile:set:bottom');
      var pn0 = cnst.init('tile:get:left:tile:get:bottom:txt:init:Show2'); // , pn_st);
      timer_obj  = make_timer_obj(pn0);
    }
    cnst.tile_set_bottom();
    if (sf.catalog && pf.catalog) if (pref.catalog.board.board_tags_same) pref_func.settings.onchange_funcs['tag.same_tag_refresh']();

//    if (site.settings3) { // working code
//      var onclick_funcs = {
//        CC: function(e){cnst.show_hide(e.target.previousSibling);},
//        entry: function(e){if (e.target.tagName==='A') onclick_funcs[e.target.textContent](e);},
//        Settings: pref_func.settings.show_hide,
//        Watcher: catalog_obj && catalog_obj.show_hide,
//        Frame: func_frame,
//        Graph: chart_obj && chart_obj.make,
//        Draft: post_form_func,
//        PostForm: post_form_func,
//        debug: debug_func,
//      };
//      site.settings3.innerHTML = '<span style="float:right">[<span style="display:none">'+
//                                (sf.setting && pf.setting? '<a>Settings</a> ':'')+
//                                (sf.catalog && pf.catalog? '<a>Watcher</a> ':'')+
//                                (sf.catalog && pf.catalog && pref.catalog.embed && site.whereami==='catalog'? '<a>Frame</a> ':'')+
//                                (sf.graph && pf.graph && pref3.stats.use? '<a>Graph</a> ':'')+
//                                (sf.postform && pf.postform && (site.postform!=null || site2[site.nickname].postform.activation)? '<a>'+(draft?'Draft':'PostForm')+'</a> ':'')+
//                                (sf.debug && pf.debug? '<a>debug</a> ':'')+
//        '</span><a>CC</a>]</span>';
//      site.settings3.setAttribute('style','float:right;');
//      site.settings3.addEventListener('click',onclick_funcs.entry,false);
//    }
    
  })();

  function make_uip_tracker (){
//    var base_thread = site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'thread_html', {thread:site.no})[0];
////    site.thread = site.get_ops(document)[0];
////    var url = site.protocol + '//a.4cdn.org' + site.board +'thread/' + site.thread + '.json';
////    var url2= site.protocol + '//a.4cdn.org' + site.board +'catalog.json';
////    var url = site2[site.nickname].get_json_url_thread(site.board,site.thread);
//    var url2= site2[site.nickname].get_json_url_catalog(site.board);
//    var key = site.nickname + site.board + site.no;
    var post_uip = [];
    var last_updated = [0,1,1]; //no,posts,uips
    var posts_old = {};
    var uip_tracker_id;
    uip_check();
//    function uip_check(){http_req.get('uip',key,url,uip_show,false,false);}
    function uip_check(){
      uip_tracker_id = null;    
      site2[site.nickname].uip_check(uip_show, sage_detect);
    }
//    if (pref.debug_mode['0']) { // for debug
////      window.addEventListener('load',function(){console.log(new Date().toLocaleTimeString()+': load_event');},false); // can't get.
////      window.addEventListener('DOMSubtreeModified',function(){console.log(new Date().toLocaleTimeString()+': DOMSubtreeModified');},false); // get too much because of root.
//      site2[site.nickname].catalog_threads_in_page(document)[0].addEventListener('DOMSubtreeModified',function(){console.log(new Date().toLocaleTimeString()+': DOMSubtreeModified');},false); // ok.
//    }

    var observer = new MutationObserver(uip_show_from_doc);
    observer.observe(site.myself.pn, {childList: true});
//    function add_dom_event_listener(){
//      observer.observe(site.myself.pn, {childList: true});
//    }
//    function remove_dom_event_listener(){
//      observer.disconnect();
//    }
//    add_dom_event_listener();

    var auto_open = (function(){
      var threads = {};
      var threads_req = {};
      var threads_opened = {};
      var max_no = site.no;
      function auto_open_prep(key,value){
//        var dbt = key.split(','); // for universal
//        var ths = site2[dbt[0]].wrap_to_parse.get(value.response, dbt[0], dbt[1], dbt[3]);
//        for (var i=0;i<ths.length;i++) {
//          var th = ths[i];
        var obj = value.response; // 4chan or 8chan only
        for (var i=0;i<obj.length;i++) for (var j=0;j<obj[i].threads.length;j++) {
          var th = obj[i].threads[j];
          threads[th.no] = {sub:th.sub, name:th.name, com:th.com}; // for reducing memory consumption
          if (max_no<th.no) max_no = th.no;
        }
//        var obj = site2[site.nickname].parse_json_catalog(value.responseText);
//        for (var i=0;i<obj.length;i++) {
//          for (var j=0;j<obj[i].threads.length;j++) {
//            var no = obj[i].threads[j].no;
//            threads[no] = {};
//            threads[no].sub = obj[i].threads[j].sub;
//            threads[no].com = obj[i].threads[j].com;
//            if (max_no<no) max_no=no;
//          }
//        }
        auto_open_check();
      }
      function auto_open_check(){
        var rx = new RegExp(pref.uip_tracker.auto_open_kwd || '.*');
        for (var i in threads_req) {
          var th = threads[i];
          if (th && i>site.no && threads_opened[i]===undefined) {
            if (th.sub && th.sub.search(rx)!=-1 || th.name && th.name.search(rx)!=-1 || th.com && th.com.search(rx)!=-1) {
              window.open(site2[site.nickname].make_url4([site.nickname, site.board, i, 'thread_html'])[0], '_blank');
  //            window.open(site2[site.nickname].make_url3(site.board,i), '_blank');
              threads_opened[i] = i;
            }
            delete threads_req[i];
          } else if (max_no>i || threads_req[i]--<=0) delete threads_req[i];
        }
      }
      return function(posts, posts_new, posts_old){
        var i=posts.length-1;
        while (i>=1 && i>=pref.uip_tracker.auto_open_th && posts_old[posts[i].no]===undefined) i--; // don't check OP
        if (i==posts.length-1) return;
        var pn_test = document.createElement('span');
        while (++i<posts.length) {
          pn_test.innerHTML = posts[i].com;
          var pn_a = pn_test.getElementsByTagName('a');
          for (var j=0;j<pn_a.length;j++) {
            var tgt = pn_a[j].textContent.match(/>>([0-9]+)$/);
            if (!tgt) continue; else tgt = tgt[1];
            if (posts_new[tgt]!==undefined || posts_old[tgt]!==undefined) continue; // invoke redundant check if ahchor refers deleted posts
  //        var flag = true;
  //        var tgt = pn_a[j].textContent.substr(2);
  //        for (var k=0;k<nof_posts.length;k++) if (posts[k].no==tgt) {flag=false;break;}
  ////        if (flag) console.log('auto_opener: '+tgt);
            threads_req[tgt] = 3; // check 3 times for non-coherency not between catalog and threads
          }
        }
        auto_open_check();
        if (Object.keys(threads_req).length!=0) http_req.get('uip_auto',[site.nickname,site.board,site.no,'catalog_json'].join(),'',auto_open_prep);
      };
    })();
                       
    function uip_show(key,value){ // from http
      uip_show_2(key,value,true);
    }
    function uip_show_from_doc(e){ // from document
 //     uip_show_2(Date.now(),1200,'',false);
      if (post_uip.length!=0) uip_show_3();
      else if (time_req + interval > Date.now() + pref.uip_tracker.interval*1000) {
        clearTimeout(uip_tracker_id);
        waste_count = 0;
        uip_retry();
      }
    }
    function uip_show_3(){  // show
//      remove_dom_event_listener();
      for (var i=0;i<post_uip.length;i++) {
        var tgt_uip = post_uip[i];
        var no = tgt_uip[0];
        var tgt_post = site2[site.nickname].uip_tgt_post(no);
        if (tgt_post) {
          var pn_uip = document.createElement('span');
          pn_uip.innerHTML = ' (<span>'+tgt_uip[2]+'</span>)';
          if (tgt_uip[4]) pn_uip.childNodes[1].setAttribute('style',pref.uip_tracker.highlight_str);
          if (pref.uip_tracker.posts) {
            var pn_posts = document.createElement('span');
            pn_posts.innerHTML = '<span>'+ tgt_uip[1] +'</span>' + ((tgt_uip[5] && pref.uip_tracker.deletion.show)? '('+(
              pref.uip_tracker.deletion.link? tgt_uip[5].map(function(v){return '<a href="#p'+v+'" class="quotelink">&gt;&gt;'+v+'</a>';}) : tgt_uip[5]).join()+')' : '') + '/';
            if (tgt_uip[3]) pn_posts.childNodes[0].setAttribute('style',pref.uip_tracker.highlight_str);
            pn_uip.insertBefore(pn_posts,pn_uip.childNodes[1]);
          }
          if (tgt_uip[5] && (pref.uip_tracker.deletion.name || pref.uip_tracker.deletion.addName || pref.uip_tracker.deletion.post))
            for (var j=0;j<tgt_uip[5].length;j++) sage_annotate({no:tgt_uip[5][j]},pref.uip_tracker.deletion,true);
          if (pref.uip_tracker.sage.page) {
            var pn_page = document.createElement('span');
            if (!page_his[no]) page_his[no] = {pn:pn_page, his:[]};
            else {
              page_his[no].pn = pn_page;
              if (page_his[no].his.length>0) show_page(page_his[no]);
            }
            pn_uip.insertBefore(pn_page,pn_uip.lastChild);
          }
          var post_nums = site2[site.nickname].uip_post_num(tgt_post);
          post_nums[post_nums.length-1].appendChild(pn_uip);
          if (!pn_first && i==0) pn_first = [pn_uip, tgt_uip];
          post_uip.splice(i--,1);
        }
      }
//      sage_detect(); // require to merge this with catalog, format_html.update_draw runs after this and removes style of post.pn.
//      add_dom_event_listener();
    }
    function uip_show_2(key,value,from_http){ // from http and doc
//console.log(new Date(value.date).toLocaleTimeString()+', IN, '+from_http);
      waste_count++;
      if (value.status==200) {
//        var obj = JSON.parse(value.responseText);
        var posts = site2[site.nickname].parse_json_thread(('response' in value)? value.response : JSON.parse(value.responseText),from_http).posts;
        var nof_posts = posts.length;
        var uips = posts[nof_posts-1]['unique_ips'] || last_updated[2];
        var last_no = posts[nof_posts-1]['no'];
        if (last_updated[0]!=last_no || last_updated[1]!=nof_posts || last_updated[2]!=uips) {
if (pref.debug_mode['31']) console.log(new Date(value.date).toLocaleTimeString()+', '+last_no+', '+nof_posts+', '+uips);
          var posts_new = {};
          for (var i=0;i<posts.length;i++) posts_new[posts[i].no] = null;
          if (pref.uip_tracker.auto_open && last_updated[0]!=0) auto_open(posts, posts_new, posts_old);// last_updated[0]!=0 blocks initial trial.
          var post_hilight = (posts.length<last_updated[1] || posts[last_updated[1]-1]['no']!=last_updated[0]);
          if (pref.uip_tracker.deletion.show || pref.uip_tracker.deletion.name || pref.uip_tracker.deletion.addName || pref.uip_tracker.deletion.post) {
            var posts_deleted = [];
            for (var i in posts_old) if (posts_new[i]===undefined) posts_deleted[posts_deleted.length] = i;
if (pref.debug_mode['31'] && posts_deleted!=='') console.log('uip_deleted '+posts_deleted);
          }
          last_updated = [last_no,nof_posts,uips,post_hilight,last_updated[2]!=uips, (posts_deleted.length!=0)? posts_deleted : null];
          post_uip.push(last_updated);
          waste_count = 0; // pass here always at first.
          posts_old = posts_new;
        }
        uip_show_3();
        var p0 = posts[0];
      }
      if (value.status==404 || (p0 && (p0['archived'] || p0['closed']))) uip_tracker_destroy();
      else uip_retry();
    }
    var waste_count = 0;
    var interval = 0;
    var time_req = 0;
    function uip_retry(){
      if (pref.uip_tracker.on) {
        interval = (interval===0 || waste_count==0 || !pref.uip_tracker.adaptive)? ((pref.uip_tracker.interval>10)? pref.uip_tracker.interval : 10)*1000 // waste_count===0 always at initial. // interval===0 prevent from causing inifinite loop when !uip_first in annotate_from_catalog.
                 : (waste_count%8==0)? ((interval>=1800000)? 3600000 : interval*2)
                 : interval;
        uip_tracker_id = setTimeout(uip_check,interval);
        time_req = Date.now();
      } else uip_tracker_destroy();
    }
    var pn_first = null;
    function annotate_from_catalog(vals){
      if (vals[0]==='page') {
        var his = JSON.parse(vals[1]);
        for (var i in his) page_his[i] = {pn:null, his:his[i]};
        return;
      }
      if (vals[0]==='sage') {
        sage_detect({posts:vals.map(function(v){return {no:v, email:'sage'};})}); // posts[0].no==='sage', but posts[0] will be ignored.
        return;
      }
      var uip_first = (pn_first)? pn_first[1] : (post_uip.length!=0)? post_uip.splice(0,1)[0] : null;
      for (var i=0;i<vals.length;i+=3) post_uip[post_uip.length] = [vals[i], vals[i+1], vals[i+2], false, vals[i+1]==vals[i+2] || i>=3 && vals[i+2]!=vals[i-1], null];
      if (pref.uip_tracker.sage.page) {
        var nos = {};
        for (var i=0;i<post_uip.length;i++) nos[post_uip[i][0]] = null;
        for (var i in page_his) if (nos[i]===undefined) post_uip.splice(post_uip.length-1,0,[i, '?', '?', false, false, null]);
      }
      if (!uip_first) {
        last_updated = post_uip[post_uip.length-1]; // RACING CONDITION. this is faster than network access usually.
        pn_first = true;
      } else {
//        console.log('Hit: uip_tracker: racing condition');
        var uip_last = post_uip[post_uip.length-1];
        if (uip_last[0]!=uip_first[0] || uip_last[1]!=uip_first[1] || uip_last[2]!=uip_first[2])
          post_uip[post_uip.length] = uip_first.slice(0,3).concat(false, uip_last[2]!=uip_first[2], null);
        if (pn_first) pn_first[0].parentNode.removeChild(pn_first[0]);
      }
      uip_show_3();
    }
    var page_his = {};
    var sage_done = {};
    var sage_scheduled = {};
    var page_len = 15; // default value
    function sage_detect(th, page_len_in){
      if (page_len_in) page_len = page_len_in;
      for (var i in sage_scheduled) if (sage_annotate(sage_scheduled[i])) delete sage_scheduled[i];
      if (!th || !th.posts) return;
      for (var i=th.posts.length-1;i>=1;i--)
        if (th.posts[i].email==='sage' && sage_done[th.posts[i].no]===undefined) sage_annotate(th.posts[i]);
        else break;
      // patch for 4chan's bug.
      // 4chan sends corrupted data sometimes. Bump oerder and posts are NOT synchronized, posts may be dropped around before 1s from generation.
      // You can see this by comparing Last-Modified in http header with timestamp of posts. I saw 3 sec delay so far.
      // In this situation, threads in top orders are seen as overevaluated; just bumped, but there are no new posts.
      // This bug requires re-evaluation of sage, so I can't use stateful fast approach.
      if (pref.uip_tracker.sage.patch_bug) { // patch for 4chan's bug.
        for (var i=th.posts.length-1;i>=1;i--)
          if (th.posts[i].email!=='sage')
            if (sage_done[th.posts[i].no]===null || sage_scheduled[th.posts[i].no]) {
              if (pref.debug_mode['30']) console.log('OVERDETECTION: '+th.posts[i].no);
              if (sage_done[th.posts[i].no]===null) sage_annotate(th.posts[i], {name_str:'', addName_str:'OVERDETECTED_', post_str:'', __proto__:pref.uip_tracker.sage});
              delete sage_scheduled[th.posts[i].no];
              delete sage_done[th.posts[i].no];
            } else break;
      }
      if (pref.uip_tracker.sage.page && th.page!==undefined) {
        var last_no = th.posts[th.posts.length-1].no;
        var page = page2num(th.page);
        if (!page_his[last_no]) page_his[last_no] = {pn:null, his:[page]};
        else if (site2['4chan'].page_his_update(page_his[last_no].his, page)) if (page_his[last_no].pn) show_page(page_his[last_no]);
      }
    }
    function sage_annotate(post, pref_obj, deletion){
      var pn = site2[site.nickname].uip_tgt_post(post.no);
      if (pn) {
        var pn_name = site2[site.nickname].parse_funcs['post_html'].pn_name({pn:pn, __proto__:post});
        if (!pref_obj) pref_obj = pref.uip_tracker.sage;
        if (pref_obj.name) pn_name.setAttribute('style',pref_obj.name_str);
        if (pref_obj.addName) pn_name.textContent = pref_obj.addName_str + pn_name.textContent;
        if (pref_obj.post) pn.setAttribute('style',pref_obj.post_str);
        if (!deletion) sage_done[post.no] = null;
        return true;
      } else if (!deletion) sage_scheduled[post.no] = post;
    }
    function show_page(obj){
      obj.pn.textContent = '/'+obj.his.map(function(v){return Math.floor(v/page_len)+'.'+(v%page_len);}).join('\u2192');
    }
    function page2num(page){
      var ps = page.split('.');
      return parseInt(ps[0],10)*page_len+parseInt(ps[1],10);
    }
    function uip_tracker_destroy(){
      observer.disconnect();
//      remove_dom_event_listener();
      if (uip_tracker_id) clearTimeout(uip_tracker_id);
      uip_tracker = null;
//if (pref.debug_mode['0'] && uip_tracker===null) console.log('uip_tracker: stopped, '+value.status);
    }

    return {
      destroy: uip_tracker_destroy,
//      get_id: function(){return uip_tracker_id;},
      annotate_from_catalog: annotate_from_catalog
//      annotate_from_catalog: function(arg){setTimeout(annotate_from_catalog.bind(null, arg), 10000);} // for test
    }
  }
  var uip_tracker=null;
//  setTimeout(uip_tracker_init,2); // patch for LiveTag.
  function uip_tracker_init(){
    if (uip_tracker===null && site.features.uip_tracker && pref.features.uip_tracker && site.whereami==='thread' && pref.uip_tracker.on) uip_tracker = make_uip_tracker();
    else if (uip_tracker && !pref.uip_tracker.on) uip_tracker.destroy();
//    else if (!pref.uip_tracker.on) {
//      if (uip_tracker) clearTimeout(uip_tracker.get_id());
//      uip_tracker = null;
//    }
  }

  function make_thread_reader(){
//    site.no = site2[site.nickname].get_ops(document)[0]; // patch, will be changed to site.no.
//    var base_thread = site2[site.nickname].catalog_threads_in_page(document)[0];
    var name = site.nickname+site.board+site.no;
//    var dbt = comf.name2domainboardthread(name);
    var dummy = {sticky:null};
    var threads = {};
    threads[name] = [];
////////    threads[name][8]  = [0,0,0,0];
    threads[name][19] = liveTag.mems.init({domain:site.nickname, board:site.board, no:site.no}).wt; // [2]; // prepare data structures.
//    threads[name][19][0] = 0x000f0000; // start watching, init.
//    threads[name][19] = [1,0,0,0,null,true,-1,0,true];
    var favicon_obj = [];
////////    var buf_id = null;
////////    function updated_buf(){if (!buf_id) buf_id = setTimeout(updated,100);}
//    var posts = {};
////////    var nof_posts = 0;
//    var init = true;
////////    var time_lastpost = 0;
////////    var num_of_children = 0;
////////    var myself_th = {pn:base_thread, post_no_last:-1,
////////      domain:site.nickname, board:site.board, parse_funcs:site2[site.nickname].parse_funcs['thread_html'], __proto__:site4.parse_funcs_on_demand};
////////    var site_live = site.nickname + ((site2[site.nickname+'_live'])? '_live' : '');
////////    var myself_th = site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'thread_html',
////////                      {thread:site.myself, parse_funcs:site2[site_live].parse_funcs['thread_html']})[0];
//    var parse_obj = {domain:site.nickname, board:site.board,
//                     parse_funcs:site2[site_live].parse_funcs['thread_html'],
//                     __proto__:site4.parse_funcs_on_demand};
//    var myself_th = {pn:base_thread, __proto__:parse_obj};
//    var myself_th = {pn:base_thread, domain:site.nickname, board:site.board,
//                     parse_funcs:site2[site_live].parse_funcs['thread_html'],
//                     __proto__:site4.parse_funcs_on_demand};
////////    var regexp_anchor    = />>[0-9]+/g;
////////    var regexp_anchor_cb = />>>\/[0-9A-z_\+]+\/[0-9]+/g;
////////    var remake_own_posts = true;



    var own_posts_tracker = function(){
      var get_flag = true;
      function get_my_posts_no(){
        get_flag = true;
      }
      function update_own_posts(posts,init){
        if (posts.length==0) {get_flag = false; return;} // clearing get_flag for watching thread which may not have any new posts in lainchan, a conflict may occur in KC...  // length check for KC.
        if (get_flag) {
          var key = pref.script_prefix + '.own_posts.' + site.board + site.no;
          var own_posts = JSON.parse(localStorage[key] || "[]");
          var my_posts = [];
          if (!init) {
            my_posts[0] = posts[posts.length-1];
            own_posts[own_posts.length] = my_posts[0].no;
            localStorage[key] = JSON.stringify(own_posts);
          } else {
            var i = 0;
            var j = posts.length-1;
            while (i<own_posts.length) {
              while (j>0 && own_posts[i]>posts[j].no) j--;
              if (own_posts[i]===posts[j].no) my_posts[my_posts.length] = posts[j];
              i++;
            }
          }
          for (var i=0;i<my_posts.length;i++) {
            var pn_name = my_posts[i].parse_funcs_html.pn_name(my_posts[i]);
            if (pref.thread_reader.show_own_post_by==='plain') pn_name.parentNode.insertBefore(document.createTextNode(' (You)'), pn_name.nextSibling);
            else pn_name.innerHTML = pn_name.innerHTML + ' (You)';
          }
          get_flag = false;
        }
        for (var i=0;i<posts.length;i++) site2[site.nickname].check_reply.add_you(posts[i]);
      }
      function event_submit(){
        if (this.name==='post') get_my_posts_no();
      }
      return {
        update_own_posts: update_own_posts,
        get_my_posts_no: get_my_posts_no,
        event_submit: event_submit,
      }
    };
    if (pref.thread_reader.own_posts_tracker) {
      own_posts_tracker = own_posts_tracker();
      if (!site.components.postform_submit) window.addEventListener('submit', own_posts_tracker.event_submit, false); // doesn't work....
    } else {
      own_posts_tracker = null;
//      if (!site.components.postform_submit) window.removeEventListener('submit', own_posts_tracker.event_submit, false);
    }

////////    var ignore_old = pref.catalog_footer_ignore_my_own_posts; // patch for showing (You), don't discard my post...
////////    pref.catalog_footer_ignore_my_own_posts = false;
////////    updated(true);
////////    pref.catalog_footer_ignore_my_own_posts = ignore_old;
////////
//////////    threads[name][19][5] = false; // modified in check_reply
////////    comf.set_value_to_root(threads[name][19],'5',false);
////////    function remake_own_posts_flag(){remake_own_posts = true;}
////////    function add_event_to_submit(pn){pn.addEventListener('click', remake_own_posts_flag, false);}
////////    function remove_event_from_submit(pn){pn.addEventListener('click', remake_own_posts_flag, false);}
//    function add_event_to_submit(pn){pn.addEventListener('click', site2[site.nickname].check_reply.remake_own_posts, false);}
//    function remove_event_from_submit(pn){pn.addEventListener('click', site2[site.nickname].check_reply.remake_own_posts, false);}
    function add_event_to_submit(pn){
      pn.addEventListener('click', site2[site.nickname].check_reply.remake_own_posts, false);
      if (own_posts_tracker) pn.addEventListener('click', own_posts_tracker.get_my_posts_no, false);
    }
    function remove_event_from_submit(pn){
      pn.removeEventListener('click', site2[site.nickname].check_reply.remake_own_posts, false);
      if (own_posts_tracker) pn.removeEventListener('click', own_posts_tracker.get_my_posts_no, false);
    }
////////    function updated(init){
////////      buf_id = null;
//////////console.log('called');
////////      if (pref.thread_reader.check_num_of_children) {
////////        if (num_of_children>=myself_th.pn.childNodes.length) return; // KC doesn't work by this, because all of children are not posts.
////////          num_of_children = myself_th.pn.childNodes.length;
////////      }
////////////      myself_th.parse_funcs['pop_post_prep'](myself_th);
////////      site2[site.nickname].check_reply.check(myself_th, threads[name][19]);
////////      delete myself_th.posts;
////////////////      nof_posts += threads[name][19][7]; // patch
////////      for (var i=0;i<threads[name][19][4].length;i++)
////////        threads[name][19][4][i].pn.addEventListener('mouseover', favicon_check, false);
//////////console.log('length:'+threads[name][19][4].length);
////////

//////////      threads[name][19][1] = 0;
////////      threads[name][19][4] = [];
////////      var flag_first = true;
////////      var post_no_last_new = myself_th.post_no_last;
////////      myself_th.parse_funcs['pop_post_prep'](myself_th);
////////      while (1) {
////////        myself_th.post = myself_th.parse_funcs['pop_post'](myself_th);
////////        if (myself_th.post && myself_th.post.post_no>myself_th.post_no_last) {
////////      
//////////      for (var i=base_thread.childNodes.length-1;i>=0;i--) { // num of posts is changed by hover and inline.
//////////        var classname = base_thread.childNodes[i].className;
//////////        if (classname && classname.indexOf('post')!=-1 && classname.indexOf('reply')!=-1) { // remove popuped posts.
//////////          var id = base_thread.childNodes[i].id;
//////////          if (!(id in posts)) {
////////          if (flag_first) {
////////            post_no_last_new = myself_th.post.post_no;
////////            if (remake_own_posts) {
////////              site2[site.nickname].prep_own_posts(); // couldn't get an event from myself, so don't miss posts from my thread.
////////              remake_own_posts = false;
////////            }
////////            time_lastpost = myself_th.post.time;
////////            flag_first = false;
////////          }
////////          nof_posts++;
//////////          posts[id] = id;
//////////          var post = myself_th.post.pn; // temporal
////////          if (!init) {
//////////            var post = base_thread.childNodes[i];
//////////            var image = post.getElementsByClassName('post-image');
//////////            image = (image[0])? image[0].src : undefined;
//////////            var body = post.getElementsByClassName('body')[0].innerHTML;
////////            var to_me  = false;
////////            var anchors = regexp_anchor.exec(myself_th.post.com);
////////            if (anchors!==null) {
////////              for (var j=0;j<anchors.length;j++) {
////////                var tgt = anchors[j].substr(2);
////////                if (site3[site.nickname].own_posts[dbt[1]+tgt]===null) {to_me = true; break;}
////////            }}
////////            if (!to_me) {
////////              anchors = regexp_anchor_cb.exec(myself_th.post.com);
////////              if (anchors!==null) {
////////                for (var j=0;j<anchors.length;j++) {
////////                  var tgt = anchors[j].substr(3);
////////                  if (site3[site.nickname].own_posts[tgt]===null) {to_me = true; break;}
////////            }}}
//////////            var anchors = body.match(/&gt;&gt;[0-9]+/g);
//////////            if (anchors) {
//////////              for (var j=0;j<anchors.length;j++) {
//////////                var tgt = anchors[j].substr(8);
//////////                if (site3[site.nickname].own_posts[dbt[1]+tgt]===null) {to_me = true; break;}
//////////            }}
//////////            if (!to_me) {
//////////              anchors = body.match(/&gt;&gt;&gt;\/[0-9A-z_\+]+\/[0-9]+/g);
//////////              if (anchors) {
//////////                for (var j=0;j<anchors.length;j++) {
//////////                  var tgt = anchors[j].substr(12);
//////////                  if (site3[site.nickname].own_posts[tgt]===null) {to_me = true; break;}
//////////            }}}
////////            threads[name][19][4].unshift(
////////              {icon: myself_th.post.op_img_url,
////////               body: myself_th.post.com,
////////               time: myself_th.post.time,
//////////               icon: image,
//////////               body: body,
//////////               time: Date.parse(post.getElementsByTagName('time')[0].getAttribute('datetime')) - pref.localtime_offset*3600000,
////////               to_me: to_me,
////////               offsetTop: myself_th.post.pn.offsetTop});
////////            if (to_me) threads[name][19][1]++;
////////          }
////////          myself_th.post.pn.addEventListener('mouseover', favicon_check_event, false);
//////////            if (time_lastpost<threads[name][19][4][0].time) time_lastpost=threads[name][19][4][0].time;
//////////          } else break;
//////////        }
//////////      }
////////
////////        } else break;
////////      }
////////      myself_th.post_no_last = post_no_last_new;

////////
////////      if (!init) {
//////////      if (init) {init=false;buf_id=null;return;}
////////////////        threads[name][8][2]  = nof_posts;
//////////        for (var i=0;i<threads[name][19][4].length;i++) favicon_obj.push([threads[name][19][4][i].pn.offsetTop,threads[name][19][4][i]]);
//////////        for (var i=threads[name][19][4].length-1;i>=0;i--) favicon_obj.push([threads[name][19][4][i].pn.offsetTop,threads[name][19][4][i]]);
////////        for (var i=threads[name][19][4].length-1;i>=0;i--) favicon_obj.push(threads[name][19][4][i]);
//////////        threads[name][19][2] = nof_posts - favicon_obj.length;
////////        threads[name][19][2] = favicon_obj.length;
////////
//////////      if (!init) { // merge with mark_newer_posts. MIGHT CAUSE UNSTABLITY???
//////////        var nof_posts = site2[site.nickname].get_posts(document).length; // works well in static.
//////////        threads[name][8][2] = nof_posts;
//////////        site2[site.nickname].check_reply_to_me(name,dbt,threads[name][19], document, threads[name][8], dummy);
//////////        for (var i=0;i<threads[name][19][4].length;i++) favicon_obj.push([site2[site.nickname].get_post_offsetTop(document,nof_posts-1-i),threads[name][19][4][i]]);
////////
////////        if (threads[name][19][4].length!=0) notifier.changed(name, threads[name][19][4]);
////////      }
////////      if (own_posts_tracker && cataLog.embed_mode!=='thread') own_posts_tracker.update_own_posts(threads[name][19][4],init); // PATCH
//////////      if (own_posts_tracker) own_posts_tracker.update_own_posts(threads[name][19][4],init);
////////    }

    var time_jumped = site.myself.posts[site.myself.posts.length-1].time_tu;
    function mark_posts_from_parent(time){
      time_jumped = time;
      time /= site2[site.nickname].parse_funcs.thread_html.time_unit;
//      ignore_scroll = true;
      if (favicon_obj[0] && favicon_obj[0].time<time) { // not debugged this path, usually doesn't occur.
        var i = 1;
        while (i<favicon_obj.length && favicon_obj[i].time<=time) i++;
        favicon_obj_trim({target:favicon_obj[i-1].pn});
      } else {
        var site_live = site.nickname + ((site2[site.nickname+'_live'])? '_live' : '');
        var th = site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'thread_html',
                   {thread:site.no, parse_funcs:site2[site_live].parse_funcs['thread_html']})[0];
        var end = th.posts.length;
        if (favicon_obj[0]) while (end>0 && th.posts[end-1].time>=favicon_obj[0].time) end--;
        var i=end;
        while (i>0 && th.posts[i-1].time>time) i--; // th.posts[--i].pn.addEventListener('mouseover', favicon_obj_trim, false);
        favicon_obj = th.posts.slice(i,end).concat(favicon_obj);
      }
    }
    if (own_posts_tracker) own_posts_tracker.update_own_posts(site.myself.posts, true); // patch for init issue below.
    function updated(new_posts, init){ // called from catalog // this is NOT called with init, because catalog is made before thread_reader.
      for (var i=0;i<new_posts.length;i++) if (new_posts[i].time_tu>=time_jumped) favicon_obj[favicon_obj.length] = new_posts[i];
      favicon_obj.sort(function(a,b){return a.time_tu-b.time_tu;}); // for merge
//      for (var i=new_posts.length-1;i>=0;i--) {
////        new_posts[i].pn.addEventListener('mouseover', favicon_obj_trim, false);
//        favicon_obj.push(new_posts[i]);
//      }
      threads[name][19][1] = (threads[name][19][1]&0xffff0000) + favicon_obj.length;
//      if (new_posts.length!=0) notifier.changed(name, new_posts);
      if (own_posts_tracker) own_posts_tracker.update_own_posts(new_posts,init);
    }
//    function favicon_check_event(){
//      this.removeEventListener('mouseover', favicon_check_event, false);
//      favicon_check();
//    }
//    var ignore_scroll = false;
    var ref_height_checked = 0;
    window.addEventListener('resize',function(){ref_height_checked = 0;},false);
    function favicon_check(e){
//      if (this===window && ignore_scroll) {ignore_scroll = false; return;}
      if (favicon_obj.length==0) return;
      var ref_height = brwsr.document_body.scrollTop;
      if (ref_height<=ref_height_checked) return;
      else ref_height_checked = ref_height;
      ref_height += window.innerHeight; //  -50;
      favicon_obj_trim(e,ref_height);
    }
    function favicon_obj_trim(e,ref_height){
      var time;
      while (favicon_obj.length!=0) {
        if (ref_height && favicon_obj[0].pn.offsetTop + favicon_obj[0].pn.offsetHeight>ref_height) break;
        var tgt = favicon_obj.shift();
//        tgt.pn.removeEventListener('mouseover', favicon_obj_trim, false);
        time = tgt.time * site2[site.nickname].parse_funcs['post_json'].time_unit;
//        if (pref['thread'].mark_new_posts) site2['DEFAULT'].unmark_post(tgt.pn);
//        if (pref['thread'].mark_new_posts) tgt.pn.setAttribute('style','border:none');
//        if (e.currentTarget===tgt.pn) break;
      }
      if (time) {
        threads[name][19][1] = favicon_obj.length;
//        threads[name][19][0] = time;
        for (var i=0;i<favicon_obj.length;i++) if (favicon_obj[i].reply_to_me) threads[name][19][1]+=0x10000;
        if (pref.INST.testMode167 /*pref.test_mode['167']*/) time = site.myself.posts.length - favicon_obj.length;
        notifier.favicon.set(threads);
        if (pref.thread_reader.sync && window.opener) send_message('parent',['TRIAGE',[name,'TRACK',threads[name][19][1],false,time]]); // for tracking what you see
        else if (pClg) pClg.triage_multicast(name,'TRACK',threads[name][19][1],false,time);
      }
    }
//    function mark_newer_posts(time){ // working code
//      var retval = null;
//      var i=0;
//      while (i<favicon_obj.length && favicon_obj[i].time < time) i++;
//      if (i<favicon_obj.length) retval = favicon_obj[i].pn;
//      while (i<favicon_obj.length) {
//        favicon_obj[i].marked = true;
////        favicon_obj[i].pn.setAttribute('style','background:yellow');
//        favicon_obj[i].pn.setAttribute('style','border:2px solid red');
//        i++;
//      }
//      ignore_scroll = true;
//      return retval; // scroll will be done.
//    }

//    var triage;
    var btns_p = document.createElement('div');
//    btns_p.innerHTML =
//      '<div>'+
//        '<button name="thread_reader.buttons.B" type="button">B</button>'+
//        '<button name="thread_reader.buttons.UB" type="button">UB</button>'+
//        ((window.opener)? '<button name="thread_reader.buttons.X" type="button">X</button>'+
//                          '<button name="thread_reader.buttons.v" type="button">v</button>' : '')+
//      '</div>';
//    var btns = btns_p.getElementsByTagName('button');
//    btns['thread_reader.buttons.B'].onclick  = function(){comf.modify_bookmark(name,true)};
//    btns['thread_reader.buttons.UB'].onclick = function(){comf.modify_bookmark(name,false)};
//    if (window.opener) {
//      btns['thread_reader.buttons.X'].onclick = function(){triage_exe('KILL','');};
//      btns['thread_reader.buttons.v'].onclick = function(){triage_exe('TIME','');};
//    }
////    var th_link = document.getElementById('thread-links');
////    th_link.parentNode.insertBefore(buttons,th_link);
    var pn_ref = site.embed_to['bottom'](); // (typeof(site.embed_to['bottom'])==='function')? site.embed_to['bottom']() : site.embed_to['bottom'];
    pn_ref.parentNode.insertBefore(btns_p,pn_ref);
    btns_p.appendChild(new Triage('WATCH,Watch,,UNWATCH,UnWatch,',{onclick:triage_exe_tr}).pn);
    if (window.opener && pref.thread_reader.triage) btns_p.appendChild(new Triage(pref.catalog_triage_str,{onclick:triage_exe}).pn);
//    if (window.opener && pref.thread_reader.triage) {
////      var triage_all = comf.make_triage({onclick:triage_event, wheelpatch:false});
////      triage_str = triage_all.str;
////      th_link.parentNode.insertBefore(triage_all.pn,th_link);
//      triage = new Triage(pref.catalog_triage_str,{onclick:triage_exe});
//////      triage = new Triage(pref.catalog_triage_str,{onclick:triage_event, wheelpatch:false, name:'thread_reader_triage'});
//      btns_p.appendChild(triage.pn);
//    }
////    pref_func.tooltips.add_root(btns_p);

//    function triage_event(){
//      var flds = this.name.split(',');
//      var i = parseInt(flds[0].replace(/[^\(]*\(/,''),10);
//      var j = parseInt(flds[1].replace(/\).*/,''),10);
////      var i = parseInt(flds[0],10);
////      var j = parseInt(flds[1],10);
//      triage_exe(triage.str[i][j],triage.str[i][j+2]);
//    }
    function triage_exe_tr(cmd,attr){
      if (!pClg) catalog_obj.show_hide(null, 'thread');
      setTimeout(function(){pClg.triage_exe_broadcast(name,cmd,attr,true);},0); // embed mode uses setTimeout for interaction with native js.
//      if (pClg) pClg.triage_exe_broadcast(name,cmd,attr,true);
//      if (pref.thread_reader.triage_close) window.close();
    }
    function triage_exe(com0,com1){
      send_message('parent',['TRIAGE',[name,com0,com1,true, site2['DEFAULT'].check_reply.get_checked_time(threads[name][19])]]);
//      if (pref.thread_reader.triage_close) window.close();
    }

//    window.addEventListener('storage', site2[site.nickname].prep_own_posts_event, false); // can't catch events from this thread.
    if (pref.notify.favicon) window.addEventListener('scroll', favicon_check, false);
//    base_thread.addEventListener('DOMSubtreeModified',updated_buf,false);
//    base_thread.addEventListener('DOMNodeInserted',updated_buf,false);
////////    var observer = new MutationObserver(updated_buf);
////////    observer.observe(myself_th.pn, {childList: true});

//    threads[name][19][3] = --threads[name][19][0];threads[name][19][0]--;updated(); // debug
//    if (site.components.postform_submit) add_event_to_submit(site.components.postform_submit);
    function setup_submit(inst_submit, postform_submit){
      if (!inst_submit && pref.thread_reader.own_posts_tracker && postform_submit) {
        add_event_to_submit(postform_submit);
        inst_submit = postform_submit;
      } else if (inst_submit && (!pref.thread_reader.own_posts_tracker || !postform_submit)) {
        remove_event_from_submit(inst_submit);
        inst_submit = null;
      }
      return inst_submit;
    }
    var inst_submit  = setup_submit(null, site.components.postform_submit);
    var inst_submit2 = setup_submit(null, site.components.postform_submit2);
////////    window.addEventListener('storage', remake_own_posts_flag, false);
    window.addEventListener('storage', site2[site.nickname].check_reply.remake_own_posts, false);

    if (site.nickname==='4chan') (function(){ // popup youtube
      var tacks = new WeakMap();
      site2[site.nickname].catalog_get_native_area().addEventListener('click',function(e){
        var et = e.target;
        if (et.tagName==='A' && et.dataset.cmd=='embed') { //  && (!et.nextElementSibling || !et.nextElementSibling.classList.contains('stickyIcon'))) {
          var tack = tacks.get(et);
          if (!tack) observer.observe(et.parentNode, {childList: true});
          else {
            if (tack.parentNode) tack.remove(); // for racing condition
            else tacks.get(tack).remove();
            tacks.delete(tack);
            tacks.delete(et);
          }
        }
      },false);
      var func_float = function(e){cnst.tack_float_nSblgs(e,null,true);};
      var func_embed = function(e){
        var obj = cnst.tack_dock_nSblgs(e,true);
        if (e.target.textContent==='X') obj.tack.previousSibling.previousSibling.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window}));
        else {
          obj.pn.style.float = null;
          obj.pn.style.display = null;
          obj.pn.style.overflow = null;
          obj.pn.classList.remove(pref.cpfx+'titleBar');
        }
      };
      var observer = new MutationObserver(function(e){
        var tgt = e[0].addedNodes[0];
        var tack = cnst.set_tack_float2(tgt, func_float, func_embed);
//        var tack = site2[site.nickname].make_tack();
//        cnst.set_tack_float(tack, func_float, func_embed);
////        tack.onclick = cnst.tack_float_nSblgs;
        tacks.set(tgt.previousSibling.previousElementSibling,tack);
        tacks.set(tack,tgt);
//        tgt.parentNode.insertBefore(tack,tgt);
        observer.disconnect();
      });
    })();

    var observer_IDinfo = null;
    if (pref.IDinfo.use) (function(){ // 4chan and 8chan
      var IDop = site.myself.parse_funcs.id(site.myself);
      var pfID = !pref.IDinfo.auto || IDop;
      var pfFlag = !pref.IDinfo.auto || site.myself.posts[0].flag;
//      if (!pfID && !pfFlag && !pref.IDinfo.trip && !pref.IDinfo.name) return;
//      var IDs = {};
//      var flags = {};
//      var flagIDs = {};
//      var trips = {};
//      var names = {};
//      var nameTrips = {};
      var ID2op = site.myself.parse_funcs.id2(site.myself);
      var pfID2 = !pref.IDinfo.auto || ID2op;
//      var ID2s = {};
      var data = {ID:{}, ID2:{}, flag:{}, name:{}, trip:{}, flagIDs:{}, nameTrips:{}, tripNames:{}};
      function entry(posts, pf){
        if (pf.ID.use && pfID) show(data.ID, posts, 'pn_id', 'id', pref.IDinfo.ID, null, null, pref.IDinfo.ID.op && IDop);
        if (pf.ID2.use && pfID2) show(data.ID2, posts, 'pn_id2', 'id2', pref.IDinfo.ID2, null, null, pref.IDinfo.ID2.op && ID2op);
        if (pf.flag.use && pfFlag) show(data.flag, posts, 'flag', 'country', pref.IDinfo.flag, pref.IDinfo.flag.ID && data.flagIDs, 'id');
        if (pf.trip.use) show(data.trip, posts, 'pn_trip', 'trip', pref.IDinfo.trip, pref.IDinfo.trip.name && data.tripNames, 'name');
        if (pf.name.use) show(data.name, posts, 'pn_name', 'name', pref.IDinfo.name, pref.IDinfo.name.trip && data.nameTrips, 'trip');
        /*if (posts)*/ for (var i=0;i<posts.length;i++) done.add(posts[i].no);
      }
      function show(obj, posts, prop_pn, prop, pf, combo, combo_prop, show_IDop){
        var updated = {};
        /*if (posts)*/ for (var i=0;i<posts.length;i++) {
          var post = posts[i];
          var pn = post[prop_pn];
          if (pn) {
            var post_prop = post[prop];
            prep(obj, post_prop, pn, updated);
            if (combo && post[combo_prop]) {
              if (combo[post_prop]==undefined) combo[post_prop] = {};
              combo[post_prop][post[combo_prop]] = null;
            }
          }
        } // else for (var i in obj) updated[i] = 0;
        show_pn(obj, updated, pref_func.sanitize(pf.h), pref_func.sanitize(pf.t), pf.nof, combo, show_IDop);
      }
      function prep(obj, key, pn, updated){
        var val = {pn:pn, c:Object.keys(obj).length+(!obj[key]?1:0)};
        if (!obj[key]) {
          val.m = true; // mark
          obj[key] = [val];
        } else obj[key].push(val);
        if (updated[key]===undefined) updated[key] = obj[key].length-1;
      }
      function show_pn(obj, updated, header, trailer, total, obj_combo, show_op){
        for (var key in updated) {
          var combo = obj_combo && obj_combo[key] && Object.keys(obj_combo[key]).length || 0;
          var op = show_op && show_op===key? 'OP:' : '';
          var pns = obj[key];
          for (var i=0;i<pns.length;i++) {
            var str = header + op + (i+1)+'/'+pns.length+(combo? '/'+combo:'')+(total? '/':trailer);
//            var str = op + (i+1)+'/'+pns.length+(combo? '/'+combo:'')+(total? '/':trailer);
            if (i<updated[key]) pns[i].pn.textContent = str;
            else {
//              if (header) pns[i].pn.parentNode.insertBefore(document.createTextNode(header), pns[i].pn);
              var pn = document.createElement('span');
              pn.innerHTML = str+(total? '<span'+(pns[i].m?' style="color:red"':'')+'>'+pns[i].c+'</span>'+trailer:'');
              pns[i].pn = pns[i].pn.parentNode.insertBefore(pn, pns[i].pn.nextSibling).firstChild;
            }
          }
        }
      }
      var done = new Set(); // for kwd search
      entry(site.myself.posts, pref.IDinfo);
      var proto = site2['DEFAULT'].wrap_to_parse.prep_pfunc(site.nickname, site.board, 'post_html');
      var observer = new MutationObserver(function(e){
        var posts = [];
        for (var i=0;i<e.length;i++) if (e[i].addedNodes) for (var j=0;j<e[i].addedNodes.length;j++) {
          var post = {pn:e[i].addedNodes[j], __proto__:proto};
//          try { // for merge footer's being added
          if (post.no && !done.has(post.no)) posts[posts.length] = post; // 'post.no &&' for 4chan's summary(expander), it hits observation criteria.
//          } catch (e){
//            console.log(e);
//          }
        }
        if (posts.length>0) entry(posts, pref.IDinfo);
//        for (var i=0;i<e.length;i++) if (e[i].addedNodes) entry(Array.prototype.map.call(e[i].addedNodes, function(v){return {pn:v};}));
      });
      observer_IDinfo = {
        observe: function(){observer.observe(site.myself.pn, {childList: true})},
        disconnect: function(){observer.disconnect();}
      };
      observer_IDinfo.observe();
      function redraw(e,[,prop]){
        var tgt = {ID:{}, ID2:{}, flag:{}, trip:{}, name:{}};
        tgt[prop].use = true;
        remove_pn(data[prop]);
        data[prop] = {};
        if (pref.IDinfo[prop].use) entry(site.myself.posts, tgt);
//        if (pfID && (pref.IDinfo.ID ^ src==='IDinfo.ID')) remove_pn(IDs); // working code
//        if (pfID2 && pref.IDinfo.ID2 ^ src==='IDinfo.ID2') remove_pn(ID2s, true);
//        if (pfFlag && (pref.IDinfo.flag ^ src==='IDinfo.flag')) remove_pn(flags, true);
//        if (pref.IDinfo.name ^ src==='IDinfo.name') remove_pn(names, true);
//        if (pref.IDinfo.trip ^ src==='IDinfo.trip') remove_pn(trips, true);
//        entry(null);
      }
      function remove_pn(obj){
        for (var i in obj) for (var j=0;j<obj[i].length;j++) obj[i][j].pn.parentNode.remove();
//        for (var i in obj) for (var j=0;j<obj[i].length;j++) {
//          var pn = obj[i][j].pn.parentNode;
//          obj[i][j].pn = pn.previousSibling;
////          if (header) pn.parentNode.removeChild(pn.previousSibling.previousSibling);
//          pn.parentNode.removeChild(pn);
//        }
      }
      pref_func.settings.onchange_funcs['IDinfo.*.*W/'] = redraw;
    })();

    return {
//      mark_newer_posts: mark_newer_posts,
      destroy : function(){ // destroy
        Tooltips.remove_root(btns_p);
//        base_thread.removeEventListener('DOMSubtreeModified',updated_buf,false);
//        base_thread.removeEventListener('DOMNodeInserted',updated_buf,false);
        if (observer_IDinfo) observer_IDinfo.disconnect();
////////        window.removeEventListener('storage', remake_own_posts_flag, false);
        window.removeEventListener('storage', site2[site.nickname].check_reply.remake_own_posts, false);
        if (inst_submit ) remove_event_from_submit(inst_submit);
        if (inst_submit2) remove_event_from_submit(inst_submit2);
        return null;
      },
      setup_submit2: function(){inst_submit2 = setup_submit(inst_submit2, site.components.postform_submit2);},
      own_posts_tracker: own_posts_tracker,
      updated: updated,
      mark_posts_from_parent: mark_posts_from_parent,
//      observer_IDinfo: observer_IDinfo,
    };
  }
  function thread_reader_init(){
    if (common_obj.thread_reader===null && site.features.thread_reader && pref.features.thread_reader && site.whereami==='thread' && pref.thread_reader.use) common_obj.thread_reader = make_thread_reader();
    else if (!pref.thread_reader.use && common_obj.thread_reader) common_obj.thread_reader = common_obj.thread_reader.destroy();
  }
//  setTimeout(thread_reader_init,1); // call after making liveTag, and for safety, used setTimeout. (thread_reader_init contains DOM access, it may fail).



  var liveTag = {
    pn : null,
    pn_summary : null,
//    pn_filter_rexp : null,
    active: {pk:0, in:0, ex:0},
    actives: Object.create(null),
    tags_ci: null, // refers tags.__proto__
    tags : Object.create(Object.create(null)),
                                // tags[TAG] = {key:, num;, mems:, cbx:{pk:, in:, ex:}, (tgts:,) pn:, pn_num:, // summary tree
                                //              ur:};
//  mems : Object.create(null), // member tree : mems[domain][boards][no] = [[[],{},' '], [[],{}], threads_name_19];
                                //   [0]:fixed, [1]:bumped, [x][0]:tags, [x][1]:keys, [0][2]:string,
                                //   [2]:threads[name][19]
                                //     [0]:time_of_checked, [1]:num_of_unread_replies_TO_ME, [2]:num_of_unread_replies,
                                //     [3]:time_of_checked_time_internal, [4]:args_for_desktop_notification, [5]:init,
                                //     [6]:time_of_checked_old, [7]:num_of_checked_posts, [8]:inital_loop,
                                //     [9]:tag_temp, [10]:num_of_posts
    mems : Object.create(null, {  //  CAUTION. USED ALWAYS EVEN IF pref.liveTag.use===false
      getFromName: {value: function(name) {
        var dbt = comf.fullname2dbt(name);
        return (dbt[2])? this[dbt[0]][dbt[1]][dbt[2]] : this[dbt[0]][dbt[1]];}},
//      getFromDBT: {value: function(d,b,t){
//        return (!this[d] || !this[d][b] || !this[d][b][t])? null : this[d][b][t];}}, // read for trial, getFromName assumes that target must exist.
      init: {value: (function(){
        function prep_domain(th, _this){
////        if (this[th.domain]===undefined) this[th.domain] = Object.create(null, {proto:{value:Object.create(this.acc, {domain:{value:th.domain}})}, // working code.
////                                                                                pfunc:{value:Object.create(null)}});
////        if (!th.board) return this[th.domain];
////
////        if (this[th.domain][th.board]===undefined) {
////          this[th.domain][th.board] = Object.create(this[th.domain].proto,
////                                                     {proto:{value:Object.create(this[th.domain].proto,
////                                                                                  {board:{value:th.board},
////                                                                                   btag:{value:'#'+th.board.substr(1,th.board.length-2)}})},
////                                                      nr:{value:0, writable:true}, nrtm:{value:0, writable:true}, nr_dirty:{value:false, writable:true},
////                                                      u:{value:0, writable:true}, board:{value:th.board}, read_time:{value:0, writable:true},
////                                                     });

          var ldm =         Object.create(Object.create(_this.acc, {domain:{value:th.domain}}),
                                          {p:{value:Object.create(null)}, // pool of parse_funcs
//                                         u:{value:0, writable:true}, // for 'add_domain'
                                          });
          _this[th.domain] = ldm;
          if (site2[th.domain].lth_init) site2[th.domain].lth_init(th, ldm); // liveTag.mems[th.domain] is used in this.
        }
        function prep_board(th, ldm){
          var btag = '#'+th.board.substr(1,th.board.length-2);
          if (liveTag.tags[btag] && liveTag.tags[btag].ref_tag === btag) btag = liveTag.tags[btag].ref_tag; // force to use the same reference.
          var ldb =                   Object.create(Object.create(Object.getPrototypeOf(ldm),
                                                                  {board:{value:th.board},
                                                                   btag:{value:btag, writable:true}}),
                                                    {nr:{value:0, writable:true},
                                                     nrtm:{value:0, writable:true},
//                                                     nr_dirty:{value:false, writable:true},
                                                     u:{value:0, writable:true},
//                                                     read_time:{value:0, writable:true},
                                                     o:{value:null, writable:true}, // for 'add_domain' // order
//                                                     f:{value:0, writable:true}, // for 'add_domain'
                                                     p:{value:{}, configurable:true, writable:true}, // pool of parse_funcs
                                                     pgs:{value:null, writable:true}, // for 'add_domain' // max pages
                                                     d:{value:0, writable:true}, // flags
                                                     // s:   for statistics
                                                     // sr:  for statistics
                                                     // srt: for statistics
                                                     // st:  for statistics(passive)
                                                     // vm: virtual board members
                                                    });
          if (pref.liveTag.inherit_board_name) {
            liveTag.update_tags_in_th_sub([], {}, [btag], {}, 1, null, 0, ldb);
//            liveTag.update_boardlist_1(btag); // for passive virtual boarding. // BUG. don't update new tags.
            liveTag.update_pn_buf.delayed_do();
          }
          var btag2 = site2[th.domain].btags && site2[th.domain].btags[th.board.slice(1,-1)];
          if (btag2) Object.defineProperty(Object.getPrototypeOf(ldb),'btag2',{value:btag2, writable:true});
          return ldb;
        }
        function prep_thread(th, ldb){
//          this[th.domain][th.board][th.no] = {1:undefined, 2:[0,0,0,0,null,true,-2, 0, true, [], 1],  // init [10] as 1.// working code.
//                                              u:0, no:th.no,
//                                              __proto__:Object.getPrototypeOf(this[th.domain][th.board])};
//          var bd = this[th.domain][th.board];
          return     Object.create(Object.getPrototypeOf(ldb),{ // 28/324 for 1,2,u,no,tl.
                                                                // 28/240 for 1,2,u,no.
                                                                // 28/236 for 1,2,u,no,tl. (1===null)
                                                                // 28/152 for   2,u,no.
            '1':{value:undefined, writable:true},
            '2':{value:[0x00830000, 0, 0]}, // 16/48 Bytes each. (header/all=header+contents)
////                                               '2':{value:Object.create(this.watch,{ // 4,5,7,9 are shared for reducing memory consumption. // working code. // 28/116 Bytes each.
////                                                 '0':{value:0x00010000, writable:true},
////                                                 '1':{value:0, writable:true},
////                                                 '2':{value:0, writable:true},
//////                                                 '3':{value:0, writable:true},
//////                                                 '6':{value:-2, writable:true},
//////                                                 '8':{value:true, writable:true},
//////                                                 '10':{value:-1, writable:true}
////                                               })},
            u:{value:0, writable:true}, // require 32Bytes/instance, 2.5% memory increase/instance.
            no:{value:parseInt(th.no,10)},
            // tl: locked tag data
            // s: for statistics
            // th:  thread data, aka th.
            // _th:  patch for 2ch.
            // ta:  live thread, 'posts' contains all live posts.
            // pd:  posts_deleted
            // ed_p: editing posts
            // ed_t: editing tags
            // ed_u: update timestamp for editing posts,
            // sID: stats of IDs
            // sg: saged posts
            // pgh: page history
            // mh: merge hop count (not used now)
            // q: quoted posts
            // hp: hidden post(manually)
          });
//          var btag = bd[th.no].btag; // redundant
//          liveTag.key_dirty[(pref.liveTag.ci)? btag.toLowerCase() : btag] = null;
//          var btag2 = bd[th.no].btag2;
//          if (btag2) for (var i=0;i<btag2.length;i++) liveTag.key_dirty[(pref.liveTag.ci)? btag2[i].toLowerCase() : btag2[i]] = null;
        }
        return function(th) {
          var ldm = this[th.domain];
          if (ldm===undefined) {prep_domain(th, this); ldm = this[th.domain];} // this[th.domain] may be wrapped in lth_init.
          if (!th.board) return ldm;
          var ldb = ldm[th.board];
          if (ldb===undefined) ldm[th.board] = ldb = prep_board(th,ldm);
          if (!th.no) return ldb;
          var lth = ldb[th.no];
          return (lth===undefined)? (ldb[th.no] = prep_thread(th,ldb)) : lth;
//          if (this[th.domain]===undefined) prep_domain.call(this,th);
//          if (!th.board) return this[th.domain];
//          if (this[th.domain][th.board]===undefined) prep_board.call(this,th);
//          if (!th.no) return this[th.domain][th.board];
//          if (this[th.domain][th.board][th.no]===undefined) prep_thread.call(this,th);
//          return this[th.domain][th.board][th.no];
        }
      })()},
//      watch: {value: Object.create(null, {  //'1':{value:undefined, writable:true, configurable:true}, // default value of 1.
//                                          '4':{value:null, writable:true},
//                                          '5':{value:true, writable:true},
//                                          '7':{value:0, writable:true},
//                                          '9':{value:[], writable:true},
//                                         })},
      acc: {value: Object.create(null, {  // to reduce memory consumption.
        0: { // TAGS MUST BE UPDATED BY REPLACING INSTEAD OF SPLICING because Footer takes stateful approach at test_mode['119'] // test_mode['119'] was discarded, it doesn't work with multiple footers. see Footer.
////          get: function() {return (pref.liveTag.inherit_board_name && pref.liveTag.lock_board_name)? [this.btag] : null;}, // working code.
////          set: function(val) {if (!pref.liveTag.inherit_board_name || !pref.liveTag.lock_board_name || val.length>=2) // inherit and lock ensures val.length>=1
////                 Object.defineProperty(this,'0',{value:val, enumerable:true, configurable:true, writable:true}); // works.
//////                 this.mems[this.domain][this.board][this.no]  = [val, this[1], this[2]];
////               },
////        },
          get: function() {
            var tag = this.btags;
            if (this.tl) tag = (tag)? tag.concat(this.tl) : this.tl;
            return tag || [];
          },
          set: function(val) {
            var ref = this.btags;
            if (ref) {
              if (pref.liveTag.ci) ref = ref.map(function(v){return v.toLowerCase();});
//              if (pref.liveTag.ci) for (var i=0;i<ref.length;i++) ref[i] = ref[i].toLowerCase(); // BUG, ref may original, not shallow copy.
              for (var i=0;i<val.length;i++) if (ref.indexOf((pref.liveTag.ci)? val[i].toLowerCase() : val[i])!=-1) val.splice(i--,1);
            }
            if (val.length>0) this.tl = val;
            else if (this.tl!==undefined) delete this.tl;
          },
        },
        t: {get: function(){return this[1];}}, // TEMPORAL
        t0: {get: function(){return this[0];}, set: function(v){this[0] = v;}}, // TEST PATCH, TO BE REMOVED
        t1: {get: function(){return this[1];}, set: function(v){this[1] = v;}}, // TEST PATCH, TO BE REMOVED
        wt: {get: function(){return this[2];}, set: function(v){this[2] = v;}}, // TEST PATCH, TO BE REMOVED
        key: {get: function(){return this.domain + this.board + ((this.no)? this.no : '');}},
//        tag: {get: function(){return (this[0] || []).concat(this[1] || []);}},
        tags:{get: function(){return (this[0] || []).concat(this[1] || []);}}, // renaming function. use this later.
//        tag: {get: function(){
//          var tag0 = this[0];
//          var tag1 = this[1];
//          return (tag0 && tag1)? tag0.concat(tag1) : (tag0)? tag0 : (tag1)? tag1 : null;
//        }},
        btags: {get: function(){
          var tags = (pref.liveTag.inherit_board_name && pref.liveTag.lock_board_name)? [this.btag] : null;
          return (this.btag2 && pref.liveTag.inherit_board_tags && pref.liveTag.lock_board_tags)? (tags? tags.concat(this.btag2) : this.btag2) : tags;}},
        watched:  {get: function(){return  this[2][0]&0x000c0000;}}, // see 'check_reply'
        watched_p:{get: function(){return  this[2][0]&0x0c0c0000;}}, // watching including passive.
        time_checked: {get: function(){return  this[2][2];}},
        archived: {get: function(){return (this[2][0]&0x00700000) >> 20;},
                   set: function(v){this[2][0] = this[2][0] & 0xff8fffff | ((v&0x07)<<20);}},
        posts_saved:{get: function(){return (this[2][0]&0x02000000) >> 25;},
                     set: function(v){this[2][0] = this[2][0] & 0xfdffffff | ((v&0x01)<<25);}},
        force_ar: {get: function(){return (this[2][0]&0x00500000) >> 20;}}, // init or rescan
        rescan_dp:{get: function(){return (this[2][0]&0x01000000) >> 24;},
                   set: function(v){this[2][0] = this[2][0] & 0xfeffffff | ((v&0x01)<<24);}},
        nrtm:     {get: function(){return (this[2][1]) >> 16;}},
        nr:       {get: function(){return  this[2][1]&0x0000ffff;}, // set directly in check_reply.check_1 and thread_reader.favicon_obj_trim
                   set: function(v){this[2][1] = (this[2][1]&0xffff0000)+v;}}, // setter for test_mode['166']
        nof_posts:{get: function(){return  this[2][0]&0x0000ffff ;},
                   set: function(v){this[2][0] = (this[2][0] & 0xffff0000) + v;}},

        LS_synced: {get: function(){return this.d&0x00000001;},
                    set: function(v){this.d = (this.d&0xfffffffe) | (v&0x01);}},
        IDB_synced:{get: function(){return (this.d&0x00000002) >> 1;},
                    set: function(v){this.d = (this.d&0xfffffffd) | ((v&0x01)<<1);}},
//        q: {get: function(){ // meaningless. always added by checking lth.q.waiting in callback2
//          var obj = this.no>1000000? Object.create(this) : Object.create({0:undefined, 1:undefined, 2:undefined, __proto__:this}); // PATCH before removing [0:2] // CYCLIC REFERENCE.
//          return Object.defineProperty(this,'q',{value:obj, writable:true, enumerable:true, configurable:true}).q;}},
      })}
    }),
//    mems_obj_accessors: { // to reduce memory consumption.
//      get 0() {return (this._btag)? ((this._0)? [this.btag].concat(this._0) : [this.btag]) : this._0;}, // _0 is null at initial, and will never back to null after once promoted to [].
//      set 0(val) {if (val[0]===this.btag) {
//                     this._btag = true;
//                     val.shift();
//                   } else this._btag = false;
//                   this._0 = (val.length!=0)? val : null;
//                 }
//    },
    postprocess_board_add_btag: function(tags,bd){
      for (var j=0;j<tags.length;j++) {
        tags[j] = '#'+tags[j].replace(/[&<>'"#]/g,'');
        if (tags[j].length>pref.liveTag.maxstr) tags.splice(j--,1);
      }
      var flag = bd.btag2 && bd.btag2.length===tags.length;
      if (flag) for (var j=0;j<tags.length;j++) if (tags[j]!==bd.btag2[j]) {flag = false;break;}
      if (!flag) {
        for (var j=0;j<tags.length;j++) if (liveTag.tags[tags[j]] && liveTag.tags[tags[j]].ref_tag===tags[j]) tags[j] = liveTag.tags[tags[j]].ref_tag; // force to use the same pointer to reduce memory consumption.
        Object.defineProperty(Object.getPrototypeOf(bd),'btag2',{value:tags, writable:true});
        if (pref.liveTag.inherit_board_tags) {
          liveTag.inherit_board_tags_changed_1(tags, bd);
//          for (var j=0;j<tags.length;j++) liveTag.update_boardlist_1(tags[j]); // for passive virtual boarding. // BUG. don't update new tags.
//          for (var j=0;j<tags.length;j++) liveTag.update_pn_buf.delayed_do(); // called in liveTag.inherit_board_tags_changed_1
        }
      }
    },
    retag: function(lth, scan){ // retag IS NOT a full function, which tags are extracted from OP is not stored.
      if (scan) {
        scan.list_nup.add_scan(lth);
        lth.wt[0] |= 0x00020000;
      }
      var th = {com:lth.tags.join(' '), parse_funcs:{type_com:'txt'}, __proto__:lth}; // patch
      liveTag.prep_tags(th, true);
      if (cataLog.threads[lth.key]) cataLog.Footer.update_force(lth.key,null,lth);
    },
    retag_db: DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(function(){gClg && gClg.FilterChanged();}, 10)), // 'gClg &&' is patch for 8chan boards' index(8kun.top/index.html)
    inherit_board_name_changed: function(){
      for (var d in this.mems) for (var b in this.mems[d]) this.inherit_board_tags_changed_1([this.mems[d][b].btag], this.mems[d][b], !pref.liveTag.inherit_board_name);
////      if (!pref.liveTag.inherit_board_name) this.delete_tags_buffered();
    },
    inherit_board_tags_changed: function(){
      for (var d in this.mems) for (var b in this.mems[d]) if (this.mems[d][b].btag2) this.inherit_board_tags_changed_1(this.mems[d][b].btag2, this.mems[d][b], !pref.liveTag.inherit_board_tags);
////      if (!pref.liveTag.inherit_board_tags) this.delete_tags_buffered();
    },
    inherit_board_tags_changed_1: function(tags, bd, remove){
      for (var i=0;i<tags.length;i++) this.retag_in_th_sub(tags[i], bd, remove); // must before 'this.retag'.
      for (var j in bd) {
        var th_key = bd.domain + bd.board + bd[j].no;
        this.retag(bd[j], remove && bd[j].tags.length >= pref.liveTag.max);
      }
      this.retag_db();
      this.update_pn_buf.delayed_do();
    },
    ex_list_changed: function(){ // on demand remove only. When you remove a piece of list, reload is required.
      if (pref.liveTag.ex_list) {
        for (var d in this.mems) for (var b in this.mems[d]) for (var t in this.mems[d][b]) {
          var lth = this.mems[d][b][t];
          var tags_len = lth.tags.length;
          if (this.exclude_tags(lth, lth.t0, lth.t1, true)) this.retag(lth, tags_len>=pref.liveTag.max);
        }
      }
    },
    exclude_tags: function(lth, tags0, tags1, remove){
      var ex_list = pref_func.merge_obj5a(lth,pref.liveTag.ex_list_obj5,null);
      if (!ex_list) return;
      var t0 = this.exclude_tags_1(tags0, ex_list, remove? lth : null);
      var t1 = this.exclude_tags_1(tags1, ex_list, remove? lth : null);
      if (remove) {
        if (t0) lth.t0 = t0;
        if (t1) lth.t1 = t1;
      }
      return t0 || t1;
    },
    exclude_tags_1: function(tags, ex_list, lth){ // CAUTION. LIMIT CHANGABLE TAG OR ISSUE REDUNDANT ACCESS.
      var flag = false;
      if (tags) for (var i=tags.length-1;i>=0;i--) {
        loop_tag: for (var k=0;k<ex_list.length;k++) if (ex_list[k].test(tags[i])) {
          if (lth) this.delete_tags(tags[i],lth.key);
          tags.splice(i,1);
          flag = true;
          break loop_tag;
        }
      }
      return flag? tags : null;
    },
//    exclude_tags: function(lth, tag, remove){ // working code
//      var ex_list = pref_func.merge_obj5a(lth,pref.liveTag.ex_list_obj5,null);
//      if (ex_list) {
//        var flag = false;
//        for (var k=0;k<ex_list.length;k++) { // CAUTION. LIMIT CHANGABLE TAG OR ISSUE REDUNDANT ACCESS.
//          for (var j=0;j<2;j++) {
//            if (tag[j]) for (var i=tag[j].length-1;i>=0;i--) if (ex_list[k].test(tag[j][i])) {
////            if (tag[j]) for (var i=0;i<tag[j].length;i++) if (ex_list[k].test(tag[j][i])) { // BUG, cause infinite loop when ex_list refers btag(board's tag).
//              if (remove) this.delete_tags(tag[j][i],lth.key);
//              tag[j].splice(i,1);
////              tag[j].splice(i--,1);
//              flag = true;
//            }
//          }
//        }
//      }
//      return flag;
//    },
    key_dirty: Object.create(null),
//    key_dirty_creation: Object.create(Object.create(null)),
//    key_dirty: null, // {__proto__:key_dirty_creation},
////    tags_mems: Object.create(null, { // working code.
////      keys_obj: {get: function(){ // working code.
////        var keys = Object.create(null);
////        for (var i in this) {
////////////if (!pref.test_mode['23']) { // test_mode['23'] is meaningless, because String is handled by reference.
////////////          var val = this[i];
////////////} else {
////////////          var val = this.search_from_dic(this[i]);
////////////}
////          if (i[i.length-1]==='/') {
////            var dbt = comf.fullname2dbt(i);
////            for (var j in liveTag.mems[dbt[0]][dbt[1]]) keys[i+j] = this[i];
////          } else keys[i] = this[i];
////        }
////        return keys;
////      }},
////      keys_length: {get: function(){
////        var len = 0;
////        for (var i in this) {
////          if (i[i.length-1]==='/') {
////            var dbt = comf.fullname2dbt(i);
////            len += Object.keys(liveTag.mems[dbt[0]][dbt[1]]).length;
////          } else len++;
////        }
////        return len;
////      }},
////////////      search_from_dic: {value: function(no){for (var i in this.dic) if (this.dic[i]==no) return i;}},
////////////      clean_up_dic: {value: function(){
////////////        var keys = {};
////////////        for (var i in this) keys[this[i]] = null;
////////////        for (var i in this.dic) if (keys[this.dic[i]]!==null) delete this.dic[i];
////////////      }},
////    }),
    tags_array_old : [],
    tags_proto: {pk:false, in:false, ex:false, pn:null,
      mems_keys_obj: function(){ // working code.
        var keys = {};
        for (var i of this.mems.keys())
          if (i.no===undefined) for (var j in i) keys[i[j].key] = this.mems.get(i);
          else keys[i.key] = this.mems.get(i);
        return keys;
      },
      mems_keys: function*(){ // iterator is slow in chome because iterator is not optimized.
        for (var i of this.mems.keys())
          if (i.no===undefined) for (var j in i) yield i[j];
          else yield i;
      },
      mems_keys_length: function(){
        var len = 0;
        for (var i of this.mems.keys())
//          if (i.no===undefined) len += Object.keys(i).length; // slow???
          if (i.no===undefined) for (var j in i) ++len;
          else ++len;
        return len;
      },
      mems_keys_length_and_set_top: function(){
        var len = 0;
        var keys = {};
        for (var i of this.mems.keys()) {
          var key = this.mems.get(i);
          if (keys[key]===undefined) keys[key] = 0;
          if (i.no===undefined) for (var j in i) {++keys[key];++len;} // working code.
          else {++keys[key];++len;}
//          if (i.no===undefined) {
//            if (Object.keys(i).length===0) {++keys[key];++len;} // TEST for passive virtual boarding. // TEMPORAL PATCH // empty boards goes high.
//            if (Object.keys(i).length===0) {if (len===0) {++keys[key];++len;}} // TEST for passive virtual boarding. // TEMPORAL PATCH // tag which has only 1 thread goes down.
//            else for (var j in i) {++keys[key];++len;}
//          } else {++keys[key];++len;}
        }
        for (var j in keys) if (keys[j]>keys[this.key] || (keys[j]==keys[this.key] && j<this.key)) this.key = j;
        if (this.key===this.ref_tag) this.key = this.ref_tag; // get the same reference to reduce memory consumption.
        return len;
      },
      mems_boards: function(tgts){
        if (!tgts) tgts = {};
        for (var j of this.mems.keys()) tgts[j.key.substr(0,j.key.lastIndexOf('/')+1)]=null;
        return tgts;
      },
      caption: function(boards){
        return this.num +(boards? '/'+Object.keys(this.mems_boards()).length:'')+': ' + this.key;
      },
      re_caption: function(boards){
         this.pn.childNodes[3].textContent = this.caption(boards);
      },
      make_pn: (function(){
        var pn_template = cnst.dom('<div name="dummy"><input type="checkbox" name="pk"><input type="checkbox" name="in"><input type="checkbox" name="ex"> </div>');
        return function(boards){
          var pn = pn_template.cloneNode(true);
          pn.setAttribute('name',this.key);
          pn.childNodes[0].checked = this.pk;
          pn.childNodes[1].checked = this.in;
          pn.childNodes[2].checked = this.ex;
          pn.childNodes[3].textContent = this.caption(boards);
          this.pn = pn;
          return pn;
        };
      })(),
      inv_val: function(prop){
        var val = !this[prop];
        this[prop] = val;
        if (this.pn!==null) this.pn.childNodes[prop==='pk'? 0 : prop==='in'? 1 : 2].checked = val;
        return val;
      },
    },
    scan_regex : /#[^#, \.:;\n\|"<>&]+(?=[#, \.:;\n\|"<>&]|$)/g, // ATTENTION. REFER function prep_tag_str();
////    update_tags_in_th_sub: function(tags, keys, src, keys_fix, num, th, ur, btag){ // working code for obj.
////      var count = tags.length;
////      var i = 0;
////      while (count<num && i<src.length) {
////        var k = src[i++];
////        var k_ci = (pref.liveTag.ci)? k.toLowerCase() : k;
////        if (k_ci===k) k_ci=k; // to reduce memory consumption.
////        if (keys_fix[k_ci]===undefined) {
////          if (keys[k_ci]===undefined) {
////            keys[k_ci] = k; // discarded every time, so needless to think about reference of 'k'.
//////            tags[count++] = k;
////            if (this.tags[k]===undefined) {
////              if (this.tags_ci[k_ci]===undefined) {
////////////if (!pref.test_mode['23']) {
////                this.tags_ci[k_ci] = {key:k, mems:Object.create(this.tags_mems), ref_tag:k,
//////                                    pk:false, in:false, ex:false, // moved to prototype
////                                      pn:null, pn_num:0, ur:ur, __proto__:this.tags_proto};
////////////} else {
////////////                this.tags[k] = {key:k, mems:Object.create(this.tags_mems, {dic:{value:{}, writable:true}, dic_no:{value:0, writable:true}}),
////////////                                cbx:{pk:false, in:false, ex:false}, pn:null, pn_num:0, ur:ur};
////////////}
////                if (this.tags[k]===undefined) this.tags[k] = this.tags_ci[k_ci];
////              } else this.tags[k] = this.tags_ci[k_ci];
//////              this.tags[k].tgts[k] = null;
////            }
////////            this.tags[k].mems[name] = k;
////            var mems = this.tags[k].mems;
////////////if (!pref.test_mode['23']) {
////////////            var val = k;
////////////} else {
////////////            if (mems.dic[k]===undefined) {mems.dic[k]=mems.dic_no++;} // to reduce memory consumption, use dictionary.
////////////            var val = mems.dic[k];
////////////}
////            if (k===this.tags[k].ref_tag) k = this.tags[k].ref_tag; // get the same reference to reduce memory consumption. This makes major of members see 'btag'.
////            else for (var j in mems) if (k===mems[j]) {k=mems[j]; break;}
////            if (k!==btag) {
////              if (mems[th.key]!==k) this.key_dirty[k_ci] = null; // cases are changed or a new tag is added. 'if (keys[k_ci]!==k)' ensures (pref.liveTag.ci===true).
////              mems[th.key] = k; // write always to force to use the same reference to reduce memory.
////            }
////            tags[count++] = k;
////          }  // else if (keys[k_ci]!==k) this.tags[this.tags_ci[k_ci]].key_dirty = true; // case is changed. 'if (keys[k_ci]!==k)' ensures (pref.liveTag.ci===true).
////        }
////      }
////      return i;
////    },
    update_tags_in_th_sub: function(tags, keys, src, keys_fix, num, th, ur, bd, clean){
      var count = tags.length;
      var i = 0;
      while (count<num && i<src.length) {
        var k = src[i++];
        var k_ci = (pref.liveTag.ci)? k.toLowerCase() : k;
        if (k_ci===k) k_ci=k; // to reduce memory consumption.
        if (keys_fix[k_ci]===undefined) {
          if (keys[k_ci]===undefined) {
            keys[k_ci] = k; // discarded every time, so needless to think about reference of 'k'.
            if (this.tags[k]===undefined) {
              if (this.tags_ci[k_ci]===undefined) {
                if (this.key_dirty[k_ci]===null) { // fallback for rare case, Case of deleted alerady.
                //  Since tags_array_old ISN'T synchronized with tas.tags_ci,
                //  the same key would be duplicated in tags_array_old when deletion, creation, deletion and creation(4 operations) were in a timeslot. This would be a bug.
                  this.update_pn(); // synchronize them now.
                }
////                this.tags_ci[k_ci] = {key:k, mems: new Map(), ref_tag:k, // not optimized
////                                      pn:null, pn_num:0, ur:ur, __proto__:this.tags_proto};
                var mems_map = new Map(); // consumes 16 Bytes per content, but I can't reduce.... If this is changed to Set, 2.5% reduce in 8chan, but 0.5% in 4chan.(estimated)
                this.tags_array_old[this.tags_array_old.length] = // try this way of writing assigning. Is this odd or bad?
                this.tags_ci[k_ci] = {key:k, mems: mems_map, ref_tag:k, // optimized in V8
                                      num:0, ur:ur, __proto__:this.tags_proto};
//                                      pn:null, pn_num:0, ur:ur, __proto__:this.tags_proto};
//                if (k!==k_ci) this.tags[k] = this.tags_ci[k_ci]; // this is equiavlent to 'if (this.tags[k]===undefined)' because this.tags.__proto__ === this.tags_ci // moved to later and was consolidated.
//                this.key_dirty_creation[k_ci] = null; // for pickup in 'update_pn'
//                if (pref.debug_mode['3']) console.log('Added: '+k);
              }
              if (k!==k_ci) { // this is equiavlent to 'if (this.tags[k]===undefined)' because this.tags.__proto__ === this.tags_ci
                var to = this.tags[k] = this.tags_ci[k_ci];
                if (to.sb===undefined) to.sb = [k]; // subscribe for deletion
                else to.sb[to.sb.length] = k;
              }
            } // 'else' isn't required because this.tags.__proto__ === this.tags_ci
            var mems = this.tags[k].mems;
            if (k===this.tags[k].ref_tag) k = this.tags[k].ref_tag; // get the same reference to reduce memory consumption. This makes major of members see 'btag'.
            else for (var j in mems) if (k===mems.get(j)) {k=mems.get(j); break;}
//            if (k!==btag) {
            if (!mems.has(bd)) {
              var me = (th===null)? bd : bd[th.no];
              var val = mems.get(me);
              if (val===undefined || val!==k) this.key_dirty[k_ci] = null; // a new tag is added or cases are changed. 'if (keys[k_ci]!==k)' ensures (pref.liveTag.ci===true).
              mems.set(me,k); // write always to force to use the same reference to reduce memory.
            } else if (!clean) this.key_dirty[k_ci] = null; // board tag is hit. Case may be changed in case of board tag.
            tags[count++] = k;
          }
        }
        if (th===null) if (this.tags_ci[k_ci]['pk']) {
          scan.list_nup.add_board(bd,0);
          scan.scan('b',bd.domain);
        }
        if (k_ci in this.tags_reserved) this.tags_reserved_found(k_ci);
//        if (th===null) { // working code
//          var tag_pk = this.tags_ci[k_ci]['pk'];
//          if (tag_reserved!==undefined || tag_pk) this.tags_reserved_found_scan(k_ci, true, tag_reserved, tag_pk, bd);
//        } else if (tag_reserved!==undefined) this.tags_reserved_found(k_ci, true, tag_reserved);
      }
      return i;
    },
    retag_in_th_sub: function(tag, bd, remove){
      if (remove) {
        if (this.tags[tag]) this.delete_tags(tag, bd.key); // , true);
      } else { // consolidate
        this.update_tags_in_th_sub([], {}, [tag], {}, 1, null, 0, bd);
        var mems = this.tags[tag].mems;  
        for (var t in bd) if (mems.has(t)) mems.delete(t);
      }
    },
    update_tags_in_th: function(src_new, src_old, keys_fix, num, th, watch, bd){
      var ur = this.generate_ur(watch[1]); // moved to here from 'extract_tags' because 'prep_tags' needs this when it has tags_old.
      var tags = [];
      var keys = {};
      this.update_tags_in_th_sub(tags, keys, src_new, keys_fix, num, th, ur, bd);
      if (src_old) {
        var i = this.update_tags_in_th_sub(tags, keys, src_old, keys_fix, num, th, ur, bd);
        while (i<src_old.length) {
          var k = src_old[i++];
          var k_ci = (pref.liveTag.ci)? k.toLowerCase() : k;
          if (keys_fix[k_ci]===undefined) {
            if (keys[k_ci]!==k) this.key_dirty[k_ci] = null; // case is changed, or removed. 'if ((k_ci in keys) && keys[k_ci]!==k)' ensures (pref.liveTag.ci===true).
            if (keys[k_ci]===undefined) this.delete_tags(k,th.key);
          }
        }
      }
      for (var i in keys_fix) this.check_update_tags_color(keys_fix[i],ur);
      for (var i=0;i<tags.length;i++) this.check_update_tags_color(tags[i],ur);
//      this.update_pn();
//      this.update_pn_buf.delayed_do();
//      return [tags, keys, false];
      src_new = null;
      this.keys_fix = keys;
      return tags;
    },
    remove_tags_in_th: function(dbt){
      var lth = this.mems[dbt[0]][dbt[1]][dbt[2]];
      if (!lth) return;
      var tags = lth.tags;
      for (var i=0;i<tags.length;i++) this.delete_tags(tags[i],lth.key);
      var btags = lth.btags;
      if (pref3.stats.use) stats.thread_removed(dbt);
      if (lth.q) lth.q = null; // cut cyclic reference, lth.q.__proto__  = lth
      if (lth.nr && (pref.notify.favicon || pref.notify.title.notify)) notifier.favicon.set();
      delete this.mems[dbt[0]][dbt[1]][dbt[2]];
      if (btags) for (var i=0;i<btags.length;i++) {
        this.key_dirty[(pref.liveTag.ci)? btags[i].toLowerCase() : btags[i]] = null;
        if (pref.liveTag.style.use) this.update_ur_1(btags[i]); // btags are updated here, AFTER deleting lth.
      }
      this.update_pn_buf.delayed_do();
//      if (pref.debug_mode['3']) console.log('remove: '+name);
//      if (pref.debug_mode['3']) {
////        var th_count = 0;
////        for (var d in this.mems) for (var b in this.mems[d]) for (var t in this.mems[d][b]) th_count++;
////        console.log('remove: '+name+', '+tags.tag+', tags_ci+tags: '+Object.keys(this.tags_ci).length+'+'+Object.keys(this.tags).length+', threads:'+th_count);
//        for (var i in this.tags_ci) for (var j of this.tags_ci[i].mems.keys()) if (j===tags) console.log('ERROR: tags_ci['+i+'].mems has a reference to a deleted thread.');
//      }
    },
    delete_tags: function(tag,name, from_subscribers){ // , buffered_deletion){
      if (!from_subscribers && this.subscribers.length!=0) for (var i=0;i<this.subscribers.length;i++) this.delete_tags.call(this.subscribers[i],tag, name, true);
//      delete this.tags[tag].mems[name];
      var tag_ci = (pref.liveTag.ci)? tag.toLowerCase() : tag;
      this.key_dirty[tag_ci] = null;
//      delete this.key_dirty_creation[tag_ci];
      var tobj = this.tags[tag];
      if (tobj) {
        if (tobj.mems.delete(this.mems.getFromName(name)) && pref.liveTag.style.use) this.update_ur_1(tag); // only thread's tag is updated here. see remove_tags_in_th.
//        this.tags[tag].mems.delete(this.mems.getFromName(name));
//        if (pref.liveTag.style) this.update_ur_1(tag); // BUG. btags can't be updated here synchronously, because ur is generated by enumerating lths, which will be deleted AFTER here.
//      if (Object.keys(this.tags[tag].mems).length==0) {
        if (tobj.mems.size==0) {
          if (tobj.pn!==null) this.pn.removeChild(tobj.pn); // CAUSE ERROR, WHY??? // because of multiple deletion orz. this.tags[tag].pn === this.tags_ci[tag_ci].pn in this case, because this.tags[X] refers prototype which is this.tags_ci[Y]
//          if (this.tags[tag].pn!==null) if (this.tags[tag].pn.parentNode===this.pn) this.pn.removeChild(this.tags[tag].pn);
//          delete this.tags[tag].tgts[tag];
          for (var i in this.active) if (tobj[i]) this.active_inc_dec(i,false,tag_ci); // this.active[i]--;
          var subscribers = tobj.sb;
          if (subscribers) for (var i=0;i<subscribers.length;i++) delete this.tags[subscribers[i]];
          delete this.tags_ci[tag_ci];
////          delete this.tags[tag];
//////          if (pref.liveTag.ci) for (var i in this.tags) if (!this.tags[i]) delete this.tags[i]; // tags are not removed in 'update_tags_in_th_sub' if cases are changed.
//////          for (var i in this.tags) if (this.tags[i]===this.tags_ci[tag_ci]) delete this.tags[i]; // TOO SLOW
////          if (!buffered_deletion) { // BUFFERED DELETION MUST BE EXECUTED ATOMICALLY, NO ACCESS TO this.taga BEFORE EXECUTION OF DELETION IS NOT ALLOWED.
////            var keys_tags = Object.keys(this.tags);
////            var tags_ci_obj = this.tags_ci[tag_ci];
////            for (var i=0;i<keys_tags.length;i++) if (this.tags[keys_tags[i]]===tags_ci_obj) delete this.tags[keys_tags[i]];
//////            if (this.tags_ci[tag_ci].pn) this.pn.removeChild(this.tags_ci[tag_ci].pn); // BUG, CAUSED AN ERROR, because of documentfragment??? // because of multiple deletion orz.
////            delete this.tags_ci[tag_ci];
////          } else {
////            if (!this.delete_tags_buffered_list) this.delete_tags_buffered_list = new Set();
////            this.delete_tags_buffered_list.add(this.tags_ci[tag_ci]);
////          }
          if (pref.debug_mode['3']) console.log('Remove: '+tag);
        }
      }
    },
////    delete_tags_buffered_list: null,
////    delete_tags_buffered: function(){
////      if (!this.delete_tags_buffered_list) return;
////      var keys_tags = Object.keys(this.tags);
////      for (var i=0;i<keys_tags.length;i++) if (this.delete_tags_buffered_list.has(this.tags[keys_tags[i]])) delete this.tags[keys_tags[i]];
////      for (var i in this.tags_ci) if (this.delete_tags_buffered_list.has(this.tags_ci[i])) {
//////        if (this.tags_ci[i].pn) this.pn.removeChild(this.tags_ci[i].pn); // because of multiple deletion orz.
////        delete this.tags_ci[i];
////      }
////      this.delete_tags_buffered_list = null;
////    },
    check_update_tags_color: function(tag, ur){ // checks 0->1 only.
      var ur_old = this.tags[tag].ur;
      this.tags[tag].ur |= ur;
//      if (pref.debug_mode['4']) if (ur_old!=ur) console.log('ur: '+tag+', '+ur_old+' <- '+ur);
      if (~ur_old & ur) this.update_tag_node(tag, null, true);
    },
    rm_404_list: function(domain, board, nos){
      var tgts = {};
      if (this.mems[domain] && this.mems[domain][board]) {
        var mems = this.mems[domain][board];
        for (var i in mems) if (nos[i]===undefined) tgts[i] = mems[i];
      }
      return tgts;
    },
    rm_404_1: function(domain, board, no, keep){
      var name = domain+board+no;
//      var lth;
//      var keep = pref.liveTag.rm_404==='no' || (lth = liveTag.mems.getFromName(name), lth && lth.watched && (pref.liveTag.rm_404==='watched' || lth.nr));
      if (pref.features.IDB) if (pref.archive.IDB.auto_clean) IDB.req(domain, board, no, 'pruned_time', Date.now(), 'check_clean');
      if (!pref.test_mode['67']) archiver.clean_list(domain, board, no);
      if (!keep && pref.features.IDB && pref.archive.IDB.auto_restore && cataLog.threads[name]) cataLog.restore_th_from_IDB(domain, board, no);
//      if (keep) { // moved into remove_thread
//        var tgt_th = cataLog.threads[name];
//        if (tgt_th && tgt_th[14]!=='IDB' && tgt_th[14]!=='FILE') {
//          if (tgt_th[14]!=='x') {
//            tgt_th[14]='x';
//            cataLog.Footer.update_force(name); // temporal, this will be rewritten after implementing watcher
//          }
//        }
//        return true;
//      }
//      gClg.remove_thread(name, null, keep);
//      if (keep) return true;
      if (!keep) this.rm_404_2(domain, board, no);
    },
    rm_404_2: function(domain, board, no){
      this.remove_tags_in_th([domain,board,no]);
      if (pref[cataLog.embed_mode].deleted_posts.auto_clean) site2[domain].clean_up_deleted_posts_1(board + no);
    },
//    find_friend_tag_ci: function(tag){
//      var tag_l = tag.toLowerCase();
//      for (var i in liveTag.tags) if (tag!==i && tag_l===liveTag.tags[i].key.toLowerCase()) return i;
//      return null;
//    },
    tooltip: function(e){
      var et = e.target;
      if (et.type==='checkbox') {
        var suffix = et.name; // .substr(-2,2);
        var tag = et.parentNode.getAttribute('name'); // .slice(0,-3);
        var str = ((suffix==='pk')? 'Fetch' : (suffix==='in')? 'Show' : 'Hide') + ' threads which have ' + tag + '.'
      } else {
        tag = et.getAttribute('name');
        str = tag + ': ' + liveTag.make_info(tag) +'<br>';
//        for (var i in liveTag.tags[tag].mems) str += i + ', ';
        var myDomain = new RegExp('^'+site.nickname);
        for (var i of liveTag.tags[tag].mems.keys()) str += i.key.replace(myDomain,'') + ', ';
      }
      return str;
//      pref_func.tooltips.show_1.call(et, e, str, null, et.type!=='checkbox');
    },
    boardlist_click_entry: function(e){
      if (!e.ctrlKey) {
        e.preventDefault();
        var tag = e.target.textContent;
        this.cbx_changed(tag, pref.liveTag.click_func_bl, null, e.target);
      }
    },
    generate_ur: function(info){
      return ((info>>16)!=0)? 3 : ((info&0x0000ffff)!=0)? 1 : 0;
    },
    CountNR: (function(){
// [ForInStatement is not fast case]
// http://www.html5rocks.com/en/tutorials/performance/mystery/?redirect_from_locale=ja
      var CountNR = function(){
        this.nrtm = 0;
        this.nr   = 0;
        this.nth  = 0;
      }
      CountNR.prototype = {
        count: function(th){
          if (th[0]&0x0c0c0000) { // includes passive
            var nr = th[1]&0x0000ffff;
            if (nr) {
              this.nrtm += th[1]>>16;
              this.nr   += nr;
              this.nth++;
            }
          }
        },
        count_all: function(){
          for (var d in liveTag.mems) for (var b in liveTag.mems[d]) {
            var lbd = liveTag.mems[d][b];
            if (lbd.nr<0) { // check dirty
              var cnr = new liveTag.CountNR();
              for (var t in lbd) cnr.count(lbd[t].wt);
              lbd.nrtm = cnr.nrtm;
              lbd.nr   = cnr.nr;
            }
//              sum.count([0x000c0000, (lbd.nrtm<<16) + lbd.nr]); // BUG, nr overflowed in 4chan.
            this.nrtm += lbd.nrtm;
            this.nr   += lbd.nr;
          }},
      }
      return CountNR;
    })(),
    dirtify_ur: function(th){
      this.mems[th.domain][th.board].nr = -1; // mark as dirty
    },
    update_ur: function(lth,ur,all_case){
      var tags = lth.tags;
      for (var i=0;i<tags.length;i++)
        if (!all_case) {if (this.tags[tags[i]]) this.check_update_tags_color(tags[i],ur);}
        else this.update_ur_1(tags[i]);
    },
//    update_ur: function(name,ur,all_case){ // working code
//      var dbt = comf.fullname2dbt(name);
//      var tag = this.mems[dbt[0]][dbt[1]][dbt[2]];
//      this.mems[dbt[0]][dbt[1]].nr = -1;
////      var tag = this.mems.getFromName(name);
//      for (var j=0;j<2;j++) if (tag[j]) for (var i=0;i<tag[j].length;i++) 
//        if (!all_case) {if (this.tags[tag[j][i]]) this.check_update_tags_color(tag[j][i],ur);}
//        else this.update_ur_1(tag[j][i]);
//    },
    update_ur_1: function(tag){ // subfunc of 'count_ur'
      var ur_old = this.tags[tag].ur;
      var ur = 0;
if (!pref.test_mode['24']) {
      var mems_objs = this.tags[tag].mems_keys_obj(); // working code.
      for (var i in mems_objs) {
//        var info = this.mems.getFromName(i).wt;
        var info = this.mems.getFromName(i);
        if (info) ur |= liveTag.generate_ur(info.wt[1]);
      }
} else {
      for (var i of this.tags[tag].mems_keys())
        ur |= liveTag.generate_ur(i.wt[1]);
}
      if (ur_old!=ur) {
        this.tags[tag].ur = ur;
        this.update_tag_node(tag, null, true);
      }
    },
//    count_ur: function(tag){
//      var nums = this.count_ur_sub(tag);
//      var friends = Object.create(null);
//      friends[tag] = null;
//      if (pref.liveTag.ci) {
//        var tag_l = tag.toLowerCase();
//        for (var i in liveTag.tags) {
//          if (tag!==i && tag_l===liveTag.tags[i].key.toLowerCase()) {
//            friends[i]=null;
//            if (this.tags[i].ur_cs===null) this.count_ur_sub(i);
//            for (var j=0;j<4;j++) nums[j] += this.tags[i].ur_cs[j];
//          }
//        }
//      }
//      for (var i in friends) this.tags[i].ur = nums;
//      return nums;
//    },
//    count_ur_sub: function(tag){
    count_ur: function(tag){
      var nof_th = 0;
      var bds = {};
      var counter = new this.CountNR();
if (!pref.test_mode['24']) {
      var mems_objs = this.tags[tag].mems_keys_obj(); // working code.
      for (var i in mems_objs) {
        var dbt = comf.fullname2dbt(i);
        var info = this.mems[dbt[0]][dbt[1]][dbt[2]].wt;
        if (info) counter.count(info);
        bds[dbt[0]+dbt[1]] = null;
      }
      nof_th = this.tags[tag].mems_keys_length();
} else {
      for (var i of this.tags[tag].mems_keys()) {
        counter.count(i.wt);
        bds[i.domain+i.board] = null;
        nof_th++;
      }
}
      var nof_bds = Object.keys(this.tags[tag].mems_boards()).length;
      return [counter.nrtm, counter.nr, counter.nth, nof_th, Object.keys(bds).length, nof_bds];
    },
    make_info: function(tag){
      var nums = liveTag.count_ur(tag); // must exec every time because every update are NOT exact.
      return 'U: '+nums[0]+'/'+nums[1]+'/'+nums[2]+' / T: '+nums[3]+' / B: '+nums[4]+'/'+nums[5];
    },
    tag_onmouseover: function(e){
//      var et = e.target;
//      if (pref.liveTag.info) pref_func.tooltips.show_1.call(et, e, liveTag.make_info(et.textContent), null, true);
      return liveTag.make_info(e.target.textContent);
    },
    filter_changed_broadcast: function(){
      if (this.NC) this.clg.filter_changed();
      else {
        var clgs = gClg.Clgs;
        for (var i=0;i<clgs.length;i++) if (!clgs[i].liveTag.NC) clgs[i].filter_changed();
      }
//      if (this.footer) this.clg.filter_changed(); else if (this.clg) gClg.filter_changed();
    },
    cbx_changed: function(tag, func, no_scan, node_in_boardlist, e, bulk_change){
      var obj = this.tags[tag];
      if (func[0]==='_') { // raw function mode, value is checked here.
        var inv_in = !obj['in'] && func.indexOf('in')!==-1;
        var inv_pk = !obj['pk'] && func.indexOf('pk')!==-1;
        var inv_ex = !obj['ex'] && func.indexOf('ex')!==-1;
        if (!inv_in && !inv_pk && !inv_ex) return;
      } else {
        var backward = e && e.shiftKey;
        if (e && e.ctrlKey && pref) func = 'ex';
        if (func==='pk_in') func = (this.count_ur(tag)[3]===0)? 'pkin' : 'in';
        var stOut  = obj['in']===false && obj['ex']===true;
        var stNone = obj['in']===false && obj['ex']===false;
        var stIn   = obj['in']===true  && obj['ex']===false;
        inv_in = func==='pkin' || func==='in' || func==='inex' && (backward? !stNone : !stOut);
        inv_pk = func==='pkin' && obj['pk']===obj['in'] || func==='pk';
        inv_ex = func==='ex' || func==='inex' && (backward? !stIn : !stNone);
      }
      if (inv_in) this.cbx_emulate_inv(tag, 'in', no_scan); // 'in' must be processed first to prevent auto set.
      if (inv_pk) this.cbx_emulate_inv(tag, 'pk', no_scan); // 'in' is NOT changed here.
      if (inv_ex) this.cbx_emulate_inv(tag, 'ex', no_scan);
      if (!bulk_change) this.filter_changed_broadcast();
//      if (this.footer) this.clg.filter_changed(); else if (this.clg) gClg.filter_changed();
//      if (cataLog.catalog_filter_changed) cataLog.catalog_filter_changed();
      this.update_tag_node(tag, node_in_boardlist);
    },
//    cbx_changed: function(tag, func, no_scan, node_in_boardlist){ // working code
//      if (func==='pkin') {
//        if (this.tags[tag]['pk'] !== !this.tags[tag]['in']) {
//          this.tags[tag]['pk'] = !this.tags[tag]['in'];
//          if (this.tags[tag].pn!==null) this.tags[tag].pn.childNodes[0].checked = this.tags[tag]['pk'];
//          this.cbx_onchange(tag,'pk', no_scan); // 'in' is upped automatically when pk goes 0 -> 1.
//        }
//        if (this.tags[tag]['in'] !== this.tags[tag]['pk']) {
//          this.tags[tag]['in'] = this.tags[tag]['pk'];
//          if (this.tags[tag].pn!==null) this.tags[tag].pn.childNodes[1].checked = this.tags[tag]['in'];
//          this.cbx_onchange(tag,'in');
//        }
//      } else {
//        var in_old = this.tags[tag]['in'];
//        if (func==='in' || (func==='inex' && !(this.tags[tag]['in']===false && this.tags[tag]['ex']===true))) {
//          this.tags[tag]['in'] = !this.tags[tag]['in'];
//          if (this.tags[tag].pn!==null) this.tags[tag].pn.childNodes[1].checked = this.tags[tag]['in'];
//          this.cbx_onchange(tag,'in');
//        }
//        if (func==='ex' || (func==='inex' && !(in_old===false && this.tags[tag]['ex']===false))) {
//          this.tags[tag]['ex'] = !this.tags[tag]['ex'];
//          if (this.tags[tag].pn!==null) this.tags[tag].pn.childNodes[2].checked = this.tags[tag]['ex'];
//          this.cbx_onchange(tag,'ex');
//        }
//      }
//      this.cbx_onchange_after();
//      this.update_tag_node(tag, node_in_boardlist);
//    },
//    filter_onchange_entry : function(e){liveTag.filter_onchange(this, e);},
    filter_onchange: function(pref_obj, init, from_filter){
      pref_obj.rexps = comf.kwd_prep_regexp(pref_obj);
//      this.pn_filter_rexp = (pref.filter.tag_search.str==='')? null : new RegExp(pref.filter.tag_search.str, (pref.liveTag.ci)? 'i': undefined);
////      for (var i in this.tags) // working code.
////        if (this.tags[i].pn!==null) this.tags[i].pn.style.display = (this.pn_filter_rexp===null || this.pn_filter_rexp.test(i))? '' : 'none';
      var master = this===liveTag;
      if (!master && pref_obj.rexps && !this.subscribed) this.subscribe();
      if (!init || this.subscribed) this.update_pn(from_filter); // draw even at initial if '!master', to prevent from loosing 'from_initial' information
    },
////    set_reserved_tags: function(tags,set){ // working code
////      for (var i=0;i<tags.length;i++) {
////        var tag = tags[i];
////        var tag_ci = (pref.liveTag.ci)? tag.toLowerCase() : tag;
////        if (this.tags_ci[tag_ci]) {
////          if (this.tags_ci[tag_ci]['pk']!=set) this.cbx_changed(tag_ci,'pkin', true);
////          this.update_tag_node(tag_ci);
////          tags.splice(i--,1);
////        }
////      }
////    },
    set_tag_by_program: function(tag, prop, val){
      var tag_ci = pref.liveTag.ci? tag.toLowerCase() : tag;
      var obj = this.tags_ci[tag_ci];
      if (obj) {
        if (obj[prop]!=val) this.cbx_changed(tag_ci, prop, true);
      } else if (val) this.tags_reserved_register(tag_ci, prop);
    },
    tags_reserved: Object.create(null),
//    tags_reserved_found_scan: function(tag_ci,set, prop, pk, bd){ // working code
//      if (prop==='pk' || pk) {
//        scan.list_nup.add_board(bd,0);
//        scan.scan('b',bd.domain);
//      }
//      if (prop!==undefined) this.tags_reserved_found(tag_ci,set, prop);
//    },
    tags_reserved_register: function(tag_ci, prop){
      var v_prop;
      var v = this.tags_reserved[tag_ci];
      this.tags_reserved[tag_ci] = !v? prop
        : (v_prop = v + prop,  (v_prop.indexOf('pk')!==-1? 'pk' : '')
                              +(v_prop.indexOf('in')!==-1? 'in' : '')
                              +(v_prop.indexOf('ex')!==-1? 'ex' : ''));
    },
    tags_reserved_found: function(tag_ci){
      var prop = this.tags_reserved[tag_ci]
      this.cbx_changed(tag_ci, '__'+prop, true); // '__' for raw function mode, see 'cbx_changed'
//      if (this.tags_ci[tag_ci][prop]!=set) this.cbx_changed(tag_ci, prop==='pk'? 'pkin':prop, true);
      delete this.tags_reserved[tag_ci];
    },
    tags_reserved_init: function(clg, sel, sel_old){
      var dirty = false;
      var pf_ts = pref.virtualBoard.ts;
      var tags = this.tags_of_bg(clg,sel);
      if (sel_old!==null) {
        var obj_next = this.tags_reserved_init_1(tags, false, true);
        var ts = [];
        if (pref.catalog_board_list_obj.length>sel_old) pref.catalog_board_list_obj[sel_old][0].ts[clg.serialNo] = ts;
        dirty = this.enumerate_tags_state(ts, pf_ts[0]==='c', obj_next);
        if (pf_ts==='ns') dirty |= this.tags_reserved_init_1(tags, false)[0];
      }
      this.tags_reserved = Object.create(null); // clear all reserved tags
      if (pf_ts[1]!=='n') dirty |= this.tags_reserved_init_1(tags, true);
      if (dirty) this.filter_changed_broadcast();
    },
    tags_reserved_init_1: function(tags, set, make_list){
      var dirty = false;
      var obj = make_list? {} : null;
      for (var i=0;i<tags.length;i++) {
        var tags_i = tags[i];
        var prop = tags_i[1]!=='#'? 'in' : tags_i[0]==='#'? 'pkin' : tags_i[0]==='!'? 'ex' : 'pk'; // !#tag for 'ex', ##tag for 'pkin', #tag for 'in', ^#tag for 'pk'
        var tag = prop==='in'? tags_i : tags_i.slice(1);
        var tag_ci = (pref.liveTag.ci)? tag.toLowerCase() : tag; // tags_ci must be used becase tags in blist is raw, the tag may not exist in liveTag.tags[tag].
        var tobj = this.tags_ci[tag_ci];
        if (make_list) {
          if (tobj) {
            var tgt = obj[tag_ci] || (obj[tag_ci] = {});
            if (prop==='pkin') tgt['pk'] = tgt['in'] = true;
            else tgt[prop] = true;
          }
        } else {
          if (tobj) {
            if (prop==='pkin'? (tobj['pk']!=set || tobj['in']!=set) : tobj[prop]!=set) {
              this.cbx_changed(tag_ci, '__'+prop, true, undefined, undefined, true); // '__' for raw function mode, see 'cbx_changed'
              dirty = true;
            }
//          this.tags_reserved_found(tag_ci,set, prop);
          } else if (set) this.tags_reserved_register(tag_ci, prop);
        }
      }
      return obj || dirty;
    },
    enumerate_tags_state: function(ts, clear, tags_next){
      var dirty = false;
      for (var t in this.tags_ci) dirty |= this.enumerate_tags_state_1(ts, this.tags_ci[t], t, clear, tags_next);
      return dirty;
    },
    enumerate_tags_state_1: function(ts, o, t, clear, tags_next){
      var d = false;
      if (o['pk'] || o['in'] || o['ex']) {
        if (o['pk'] || o['in']) ts[ts.length] = (o['pk'] && o['in']? '#' : o['in']? '' : '^')+t;
        if (o['ex']) ts[ts.length] = '!'+t;
        if (clear) {
          var n = tags_next && tags_next[t];
          if (o['in'] && (!n || !n['in'])) {this.cbx_emulate_inv(t, 'in', true); d = true;}
          if (o['pk'] && (!n || !n['pk'])) {this.cbx_emulate_inv(t, 'pk', true); d = true;}
          if (o['ex'] && (!n || !n['ex'])) {this.cbx_emulate_inv(t, 'ex', true); d = true;}
          if (d) this.update_tag_node(t);
        }
      }
      return d;
    },
    tags_of_bg: function(clg,sel){
      var bl_obj = pref.catalog_board_list_obj[sel] && pref.catalog_board_list_obj[sel][0];
      var pf_ts = pref.virtualBoard.ts;
      return pf_ts[1]==='r' && bl_obj.ts[clg.serialNo] || pf_ts[1]!=='n' && bl_obj.tags || [];
    },
    boards_picked_up_by_tags: function(clg,sel){
      var tags = this.tags_of_bg(clg,sel);
      var bds = {};
      for (var i=0;i<tags.length;i++) {
        var t = tags[i];
        if (t[1]==='#' && (t[0]==='^' || t[0]==='#') && (t = t.slice(1)) && this.tags[t]) this.tags[t].mems_boards(bds); // ##tag ans ^#tag, see 'tags_reserved_init'
      }
      return bds;
    },
    update_pn_last_filter_active: false,
    sort_func: function(a,b){return b.num - a.num || b.mems.size - a.mems.size || ((b.key > a.key)? -1:1);},
    update_pn_disp_func: function(rexps, key){
//      if (rexps===null) return 0;
      for (var i=0;i<rexps.length;i++) if (rexps[i].test(key)) return 1; // match any
      return 0;
    },
    update_pn_buf: null,
    setup_pn: function(pn, pref_obj){
//      this.clg = clg;
//      if (footer) this.footer = footer;
      this.pn = pn.getElementsByTagName('div')['filter.tag_list'];
      this.pn.onchange = this.cbx_onchange_entry.bind(this);
      this.pn_summary = this.pn.nextSibling.childNodes[0];
      this.filter_onchange(pref_obj, true, true);
    },
    update_pn: function(from_filter){
      if (this.pn===null) return;
//      if (this.pn===0) return; // trap for thread_reader.
//      if (this.pn===null) {
//        this.pn = document.getElementsByName('filter.tag_list')[0];
//        if (this.pn) {
//          this.pn.style.height = '16px';
//          this.pn.style.width = '250px';
//          this.pn_summary = this.pn.previousSibling.childNodes[0];
//          this.pn.onchange = this.cbx_onchange_entry;
//          this.filter_onchange(pref.filter.tag_search, true);
//        } else {
//          this.pn = 0;
//          return;
//        }
//      }
      this.update_pn_1_lazy.cancel();
      this.update_pn_1(from_filter, true);
    },
    update_pn_pos: null,
    update_pn_1_lazy: null, // bound later, update_pn_1.bind(liveTag,true)
    update_pn_1: function(from_filter, from_initial){
//      if (this.reserved) { // worning code
//        this.set_reserved_tags(this.reserved,true);
//        if (this.reserved.length===0) this.reserved = null;
//      }

////      var tags_array = []; // working code.
//////      var keys = {};
//////      for (var i in this.tags) // patch, this can be removed if this.tags is fully overlayed at pref.liveTag.ci. // PATCHES CAN BE REMOVED
//////        if (keys[i]!==null) {
//////          keys[i] = null;
//////          tags_array[tags_array.length] = {key:i, num:Object.keys(this.tags[i].mems).length};
//////        }
//////      if (pref.liveTag.ci) {
//////        for (var i=0;i<tags_array.length;i++) { // search major key
//////          keys = {};
//////          var mems = this.tags[tags_array[i].key].mems;
//////          for (var j in mems) keys[mems[j]] = (keys[mems[j]] || 0) +1;
//////          for (var j in keys) if (keys[j]>keys[tags_array[i].key]) tags_array[i].key = j;
//////        }
//////      }
////
////      if (pref.liveTag.ci) {
////        for (var i in this.key_dirty) { // set major key
////          var tag_obj = this.tags_ci[(pref.liveTag.ci)? i.toLowerCase() : i];
////          if (tag_obj) {
////            var keys = {};
////            var mems = tag_obj.mems;
////            for (var j in mems) keys[mems[j]] = (keys[mems[j]] || 0) +1;
////            var key_old = tag_obj.key;
////////////if (!pref.test_mode['23']) {
////            for (var j in keys) if (keys[j]>keys[tag_obj.key]) tag_obj.key = j;
////////////} else {
////////////            for (var j in keys) if (keys[j]>keys[tag_obj.key]) tag_obj.key = tag_obj.mems.search_from_dic(j);
////////////            mems.clean_up_dic();
////////////}
////            if (key_old!==tag_obj.key) if (tag_obj.pn) tag_obj.pn.childNodes[3].textContent = Object.keys(tag_obj.mems).length + ': ' + tag_obj.key;
////          }
////        }
////      }
////      this.key_dirty = Object.create(null);
////      for (var i in this.tags_ci) tags_array[tags_array.length] = {key:this.tags_ci[i].key, num:this.tags_ci[i].mems_keys_length(),
////                                                                   disp: (this.pn_filter_rexp===null || !this.pn_filter_rexp.test(this.tags_ci[i].key))? 0 : 1};
////      tags_array.sort(this.sort_func);

//      var tags_array = this.tags_array_old; // working code.
//      for (var i=0;i<tags_array.length;i++) {
//        var key = tags_array[i].key;
//        var k_ci = (pref.liveTag.ci)? key.toLowerCase() : key;
//        if (this.key_dirty[k_ci]===null) {
//          delete this.key_dirty[k_ci];
//          if (this.tags_ci[k_ci]) {
//            tags_array[i].num = (pref.liveTag.ci)? this.tags_ci[k_ci].mems_keys_length_and_set_top() : this.tags_ci[k_ci].mems_keys_length();
//            if (key!==this.tags_ci[k_ci].key) {
//              if (this.tags_ci[k_ci].pn) this.tags_ci[k_ci].pn.childNodes[3].textContent = tags_array[i].num + ': ' + this.tags_ci[k_ci].key;
//              tags_array[i].key = this.tags_ci[k_ci].key;
//            }
//          } else {
//            tags_array.splice(i--,1);
//            continue;
//          }
//        }
//        if (from_filter) tags_array[i].disp = this.update_pn_disp_func(pref.filter.tag_search.rexps, tags_array[i].key);
//      }
//      for (var i in this.key_dirty)
//        if (this.tags_ci[i]) {
//          var tags_ary_num = (pref.liveTag.ci)? this.tags_ci[i].mems_keys_length_and_set_top() : this.tags_ci[i].mems_keys_length(); // sets key
//          tags_array[tags_array.length] = {key:this.tags_ci[i].key, num:tags_ary_num,
//                                           disp: this.update_pn_disp_func(pref.filter.tag_search.rexps, this.tags_ci[i].key)};
//        }
//      tags_array.sort(this.sort_func);
//      this.key_dirty = Object.create(null);

      var master = this===liveTag;
      var tags_array = this.tags_array_old;
      var dirty_count = 0;
      var dirty_count_deleted = 0;
      var pf_ci = pref.liveTag.ci;
      for (var k_ci in this.key_dirty) { // must enumerate item in prototype.
        if (this.tags_ci[k_ci]) {
          if (master) this.tags_ci[k_ci].num = (pf_ci)? this.tags_ci[k_ci].mems_keys_length_and_set_top() : this.tags_ci[k_ci].mems_keys_length();
//        delete this.key_dirty_creation[k_ci]; // may be respawned, typically by IDB.
        } else dirty_count_deleted++; // CAUTION, this incudes count of creation.
        dirty_count++;
      }
//      for (var k_ci in this.key_dirty_creation) if (this.tags_ci[k_ci]) tags_array[tags_array.length] = this.tags_ci[k_ci];
      if (master) if (dirty_count || pref.test_mode['73']) tags_array.sort(this.sort_func);
////      for (var i in this.tags) tags_array[tags_array.length] = {key:i, num:Object.keys(this.tags[i].mems).length, mems:{}}; // working code.
//////      for (var i in this.tags) { // working code, moved to 'delete_tags'.
//////        var num = Object.keys(this.tags[i].mems).length;
//////        if (num==0){
//////          if (this.tags[i].pn!==null) this.pn.removeChild(this.tags[i].pn);
//////          delete this.tags[i];
//////          if (pref.debug_mode['3']) console.log('Remove: '+i);
//////        } else tags_array[tags_array.length] = {key:i, num:num, mems:{}};
//////      }
////
////      tags_array.sort(this.sort_func);
////
////      if (pref.liveTag.ci) {
////        var tags_ci = Object.create(null);
////        for (var i=0;i<tags_array.length;i++) {
////          var key = tags_array[i].key.toLowerCase();
////          if (!(key in tags_ci)) tags_ci[key] = i;
////          else {
////            var src = this.tags[tags_array[i].key];
////            var ref = this.tags[tags_array[tags_ci[key]].key];
////            var dst = tags_array[tags_ci[key]];
////            for (var j in src.mems) if (!(j in ref.mems)) dst.mems[j] = null;
////            dst.num = Object.keys(ref.mems).length + Object.keys(dst.mems).length;
////            tags_array[i].num = 0;
//////            src.pk = ref.pk;
//////            src.in = ref.in;
//////            src.ex = ref.ex;
//////            ref.ci[tags_array[i].key] = null;
////////            src.ci = {};
////            if (src.pn!==null) { // force to remake to reflect changes next time.
////              if (src.pn!==null) this.pn.removeChild(src.pn);
////              src.pn=null;
////              src.pn_num=0;
////            }
////          }
////        }
////        tags_array.sort(this.sort_func);
////      }
//////      if (pref.debug_mode['3']) console.log(JSON.stringify(tags_array));

      var from_0 = from_initial || dirty_count || !this.update_pn_pos;
//      if (dirty_count) from_initial = true;
      var exam_pn = from_filter || this.update_pn_pos;
      var limit = pref.liveTag.lazy_each;
      var pf = (this.clg? this.clg.pref : pref).filter;
      var rexps = pf.tag_search.rexps;
      var pf_sa = pf.tag_search.showActives;
      var historical_filter_active = rexps || pf_sa || this.update_pn_last_filter_active;
      if (historical_filter_active || dirty_count_deleted) {
        var ref = (from_0)? this.pn.firstChild : this.update_pn_pos[0];
        var pn_count = (from_0)? 0 : this.update_pn_pos[2];
        var docfrag = !ref? document.createDocumentFragment() : null;
//        var docfrag = (rexps && this.pn.firstChild)? null : document.createDocumentFragment();
//        var pos_insert = (from_0)? 0 : this.update_pn_pos[0]; // BUG, up is ok, but CAN'T DOWN by 1
        var pf_boards = pf.tag_search.show_nof_boards;
        for (var i=(from_0)? 0 : this.update_pn_pos[1];i<tags_array.length;i++) {
          var key = tags_array[i].key;
          var key_ci = (pf_ci)? key.toLowerCase() : key;
          var dirty = (key_ci in this.key_dirty);
          var tag_obj = this.tags_ci[key_ci] || !master && dirty && this.prep_NC(key);
          if (tag_obj) {
            if (historical_filter_active) {
              var pn = tag_obj.pn;
              if (exam_pn || dirty) {
                var sa = pf_sa && (tag_obj.in || tag_obj.pk || tag_obj.ex);
                if (!sa && (!rexps || this.update_pn_disp_func(rexps, key_ci)===0)) {
                  if (pn!==null) {
                    if (ref===pn) ref = pn.nextSibling;
                    this.pn.removeChild(pn);
                    tag_obj.pn = null;
                  }
                } else {
                  if (pn!==null) {
                    if (dirty) tag_obj.re_caption(pf_boards);
//                    if (limit>0) if (this.pn.childNodes[pos_insert]!==pn) {
//                      this.pn.insertBefore(pn, this.pn.childNodes[pos_insert] || null); // BUG, up is ok, but CAN'T DOWN by 1
//                      if (--limit==0) this.update_pn_pos = [pos_insert+1,i+1];
//                    }
                  } else if (limit>0) pn = tag_obj.make_pn(pf_boards);
//                    pn = document.createElement('div');
//                    pn.setAttribute('name',key);
//                    pn.innerHTML = 
//                      '<input type="checkbox" name="' + key_ci + '.pk" ' + ((tag_obj['pk'])? 'checked' : '') + '>' + 
//                      '<input type="checkbox" name="' + key_ci + '.in" ' + ((tag_obj['in'])? 'checked' : '') + '>' + 
//                      '<input type="checkbox" name="' + key_ci + '.ex" ' + ((tag_obj['ex'])? 'checked' : '') + '>' +
//                      tag_obj.num +(pf_boards? '/'+Object.keys(this.tags[key].mems_boards()).length:'')+': ' + key; // same in popup_filter
//                    tag_obj.pn = pn;
////                    if (docfrag) docfrag.appendChild(pn);
////                    else this.pn.insertBefore(pn, this.pn.childNodes[pos_insert] || null);
////                    if (--limit==0) this.update_pn_pos = [pos_insert+1,i+1];
//                  }
                  pn_count++; // pos_insert++;
                  if (ref!==pn) {
                    if (limit>0) {
                      if (docfrag) docfrag.appendChild(pn);
                      else this.pn.insertBefore(pn, ref);
                      if (--limit==0) this.update_pn_pos = [ref,i+1, pn_count];
                    }
                  } else if (pn) ref = pn.nextSibling;
                }
              } else if (pn) {
                ref = pn.nextSibling; // pos_insert++;
                pn_count++;
              }
//              var key_old = this.tags_array_old[i].key; // STILL BUG, tags_array_old is obsolete in 2nd or later loop.
//              var key_old_ci = (pref.liveTag.ci)? key_old.toLowerCase() : key_old;
//              if (this.key_dirty[key_old_ci]===null && pn) {
//                pos_insert++;
//                this.key_dirty[key_old_ci] = 1;
//              }
//              if (this.key_dirty[key_ci]===1) pos_insert--;
            }
          } else if (master) tags_array.splice(i--,1); // for deleted tags, they weren't removed in 'delete_tags'.
          if ((dirty_count<=0 || dirty && --dirty_count<=0) && (!historical_filter_active || limit<=0)) break;
        }
        if (limit<=0 && this.update_pn_pos[1]<tags_array.length) this.update_pn_1_lazy.delayed_do(pref.liveTag.lazy_delay);
        else this.update_pn_pos = null;
        if (docfrag) this.pn.appendChild(docfrag);
        this.pn_summary.textContent = (rexps)? pn_count+(this.update_pn_pos?'...':'')+'/'+tags_array.length : ''; // pos_insert+'/'+tags_array.length : '';
      }

//      var historical_filter_active = pref.filter.tag_search.rexps.length!=0 || this.update_pn_last_filter_active; // working code.
//      if (historical_filter_active || dirty_count_deleted) {
//        if (pref.debug_mode['34']) {console.time('update_pn');console.time('update_pn2');}
//        var docfrag = (pref.filter.tag_search.rexps.length!==0 && this.pn.firstChild)? null : (pref.test_mode['74'])? this.pn.cloneNode() : document.createDocumentFragment();
//        var pos_insert=0;
//        for (var i=0;i<tags_array.length;i++) {
//          var key = tags_array[i].key;
//          var key_ci = (pref.liveTag.ci)? key.toLowerCase() : key;
//          var dirty = (key_ci in this.key_dirty);
//          var tag_obj = this.tags_ci[key_ci];
//          if (tag_obj) {
//            var pn = tag_obj.pn;
//            if (from_filter || dirty) {
//              var disp = this.update_pn_disp_func(pref.filter.tag_search.rexps, key_ci);
//              if (pn!==null) {
//                if (disp===0) { // for passive virtual boarding.
// //            if (tags_array[i].num===0 || tags_array[i].disp===0) {
//                  this.pn.removeChild(pn);
//                  tag_obj.pn = null;
//                } else {
//                  if (dirty) tag_obj.pn.childNodes[3].textContent = tag_obj.num + ': ' + key;
//                  if (this.pn.childNodes[pos_insert]!==pn) this.pn.insertBefore(pn, this.pn.childNodes[pos_insert] || null);
//                  pos_insert++;
//                }
//              } else if (disp==1) { // for passive virtual boarding.
//    //          } else if (tags_array[i].num!=0 && tags_array[i].disp==1) {
//                pn = document.createElement('div');
//                pn.setAttribute('name',key);
////                pn = document.createElement('span');
//                pn.innerHTML = ((pref.test_mode['101'])? '<span>P</span><span>I</span><span>E</span>'
//                                : '<input type="checkbox" name="' + key_ci + '.pk" ' + ((tag_obj['pk'])? 'checked' : '') + '>' +
//                                '<input type="checkbox" name="' + key_ci + '.in" ' + ((tag_obj['in'])? 'checked' : '') + '>' +
//                                '<input type="checkbox" name="' + key_ci + '.ex" ' + ((tag_obj['ex'])? 'checked' : '') + '>') +
//                  tag_obj.num + ': ' + key;
////                               '<span name="' + key + '">' + tag_obj.num + ': ' + key + '</span><br>';
//                if (docfrag) docfrag.appendChild(pn);
//                else this.pn.insertBefore(pn, this.pn.childNodes[pos_insert] || null);
//                pos_insert++;
////                this.pn.insertBefore(pn, this.pn.childNodes[pos_insert++] || null);
//                tag_obj.pn = pn;
//              }
//            } else if (pn) pos_insert++;
//          } else {
//            tags_array.splice(i--,1); // for deleted tags, wasn't checked in 'delete_tags'.
////            if (--dirty_count_deleted<=0 && !historical_filter_active) break; // redundant.
//          }
//          if (dirty && --dirty_count<=0 && !historical_filter_active) break;
//        }
//        if (pref.debug_mode['34']) console.timeEnd('update_pn');
//        if (docfrag) { // no effect
//          if (pref.test_mode['74']) { // no effect
//            this.pn.parentNode.replaceChild(docfrag, this.pn);
//            this.pn = docfrag;
//          } else this.pn.appendChild(docfrag);
//        }
//        if (pref.debug_mode['34']) console.timeEnd('update_pn2');
//        this.pn_summary.textContent = (pref.filter.tag_search.rexps.length!==0)? pos_insert+'/'+tags_array.length : '';
//      }

////      var j=0;
//      var pos_insert=0; // working code.
//      for (var i=0;i<tags_array.length;i++) {
//        var key = tags_array[i].key;
//        if (!this.tags[key]) {tags_array.splice(i--,1);continue;} // for deleted tags, didn't checked in 'delete_tags'.
//        var num = tags_array[i].num;
//        var num_old = this.tags[key].pn_num;
//        if (num!==num_old || from_filter) {
//          var str = num + ': ' + key;
//          this.tags[key].pn_num = num;
//          var pn = this.tags[key].pn;
//          if (pn!==null) {
//            if (tags_array[i].disp===0) { // for passive virtual boarding.
////            if (tags_array[i].num===0 || tags_array[i].disp===0) {
//              this.pn.removeChild(pn);
//              this.tags[key].pn = null;
//            } else {
//              this.tags[key].pn.childNodes[3].textContent = str;
////              if (this.tags_array_old[j] && key===this.tags_array_old[j].key) j++;
////              else {
//              if (this.pn.childNodes[pos_insert]!==pn) this.pn.insertBefore(pn, this.pn.childNodes[pos_insert] || null);
//              pos_insert++;
////                this.pn.insertBefore(pn, this.pn.childNodes[pos_insert++] || null);
////                if (pref.debug_mode['3']) console.log('Insert: '+((num>num_old)?'promote: ':'demote: ')+key+', '+num_old+' -> '+num+', '+i+', '+j+', '+num_of_skip);
////                if (num<num_old) num_of_skip--;
////              }
//            }
//          } else if (tags_array[i].disp==1) { // for passive virtual boarding.
////          } else if (tags_array[i].num!=0 && tags_array[i].disp==1) {
//            pn = document.createElement('span');
//            pn.innerHTML = '<input type="checkbox" name="' + key + '.pk" ' + ((this.tags[key]['pk'])? 'checked' : '') + '>' + 
//                           '<input type="checkbox" name="' + key + '.in" ' + ((this.tags[key]['in'])? 'checked' : '') + '>' + 
//                           '<input type="checkbox" name="' + key + '.ex" ' + ((this.tags[key]['ex'])? 'checked' : '') + '>' +
//                           '<span name="' + key + '">' + str + '</span><br>';
//            pn.childNodes[0].onchange = this.cbx_onchange_entry;
//            pn.childNodes[1].onchange = this.cbx_onchange_entry;
//            pn.childNodes[2].onchange = this.cbx_onchange_entry;
////            pn.childNodes[0].onmouseover = this.tooltip;
////            pn.childNodes[1].onmouseover = this.tooltip;
////            pn.childNodes[2].onmouseover = this.tooltip;
////            pn.childNodes[3].onmouseover = this.tooltip;
////            pn.style = {};
////            pn.style.display = (this.pn_filter_rexp===null || this.pn_filter_rexp.test(key))? '' : 'none';
//            this.pn.insertBefore(pn, this.pn.childNodes[pos_insert++] || null);
//            this.tags[key].pn = pn;
////            for (var j in this.tags[key].tgts) this.tags[j].pn = pn;  // PATCHES CAN BE REMOVED
//          }
//        } else if (tags_array[i].disp==1) pos_insert++;
//////        } else { // tracking // working code.
//////          while (j<this.tags_array_old.length && key!==this.tags_array_old[j].key) {
//////            if (this.tags[this.tags_array_old[j].key] && this.tags[this.tags_array_old[j].key].pn_num==this.tags_array_old[j].num) num_of_skip++; // removed(to be 0) while waiting or not processed yet(demote)
////////            if (pref.debug_mode['3']) {
////////              var debug_str = this.tags_array_old[j].key+', '+this.tags_array_old[j].num+', '+num_of_skip;
////////              if (this.tags[this.tags_array_old[j].key]===undefined) console.log('Removed: '+debug_str);
////////              else if (this.tags[this.tags_array_old[j].key].pn_num==this.tags_array_old[j].num) console.log('Skip: '+debug_str);
////////            }
//////            j++;
//////          }
//////          j++;
//////        }
//      }
//      this.tags_array_old = tags_array;
//      this.key_dirty = Object.create(null);
//      this.key_dirty_creation = Object.create(Object.create(null));
//      this.key_dirty = Object.create(this.key_dirty_creation);
//      if (pref.debug_mode['3']) { // CHECKER, working code.
//        var flag = true;
//        var dom = [];
//        for (var i=0;i<tags_array.length;i++) {
//          if (tags_array[i].num==0) break;
//          var tgt = this.pn.childNodes[i].childNodes[3].textContent;
//          var key = tgt.replace(/[^:]+: /,'');
//          dom[dom.length] = key;
//          if (tags_array[i].key!==key) {
//            var j=0;
//            while (j<tags_array.length && tags_array[j].key!==key) j++;
//            if (j==tags_array.length) j=-1;
//            var k=0;
//            while (k<this.tags_array_old.length && this.tags_array_old[k].key!==key) k++;
//            if (k==this.tags_array_old.length) k=-1;
//            console.log('ERROR: '+tgt+', should be '+j+', but '+i+', '+k+' in old');
//            flag = false;
//          }
//        }
//        if (!flag) {
//          console.log(tags_array);
//          console.log(dom);
//          console.log(this.tags_array_old);
//        }
//      }

//      this.pn.innerHTML = '';
//      for (var i=0;i<tags_array.length;i++) if (tags_array[i].num!=0) {
//        var item = document.createElement('span');
//        var key = tags_array[i].key;
//        var str  = tags_array[i].num + ': ' + key;
//        item.innerHTML = '<input type="checkbox" name="' + key + '.pk" ' + ((this.tags[key].pk)? 'checked' : '') + '>' +
//                         '<input type="checkbox" name="' + key + '.in" ' + ((this.tags[key].in)? 'checked' : '') + '>' +
//                         '<input type="checkbox" name="' + key + '.ex" ' + ((this.tags[key].ex)? 'checked' : '') + '>' +
//                         '<span name="' + key + '">' +  str + '</span><br>';
//        item.childNodes[0].onchange = this.cbx_onchange_entry;
//        item.childNodes[1].onchange = this.cbx_onchange_entry;
//        item.childNodes[2].onchange = this.cbx_onchange_entry;
//        item.childNodes[3].onmouseover = pref_func.tooltips.show;
//        this.pn.appendChild(item);
//      }
      this.update_pn_last_filter_active = rexps || pf_sa;
      if (master) {
//        this.tags_array_old = tags_array;
        if (pref.virtualBoard.bl.show && (!from_filter || pref.virtualBoard.bl.showActives)) this.update_boardlist();
        if (this.subscribers.length!=0) {
          for (var i=0;i<this.subscribers.length;i++) for (var t in this.key_dirty) this.subscribers[i].key_dirty[t] = null;
          if (this.update_pn_pos===null) if (!from_filter && from_initial) for (var i=0;i<this.subscribers.length;i++) this.subscribers[i].update_pn_buf.delayed_do();
        }
        if (this.sub_tagSearch) if (this.update_pn_pos===null) this.sub_tagSearch();
      } else if (!rexps) this.unsubscribe();
      this.key_dirty = Object.create(null);
    },
    subscribers: [],
    subscribe: function(){ // proxy is 5-8 times slower, I don't use as much as possible. http://dealwithjs.io/es6-features-10-use-cases-for-proxy/
      if (!this.NC) {
        this.tags_ci = {};
        this.tags = Object.create(this.tags_ci);
      }
      for (var tag in liveTag.tags) this.prep_NC(tag);
//      for (var i=0;i<this.tags_array_old.length;i++) this.prep_NC(this.tags_array_old[i].key);
      liveTag.subscribers.push(this);
      this.subscribed = true;
    },
    unsubscribe: function(force){
      if (force) for (var t in this.tags_ci) if (this.tags_ci[t].pn) this.tags_ci[t].pn.remove();
      if (this.NC) {
        for (var t in this.tags_ci) if (!this.tags_ci[t].pk && !this.tags_ci[t].in && !this.tags_ci[t].ex) delete this.tags_ci[t];
        for (var t in this.tags)    if (!this.tags[t].pk    && !this.tags[t].in    && !this.tags[t].ex)    delete this.tags[t];
      } else {
        delete this.tags_ci;
        delete this.tags;
      }
      liveTag.subscribers.splice(liveTag.subscribers.indexOf(this),1);
      this.subscribed = false;
    },
    prep_NC: function(tag){ // NotConsolidated // tag is the prefer, but NC catalog doesn't track all tags in update_tags_in_th_sub, so it needs existence check 'if (!this.tags_ci[tag_ci]) this.prep_NC(tag)'.
//      console.log('prep_NC: '+tag);
      var tobj = this.tags[tag]; // emulation of update_tags_in_th_sub
      if (!tobj) {
        var tag_ci = (pref.liveTag.ci)? tag.toLowerCase() : tag;
        tobj = this.tags_ci[tag_ci];
        if (!tobj) {
          var gto = liveTag.tags_ci[tag_ci];
          if (!gto) return;
          tobj = this.tags_ci[tag_ci] = this.NC? {pn:null, pk:false,  in:false,  ex:false,  __proto__:gto}
                                               : {pn:null, pk:gto.pk, in:gto.in, ex:gto.ex, __proto__:gto}; // pk,in,ex are stored here, but copy in cbx_onchange
        }
        this.tags[tag] = tobj;
      }
      return tobj;
//      if (this===liveTag) return; // safe for lazy draw. // working code
//      var tag_ci = (pref.liveTag.ci)? tag.toLowerCase() : tag;
//      if (!liveTag.tags_ci[tag_ci]) return;
//      if (!this.tags_ci[tag_ci]) {
//        var tobj = liveTag.tags_ci[tag_ci];
//        this.tags_ci[tag_ci] = this.NC? {pn:null, pk:false,   in:false,   ex:false,   __proto__:tobj}
//                                      : {pn:null, pk:tobj.pk, in:tobj.in, ex:tobj.ex, __proto__:tobj}; // pk,in,ex are stored here, but copy in cbx_onchange
//      }
//      if (!this.tags[tag]) this.tags[tag] = this.tags_ci[tag_ci];
    },
    factory: function(clg, footer, NC){
      var lt = {
        clg: clg, 
        footer: footer,
        pn: null,
        pn_summary: null,
        NC: NC,
        subscribed: false,
        key_dirty: Object.create(null),
        __proto__: liveTag // this
      };
      lt.update_pn_buf = new DelayBuffer(liveTag.update_pn.bind(lt), pref.liveTag.disp_delay);
      lt.update_pn_1_lazy = new DelayBuffer(liveTag.update_pn_1.bind(lt, true), pref.liveTag.lazy_delay);
      if (NC) {
        lt.tags_ci = {};
        lt.tags = Object.create(lt.tags_ci);
        lt.active = {pk:0, in:0, ex:0};
        lt.actives = Object.create(null);
      }
      return lt;
    },
    popup_filter: (function(){
      var oninput_funcs = {
        'filter.tag_search.str': function(){
          if (this['.']) this['.'].liveTag.filter_onchange(this['.'].pref.filter.tag_search, null, true);
          else liveTag.filter_onchange(pref.filter.tag_search, null, true);
        },
        __proto__: pref_func.settings.oninput_funcs.__proto__
      };
      var onchange_funcs = {
        'filter.tag_search.re': oninput_funcs['filter.tag_search.str'],
        'filter.tag_search.showActives': oninput_funcs['filter.tag_search.str'],
        'filter.tag_search.show_nof_boards': function(){
          var pf_boards = (this['.'] && this['.'].pref || pref).filter.tag_search.show_nof_boards;
          var lT = this['.'] && this['.'].liveTag || liveTag;
          for (var i=0;i<lT.tags_array_old.length;i++) {
            var key = lT.tags_array_old[i].key;
            var tag_obj = lT.tags[key];
            if (tag_obj && tag_obj.pn) tag_obj.re_caption(pf_boards);
          }
        },
        __proto__: oninput_funcs
      };
      return {
        html:
          '<div style="float:left">'+
            '<div>'+
              '<ICN"filter.tag">Tag :'+
            '</div>'+
            '<div style="text-align:right">'+
              '<a name="SHOW5" style="cursor:pointer" data-show5="tagButtons" data-str="[\u25b2]">[\u25bc]</a>'+
            '</div>'+
          '</div>'+
          '<div style="float:left">'+
            '<div>'+
              ' Fetch/In/Out/Stats:Tag '+
              '<ITBL25"filter.tag_search.str" placeholder="Search tags...">'+
  //            ' <ICBX"filter.tag_search.ci">CI'+
              ' <ICN"filter.tag_search.re">RE'+
              ' <ICN"filter.tag_search.showActives">ShowActives'+
            '</div>'+
            '<div style="display:none" data-show5="tagButtons">'+
              '<BTN"tagB.pk,F"><BTN"tagB.in,I"><BTN"tagB.ex,O"> <SE"filter.tagBFunc,Clear,Toggle"> checkboxes of <SE"filter.tagBTgts,all,shown"> tags, '+
//              '<span style="display:inline-block">'+
//                '<BTN"tagB.all_c_pk,C"><BTN"tagB.all_c_in,C"><BTN"tagB.all_c_ex,C"> Clear checkboxes of all tags<br>'+
//                '<BTN"tagB.shown_c_pk,C"><BTN"tagB.shown_c_in,C"><BTN"tagB.shown_c_ex,C"> Clear checkboxes of shown tags<br>'+
//                '<BTN"tagB.shown_t_pk,T"><BTN"tagB.shown_t_in,T"><BTN"tagB.shown_t_ex,T"> Toggle checkboxes of shown tags<br>'+
//              '</span>'+
//              '<span>'+
              '<IC"filter.tag_search.show_nof_boards">Show number of boards'+
//              '</span>'+
////              '<a name="HIDE3" style="cursor:pointer">[\u25b2]</a>'+
            '</div>'+
            '<div style="overflow:auto;resize:both;display:inline-block;height:16px;width:250px" name="filter.tag_list"></div>'+
            '<div style="display:inline-block"><span></span></div>'+
//            '<a name="SHOW3" style="cursor:pointer" data-show3="tagButtons">[\u25bc]</a>'+
            pref_func.settings.html_funcs.rollup('<div style="clear:both">'+
              '<BTN"tag2bList,Add"> selected tags to the last of board group by <SEC"filter.tag2bList.by,tag,board"><br>'+
              '1,Label: <ITBL20"filter.tag2bList.label" placeholder="default: topmost tag"><br>'+
              '1,<IC"filter.tag2bList.rm_nvb">Remove native virtual board<br>'+
//              '1,<IR"filter.tag2bList.by,tag">By tag<br>'+
//              '1,<IR"filter.tag2bList.by,board">By board<br>'+
            '</div>')+
  //          '<a name="SHOW" style="cursor:pointer" data-str="[\u25b2]">[\u25bc]</a>'+
  //          '<div name="SUB" style="clear:both;display:none">TEST</div>'+
          '</div>'+
          '<div style="clear:both"></div>', // for floating
        oninput_funcs: oninput_funcs,
        onchange_funcs: onchange_funcs,
        embed: function(pn_redirect){
          if (liveTag.pn) return;
          var pn = document.createElement('div');
          pn.innerHTML = '<div>' + pref_func.format_html_str(this.html) + '</div>';
          pn.appendChild(pn_redirect);
          liveTag.setup_pn(pn, pref.filter.tag_search);
          var tack = cnst.set_tack_float2(pn.firstChild);
//          var tack = site2[site.nickname].make_tack();
//          tack.onclick = cnst.tack_float_nSblgs;
//          tack.setAttribute('style','float:right;font-size:2em');
//          pn.insertBefore(tack, pn.firstChild);
          pref_func.add_onchange(pn, onchange_funcs, oninput_funcs);
          pref_func.apply_prep(pn, false);
          Tooltips.add_root(pn);
          liveTag.update_pn(true);
          return pn;
        },
        popup: function(){ // will be discarded
          if (liveTag.pn) return;
          var exit_func = function(){
            Tooltips.remove_root(popup.pn);
            liveTag.pn = null;
            cnst.div_destroy(popup.pn, true);
          };
          var popup = cnst.init3({func_str:'top:0px:left:0px:Show:tb', exit:exit_func});
          popup.cn.innerHTML = pref_func.format_html_str(liveTag.popup_filter.html);
          pref_func.add_onchange(popup.cn,onchange_funcs, oninput_funcs);
          pref_func.apply_prep(popup.pn, false);
          Tooltips.add_root(popup.pn);
          liveTag.update_pn(true);
        },
      };
    })(),
    cbx_all_onchange: function(e){ // CAUTION, this refers onchange_funcs
      var this_lt = this['.'] && this['.'].liveTag || liveTag;
      var et = e.target;
      var clear = !this['.'].pref.filter.tagBFunc; // et.name.substr(-4,1)==='c';
      var prop = et.name.slice(-2);
      var all = !this['.'].pref.filter.tagBTgts; // et.name.substr(-8,3)==='all';
      var tgts_all = {};
      for (var tag_ci in this_lt.tags_ci) {
        var obj = this_lt.tags_ci[tag_ci];
        if (all || obj.pn) if (!clear || obj[prop]) {
          this_lt.cbx_emulate_inv(tag_ci, prop, true);
          this_lt.update_tag_node(tag_ci);
          if (prop==='pk' && obj[prop]) obj.mems_boards(tgts_all);
        }
      }
      if (Object.keys(tgts_all).length!=0) scan.scan_ui('refresh_tag', {tgts: tgts_all, options:{callback:function(){scan.scan('t');}, priority:6}});
      this_lt.filter_changed_broadcast();
//      if (cataLog.catalog_filter_changed) cataLog.catalog_filter_changed();
    },
    cbx_emulate_inv: function(tag, prop, no_scan, sync_only){
//      var obj = this.tags[tag];
//      var val = !obj[prop];
//      obj[prop] = val;
//      if (obj.pn!==null) obj.pn.childNodes[prop==='pk'? 0 : prop==='in'? 1 : 2].checked = val;
      this.cbx_onchange(tag, prop, this.tags[tag].inv_val(prop), no_scan, sync_only);
    },
////    cbx_onchange_entry : function(e){liveTag.cbx_onchange(this, e, true);},
////    cbx_onchange : function(sender, e, from_cbx){ // working code.
////      var prop = sender.name.substr(-2,2);
//////      var val = e.target.checked;
////      var val = sender.checked;
////      var tag = sender.name.substr(0,sender.name.length-3);
    cbx_onchange_entry: function(e){
      var et = e.target;
      var tag = et.parentNode.getAttribute('name');
      var tag_ci = (pref.liveTag.ci)? tag.toLowerCase() : tag;
      var prop = et.name;
      var tobj = this.prep_NC(tag);
//      if (!this.tags_ci[tag_ci]) this.prep_NC(tag);
      var val = et.checked;
      tobj[prop] = val;
      if (prop==='pk' && val && !tobj['in']) this.cbx_emulate_inv(tag_ci, 'in');
      this.cbx_onchange(tag_ci, prop, val);
      this.filter_changed_broadcast();
//      if (cataLog.catalog_filter_changed) cataLog.catalog_filter_changed();
      this.update_tag_node(tag);
    },
    cbx_onchange : function(tag, prop, val, no_scan){
//      this!==liveTag && !this.NC && this.subscribed) liveTag.cbx_emulate_inv(tag, prop, no_scan, true); // ONLY WATCHER TO THE PRIMARY VIEW IS SYNCED.
      this.active_inc_dec(prop, val, pref.liveTag.ci? tag.toLowerCase() : tag); // this.active[prop] += (val)? 1 : -1;
      if (prop==='pk') {
//        if (val && !this.tags[tag]['in']) this.cbx_emulate_inv(tag, 'in'); // moved to cbx_onchange_entry
////        if (val && !this.tags[tag]['in']) {
////          this.tags[tag]['in'] = true;
////          this.active['in'] += 1;
////          if (this.tags[tag].pn!==null) this.tags[tag].pn.childNodes[1].checked = true;
//////          this.update_pn_buf.delayed_do();
////        }
////        var tgts = {};
//////        var now = Date.now();
//////        var prev = now - pref.liveTag.pickup_interval*1000;
//////        for (var i in this.tags)
//////          if (this.tags[i]['pk'] && (this.tags[i]['pk']==='true' || this.tags[i]['pk']<prev)) {
//////            this.tags[i]['pk']=now;
//////            for (var j in this.tags[i].mems) tgts[j.substr(0,j.lastIndexOf('/')+1)]=null;
//////          }
//////        if (val) for (var i in this.tags[tag].tgts) for (var j in this.tags[i].mems) tgts[j.substr(0,j.lastIndexOf('/')+1)]=null; // working code.
//////        if (val) for (var j in this.tags[tag].mems) tgts[j.substr(0,j.lastIndexOf('/')+1)]=null;
////        if (val) for (var j of this.tags[tag].mems.keys()) tgts[j.key.substr(0,j.key.lastIndexOf('/')+1)]=null; // working code.
        if (val && !no_scan && gClg) {
          var tgts = Object.keys(this.tags[tag].mems_boards());
          if (tgts.length>0) scan.scan_ui('refresh_tag', {tgts: tgts, options:{callback:function(){scan.scan('t');}, priority:6}});
        }
      }
      this.maintain_cbx_and_draw(prop, val);
      if (!this.NC) {
        var clgs = gClg.Clgs;
        for (var i=0;i<clgs.length;i++) {
          var tgt_lt = clgs[i].liveTag;
          if (this!==tgt_lt && !tgt_lt.NC && (tgt_lt===liveTag && this.subscribed || tgt_lt.subscribed && tgt_lt.prep_NC(tag))) { // prep_NC is a patch for changing board_list and create and find new tag like #c.
//            if (tgt_lt.subscribed && !tgt_lt.tags[tag]) tgt_lt.prep_NC(tag); // patch for changing board_list and create and find new tag like #c. // moved to 1 line above
            tgt_lt.maintain_cbx_and_draw(prop, tgt_lt.tags[tag].inv_val(prop));
//          tgt_lt.cbx_emulate_inv(tag, prop, no_scan, true);
          }
        }
      }
    },
    maintain_cbx_and_draw: function(prop, val){
      if (prop==='in' || prop==='ex') {
        if (this.active['in']+this.active['ex']===((val)? 1 : 0)) {
//        if (this.active['in']===((val)? 1 : 0)) {
          if (this.clg) { // checking this.clg for tagSearch in index of 8chan
            this.clg.pref.filter.tag = val; // may toggle twice if func==='inex', but leave this because func==='inex' isn't used so much.
            this.clg.components.tag_use.checked = val;
//            if (this!==liveTag && !this.NC) liveTag.clg.components.tag_use.checked = val; // ONLY WATCHER TO THE PRIMARY VIEW IS SYNCED.
//          if (pref_func.mirror_targets.pn12_0_4)
//            pref_func.apply_prep(pref_func.mirror_targets.pn12_0_4.getElementsByTagName('input')['filter.tag'],false);
          }
        }
      }
      if (!this.NC && pref.virtualBoard.bl.showActives || (this.clg? this.clg.pref : pref).filter.tag_search.showActives) this.update_pn_1_lazy.delayed_do(); // patch
//      if (catalog_obj && catalog_obj.catalog_func()!=null) catalog_obj.catalog_func().catalog_filter_changed();
    },
    active_inc_dec: function(prop, inc, tag_ci){
      this.active[prop] += inc? 1 : -1;
      if (!this.actives[tag_ci]) {if (inc) this.actives[tag_ci] = true;}
      else if (!inc) {
        var tobj = this.tags_ci[tag_ci];
        if (!tobj['in'] && !tobj['pk'] && !tobj['ex']) delete this.actives[tag_ci];
      }
    },
    add_tags_to_boardlist: function(){ // CAUTION. this means clg.onchange_funcs
      var arr = [];
      var lt = this['.'].liveTag;
      var tags = lt.tags_array_old;
      if (this['.'].pref.filter.tag2bList.by==='tag') tags.forEach(to=>lt.enumerate_tags_state_1(arr, to, to.key));
      else {
        var result = new Set();
        var obj_ex = {};
        var rm_nvb = this['.'].pref.filter.tag2bList.rm_nvb;
        tags.forEach(v=>{
          if (v['in']||v['pk']) for (var i in v.mems_boards({})) if (!rm_nvb || !site2[i.slice(0,i.indexOf('/'))].nativeVirtualBoard) result.add(i);
          if (v['ex']) v.mems_boards(obj_ex);
        });
        for (var i of result) if (!(i in obj_ex)) arr[arr.length] = i;
//        var obj = {}; // order can't be specified.
//        tags.forEach(v=>(v['in']||v['pk']) && v.mems_boards(obj));
//        var obj_ex = {};
//        tags.forEach(v=>v['ex'] && v.mems_boards(obj_ex));
//        for (var i in obj_ex) delete obj[i];
//        arr = Object.keys(obj);
      }
      if (arr.length==0) return;
      var label = this['.'].pref.filter.tag2bList.label || (tags.filter(v=>v['in']||v['pk'])[0] || tags.filter(v=>v['ex'])[0]).key;
      pref_func.settings.onchange_funcs['tag.add_to_list'](label +', '+ arr.join(','));
    },
//    add_tags_to_boardlist: function(){ // working code
//      var tags = liveTag.tags_array_old;
//      var label = pref.filter.tag2bList.label || (tags.filter(function(v){return v.in;})[0] || tags.filter(function(v){return v.pk;})[0] || tags.filter(function(v){return v.ex;})[0]).key;
//      var str_ary;
//      if (pref.filter.tag2bList.by==='tag') str_ary = [
//        tags.filter(function(v){return v.in;}).map(function(v){return v.key;}).join(','),
//        tags.filter(function(v){return v.pk;}).map(function(v){return '#'+v.key;}).join(','),
//        tags.filter(function(v){return v.ex;}).map(function(v){return '!'+v.key;}).join(',')];
//      else {
//        var obj_in = tags.filter(function(v){return v.in;}).map(function(v){return v.mems_boards();});
//        var obj_pk = tags.filter(function(v){return v.pk;}).map(function(v){return v.mems_boards();});
//        var obj_ex = tags.filter(function(v){return v.ex;}).map(function(v){return v.mems_boards();});
//        var obj = {};
//        for (var i=0;i<obj_in.length;i++) for (var j in obj_in[i]) obj[j] = null;
//        for (var i=0;i<obj_pk.length;i++) for (var j in obj_pk[i]) obj[j] = null;
//        for (var i=0;i<obj_ex.length;i++) for (var j in obj_ex[i]) delete obj[j];
//        str_ary = Object.keys(obj);
//      }
//      pref_func.settings.onchange_funcs['tag.add_to_list'](label +', '+ str_ary.filter(function(v){return v;}).join(','));
//    },
    search_by_tags : function(lth){ // tuned for many ins and less exes.
      var retval = !this.active.in;
      var tags = lth.tags; // all tags in an array
      if (!retval)                  for (var i=0;i<tags.length;i++) if ((tags[i] in this.tags) && this.tags[tags[i]]['in']) {retval = true; break;}
      if (retval && this.active.ex) for (var i=0;i<tags.length;i++) if ((tags[i] in this.tags) && this.tags[tags[i]]['ex']) return false;
      return retval;
    },
//    search_by_tags : function(tags){ // working code.
//      var retval = !this.active.in;
//      if (this.active.in)           for (var j=0;j<2;j++) if (tags[j]) for (var i=0;i<tags[j].length;i++) if ((tags[j][i] in this.tags) && this.tags[tags[j][i]]['in']) {retval = true; break;}
//      if (retval && this.active.ex) for (var j=0;j<2;j++) if (tags[j]) for (var i=0;i<tags[j].length;i++) if ((tags[j][i] in this.tags) && this.tags[tags[j][i]]['ex']) {retval = false; break;}
//      return retval;
//    },
    keys_fix: null, // to reduce memory consumption. THIS IS USED GLOBALLY.
    prep_tags: function(th, retag){ // prepare tag holder and extract tags in op.
//      if (this.mems[th.domain] && this.mems[th.domain][th.board] && this.mems[th.domain][th.board][th.no]) return this.mems[th.domain][th.board][th.no];
//      else {
      var lth = this.mems.init(th);
      var watch = lth.wt;
      if ((watch[0]&0x00010000) && (pref.liveTag.watch_all && th.time_created) ||
          (watch[0]&0x04840000)===0x00800000 && (th.time_created>0||th.time_posted>0)) {// for meguca or those don't have time at first.
        pClg.set_watch_time_thread(th.key, th.time_created, th.time_posted, watch, false, th.nof_posts); // refer pClg, set_watch_time_thread uses pref.filter.time.use in get_mark_time, this is NOT individual.
        if (pref.archive.store_watched && lth.watched && !lth.archived) archiver.start_1('ARC',lth);
      }
      if ((watch[0]&0x00010000) || retag) {
        if (watch[0]&0x00010000) {
          if (!pref.test_mode['64'] && pref[cataLog.embed_mode].deleted_posts.merge) archiver.prep_deleted_posts(th,lth);
          if (!pref.test_mode['67']) archiver.check_op(th, lth);
//          if (pref.liveTag.watch_all && th.time_created) cataLog.set_watch_time_thread(th.key, cataLog.embed_mode, th.time_created, th.time_posted, th, watch);
          watch[0] = (watch[0]&0xfffe0000);
        }
//        if (pref.liveTag.watch_all && th.time_created) { // working code.
//          var time_watch = catalog_obj.catalog_func().get_watch_time_of_a_thread(th.key, th.time_created, th.time_posted || th.time_bumped, true);
//          if (time_watch) site2[th.domain].check_reply.set_watch_time(watch, time_watch);
//        }
////        if (tag[2][0]===-1) tag[2][0]=1; // patch. This may be redundant.
//        if (tag[2][0]&0x00010000) tag[2][0] &= 0xfffe0000; // there is a path which doesn't throuth 'check_reply'. // redundant, do it in 'check_reply.check_t1_op'
////        if (watch[0]!==0) tag[2][2]=1; // PATCH with BUG, if op contains replies to me, this way can't find it until the thread gets new replies.
        var pf = pref.liveTag;
        var tags = [[],[]]; // first time only.
        if (pref.liveTag.inherit_board_name) { // working code.
          var idx0 = (pref.liveTag.lock_board_name)? 0 : 1;
          tags[idx0][0] = lth.btag; // '#'+th.board.replace(/\//g,'');
        }
        if (pref.liveTag.inherit_board_tags && lth.btag2) {
          var idx1 = (pref.liveTag.lock_board_tags)? 0 : 1;
          tags[idx1] = tags[idx1].concat(lth.btag2);
        }
        var tags_op = site2[th.domain].check_reply.check_t1_op(th); // extracting tags in op is redundant when tags are NOT locked, but locked extracting is only here, so this can't be removed.
        if (tags_op.length!=0) {
          var idx2 = (pref.liveTag.lock_tags_in_op)? 0 : 1;
          tags[idx2] = tags[idx2].concat(tags_op);
//          tags[idx2] = tags_op.concat(tags[idx2]);
        }
        if (th.tags && th.tags.length!=0 && !pref.test_mode['153']) {
          if (th.tags_OP) tags[0] = tags[0].concat(th.tags_OP);
          tags[1] = tags[1].concat(th.tags);
        }
        this.exclude_tags(th, tags[0], tags[1]);
//        if (tags_old) for (var i=0;i<2;i++) if (tags_old[i]) tags[i] = tags[i].concat(tags_old[i]);
        if (th.parse_funcs.has_editing && th.posts[0].editing && tags[0].length!=0) tags[0] = this.extract_tags_trim_editing(th, lth, tags[0]);
        if (tags[0].length!=0) lth.t0 = this.update_tags_in_th(tags[0], null, {}, (tags[0].length < pref.liveTag.max)? tags[0].length : pref.liveTag.max, th, watch, this.mems[th.domain][th.board]); // update this.keys_fix
        if (tags[1].length!=0) this.extract_tags(th, tags[1], this.keys_fix);
        else {
          if (lth.t1!==null) lth.t1 = null;
          this.update_pn_buf.delayed_do();
        }
      }
      return lth;
    },
    update_tags_in_editing_posts: function(th, lth, ed_f){ // called only when th.parse_funcs.has_editing === true.
      var posts_obj = th.parse_funcs.posts_obj(th,lth.ed_t[lth.ed_t.length-1].no);
      var flag_added = false;
      for (var i=lth.ed_t.length-1;i>=0;i--) if (typeof(lth.ed_t[i])!=='string') {
        var post = posts_obj[lth.ed_t[i].no];
        if (post && !post.editing) {
          if (pref.liveTag.from==='post' || post.no==th.no) {
            site2[th.domain].wrap_to_parse.posts({posts:[post], __proto__:th});
            var extracted_tags = site2[th.domain].check_reply.check_t1_op(post);
            if (extracted_tags.length!=0) flag_added = true;
            lth.ed_t = lth.ed_t.slice(0,i).concat(extracted_tags).concat(lth.ed_t.slice(i+1));
          } else lth.ed_t.splice(i,1);
          ed_f[ed_f.length] = post;
        }
      }
      if (ed_f.length==0) return undefined;
      if (pref.debug_mode['24']) console.log(th.key+': retag_req: '+extracted_tags+', '+flag_added+', '+(lth.ed_t && lth.ed_t.map(function(v){return (typeof(v)==='string')? v : v.no+'('+v.time+')';})));
      if (lth.ed_t.length==0) {
        lth.ed_t = null;
        return undefined;
      }
      return (flag_added)? this.extract_tags(th, lth.ed_t, null, null, true) : // has extracted_tags always.
        (extracted_tags)? this.extract_tags_trim_editing(th, lth, lth.ed_t, true) && undefined : // return undefined for not update intentionally.
        undefined;
    },
    extract_tags_trim_editing: function(th, lth, extracted_tags, retag_editing){
      var ex_tags_keep = null;
      for (var i=extracted_tags.length-1;i>=0;i--) if (typeof(extracted_tags[i])!=='string') { // tags are reverse ordered.
        if (!ex_tags_keep) ex_tags_keep = extracted_tags.slice(0,i+1);
        extracted_tags.splice(i,1);
      }
      if (ex_tags_keep || retag_editing) {
        lth.ed_t = (retag_editing || !lth.ed_t)? ex_tags_keep : ex_tags_keep.concat(lth.ed_t); // reverse order
        if (pref.debug_mode['24']) console.log(th.key+ ': lth.ed_t: '+ (lth.ed_t && lth.ed_t.map(function(v){return (typeof(v)==='string')? v : v.no+'('+v.time+')';})));
      }
      return extracted_tags;
    },
    extract_tags: function(th, extracted_tags, keys_fix, clean, retag_editing){
      var lth = this.mems[th.domain][th.board][th.no];
      if (th.parse_funcs.has_editing) extracted_tags = this.extract_tags_trim_editing(th, lth, extracted_tags, retag_editing);
      if (!keys_fix) {
        this.keys_fix = {};
        var t0 = lth.t0; // runs getter 1 times only.
        if (t0!==null && t0.length!=0) {
          if (pref.liveTag.ci) for (var i=0;i<t0.length;i++) this.keys_fix[t0[i].toLowerCase()] = t0[i];
          else for (var i=0;i<t0.length;i++) this.keys_fix[t0[i]] = t0[i];
        }
      }
      var t0len = t0!==undefined? t0.length : lth.t0.length;
      var t1 = this.update_tags_in_th(extracted_tags, lth.t1, this.keys_fix, pref.liveTag.max-t0len, th, lth.wt, this.mems[th.domain][th.board], clean);
      if (t1.length!=0 || (lth.t1 && lth.t1.length!==0)) lth.t1 = t1;
      this.update_pn_buf.delayed_do();
      this.keys_fix = null;
//      else if (ur>=0) {
//        for (var j=0;j<2;j++) {
//          var tgt = tag[j];
//          for (var i=0;i<tgt.length;i++) this.check_update_tags_color(tgt[i],ur);
//        }
//      }
      return lth;
    },
    tags_boardlist: null,
    update_boardlist: function(force_redraw){
////      if (!site3[site.nickname].bds && site3[site.nickname].boards) { // working code.
//////        site3[site.nickname].bds = {};
////        for (var i=0;i<site3[site.nickname].boards.length;i++) site3[site.nickname].bds['/'+site3[site.nickname].boards[i].board+'/'] = null;
////      }
//      if (site.components.boardlist && site3[site.nickname].bds) {
      if (!site.components.boardlist) return;
      var pfv = pref.virtualBoard;
      if (this.tags_boardlist===null) {
        this.tags_boardlist = [];
        pref_func.settings.onchange_funcs['virtualBoard.search.show'](null, true);
        this.filter_onchange(pfv.search, true);
      }
      var tags_bl = this.tags_boardlist;
      var i=0;
      var flag = force_redraw;
      var p = 0;
      var j=0;
      var p_remove = pfv.bl.p_board==='both' && pfv.bl.p_remove && !site.nativeVirtualBoard;
      var max = (!pfv.bl.dumb || pfv.search.str || !pfv.search.show) && pfv.bl.max || 0;
      var sv_rexps = pfv.search.show && pfv.search.rexps; // search valid and rexps
      var pflci = pref.liveTag.ci;
      var actives = {__proto__:this.actives};
      var blbs = pfv.bl.show && pfv.bl.p_board==='replace'? site2['DEFAULT'].boardlist_boards : null; // for faster execution at pfv.bl.p_board==='replace' (default) // working code
//      var blbs = pfv.bl.show && pfv.bl.p_board==='replace'? {__proto__:site2['DEFAULT'].boardlist_boards} : {}; // for faster execution at pfv.bl.p_board==='replace' (default) // working code
////      var p_rep = pfv.bl.p_board==='replace';
//      var nof_as = pfv.bl.show && pfv.bl.showActives && (this.active.in + this.active.pk + this.active.ex);
//      if (nof_as) for (var t in this.tags_ci) {
//        var o = this.tags_ci[t];
//        if (o.in || o.pk || o.ex) {
//          if (actives[t]!==true) console.log('ERROR: actives: lacking: '+t);
////          blbs[o.key] = true;
//          if (o.in) nof_as--;
//          if (o.pk) nof_as--;
//          if (o.ex) nof_as--;
//          if (nof_as<=0) break;
//        } else if (t in actives) console.log('ERROR: actives: excess: '+t);
//      }
      while (i<this.tags_array_old.length) {
//        while (j<max && i<this.tags_array_old.length) {
//        while (i<pfv.max+p && i<this.tags_array_old.length) {
        var key = this.tags_array_old[i].key;
        if (j>=max && !blbs[key]) break; // (j>=max && (!p_rep || !blbs[key]))
        if (p_remove && (('/'+key.slice(1)+'/') in this.mems[site.nickname])) p++;
        else if (!sv_rexps || this.update_pn_disp_func(sv_rexps, pflci? key.toLowerCase() : key)) {
          if (tags_bl[j]!==key) {
            tags_bl[j] = key;
            flag = true;
          }
          if (actives[key] || blbs && blbs[key]) actives[key] = null;
          j++;
        }
        i++;
      }
      if (blbs) for (var key in blbs) if (liveTag.tags[key]) if (actives[key]!==null) {
        if (!sv_rexps || this.update_pn_disp_func(sv_rexps, pflci? key.toLowerCase() : key)) {
          if (tags_bl[j]!==key) {
            tags_bl[j] = key;
            flag = true;
          }
          if (actives[key]) actives[key] = null;
          j++;
        }
      }
      if (pfv.bl.show && pfv.bl.showActives) for (var key in actives) if (actives[key]!==null) {
        if (!pfv.bl.activesWOF || !sv_rexps || this.update_pn_disp_func(sv_rexps, pflci? key.toLowerCase() : key)) {
          if (tags_bl[j]!==key) {
            tags_bl[j] = key;
            flag = true;
          }
          j++;
        }
      }
//      /*if (p_rep)*/ for (var key in blbs) if (blbs[key]!==null) { // working code
//        if (liveTag.tags[key] && (!pfv.bl.activesWOF && Object.prototype.hasOwnProperty.call(blbs,key) || !sv_rexps || this.update_pn_disp_func(sv_rexps, pflci? key.toLowerCase() : key))) {
//          if (tags_bl[j]!==key) {
//            tags_bl[j] = key;
//            flag = true;
//          }
//          j++;
//        }
//      }
      if (j!=tags_bl.length) {tags_bl.splice(j,tags_bl.length-j); flag=true;}
      if (flag) site2[site.nickname].show_boardlist(tags_bl, this.boardlist_click_entry);
    },
    redraw_boardlist: function(){
      if (!site.components.boardlist) return;
      site2[site.nickname].show_boardlist([], this.boardlist_click_entry);
      site2[site.nickname].show_boardlist(this.tags_boardlist, this.boardlist_click_entry);
    },
////    update_boardlist_1: function(tag){ // working code, but too slow.
////      var pn = this.update_tag_string([tag].concat(this.tags_boardlist), ' / ', this.boardlist_click_entry);
////      site2[site.nickname].show_boardlist(pn);
////    },
//    update_boardlist_1: (function(){ // working code, but not used.
//      var buf = [];
//      var delayed_do = new DelayBuffer(
//        function(){
//          var tags = Object.create(null);
//          for (var i=0;i<buf.length;i++) tags[liveTag.tags_ci[(pref.liveTag.ci)? buf[i].toLowerCase() : buf[i]].key] = null;
////          var pn = liveTag.update_tag_string(Object.keys(tags).concat(liveTag.tags_boardlist), ' / ', liveTag.boardlist_click_entry); // slow in 8chan.
//          site2[site.nickname].show_boardlist_physical_board(Object.keys(tags), liveTag.boardlist_click_entry);
//          buf = [];
//        }, 500).get_bound_func();
//      return function(tag) {
//        if (site.components.boardlist) {
//          buf[buf.length] = tag;
//          delayed_do();
//        }
//      };
//    })(),

//    refresh_end_proc: function(){
//      for (var i in this.tags) if (this.tags[i].ur_cs===null) this.count_ur_sub(i);
//      for (var i in this.tags_update_state) this.tags[i].ur = null;
//      for (var i in this.tags_update_state) {
//        if (this.tags[i].ur===null) this.count_ur(i);
//        this.update_tag_node(i);
//      }
//      this.tags_update_state = Object.create(null);
//    },
    update_tag_node: function(tag, node_in_boardlist, broadcast){
      if (cataLog.threads!==null) {
////if (!pref.test_mode['24']) {
        var keys = liveTag.tags[tag].mems_keys_obj(); // runs getter only 1 time. // working code.
        var footer = (broadcast || !this.NC)? gClg.footer : this.footer; //  || cataLog.Footer;
        for (var name in keys) footer.color_tag_node(name,tag, broadcast);
//          if (cataLog.Footer) {
//            var pn = cataLog.Footer.query_tag_node(name,tag);
//            if (pn) this.color_tag_node(pn,tag);
//          }
////          if (cataLog.threads[name] && cataLog.threads[name][24] && cataLog.threads[name][24][3])
////            this.update_tag_node_1(cataLog.threads[name][24][3],keys[name]);
//////} else {
//////        for (var i of this.tags[tag].mems_keys()) {
//////          var name = i.key;
//////          if (cataLog.threads[name] && cataLog.threads[name][24] && cataLog.threads[name][24][3])
//////            this.update_tag_node_1(cataLog.threads[name][24][3].getElementsByClassName(pref.cpfx+'tag'), this.tags[tag].mems.get(i)); // can't get tag when i is derived from board.
//////        }
//////}
      }
      if (!this.NC) {
        var tag_bl = this.tags[tag].key;
        var pn = node_in_boardlist || site2[site.nickname].query_tag_node(tag_bl);
        if (pn) this.color_tag_node(pn, tag_bl);
//      else if (this.tags_boardlist && this.tags_boardlist.indexOf(tag_bl)!=-1) this.update_tag_node_1(site.components.boardlist, tag_bl);
      }
    },
//    update_tag_node: function(tag, node_in_boardlist){ // working code.
//      if (catalog_obj && catalog_obj.catalog_func()!=null) {
//        var threads = catalog_obj.catalog_func().threads;
//        for (var t in liveTag.tags[tag].tgts) 
//          for (var name in liveTag.tags[t].mems) 
//            if (threads[name] && threads[name][24] && threads[name][24][3]) this.update_tag_node_1(threads[name][24][3].getElementsByClassName(pref.cpfx+'tag'),tag);
//      }
//      if (node_in_boardlist) this.color_tag_node(node_in_boardlist, tag);
//      else if (this.tags_boardlist.indexOf(tag)!=-1) this.update_tag_node_1(site.components.boardlist.getElementsByClassName(pref.cpfx+'tag'), tag);
//    },
//    update_tag_node_1: function(ppn, tag){ // working code, but removed because of query
//      var pns = ppn.getElementsByClassName(pref.cpfx+'tag');
//      var tag_ci = tag.toLowerCase();
//      for (var j=0;j<pns.length;j++) {
//        var txt = pns[j].textContent;
//        if (txt===tag || (pref.liveTag.ci && txt.toLowerCase()===tag_ci)) {
//          this.color_tag_node(pns[j],tag);
//          break;
//        }
//      }
//    },
    color_tag_node : function(node, tag){
      var pf = pref.liveTag.style;
      var tobj = this.tags[tag];
      var ur = !pf.use? 0 : tobj? tobj.ur : liveTag.tags[tag].ur; // NC ready
      var style = (tobj && tobj['ex'])? (ur>=2? pf.exurtm : ur>=1? pf.exur : pf.ex)
                : (tobj && tobj['in'])? (ur>=2? pf.inurtm : ur>=1? pf.inur : pf.in)
                : (tobj && tobj['pk'])? (ur>=2? pf.pkurtm : ur>=1? pf.pkur : pf.pk)
//      var ur = (!pf.use)? 0 : tobj.ur; // working code, but NC
//      var style = (tobj['ex'])? (ur>=2? pf.exurtm : ur>=1? pf.exur : pf.ex)
//                : (tobj['in'])? (ur>=2? pf.inurtm : ur>=1? pf.inur : pf.in)
                : ur>=2? pf.urtm : ur>=1? pf.ur : null;
//      var style_str = (this.tags[tag]['ex'])? pref.liveTag.style_ex_str :
//                      (this.tags[tag]['in'])? pref.liveTag.style_in_str :
//                      (!pref.liveTag.style || this.tags[tag].ur<=0)? null :
//                      (this.tags[tag].ur>=2)? pref.liveTag.style_urtm_str : pref.liveTag.style_ur_str;
      if (style) node.setAttribute('style',style); // this can accept '!important'
      else node.removeAttribute('style');
    },
//    color_tag_node : function(node, tag){
//      if (this.tags[tag]['in']) comf.init_set_style(node,pref.liveTag.style_in_obj4);
//      else if (pref.liveTag.style) {
//        if (this.tags[tag].ur>2) comf.init_set_style(node,pref.liveTag.style_urtm_obj4);
//        else if (this.tags[tag].ur>0) comf.init_set_style(node,pref.liveTag.style_ur_obj4);
//        else comf.init_set_style(node,null);
//      } else comf.init_set_style(node,null);
//    },
    update_tag_nodes: function(tags, sep_txt, func_click, pn_old, tags_old){ // stateful fast approach
      var tags_old_org = (pref.debug_mode['35'])? tags_old && tags_old.slice() : null;
      var debug_refs = (tags_old_org)? [] : null;
      var pn = pn_old || document.createElement('span');
      var ref = pn.firstChild;
      for (var j=0;j<tags.length;j++) {
        var debug_before = tags_old && tags_old.slice();
        if (tags_old_org) debug_refs.push(ref && ref.textContent || null);
        var sep = null;
        var tag = tags[j];
        if (tags_old && tag==tags_old[j]) {
          if (ref) ref = ref.nextSibling;
          if (ref && j!=tags.length-1) ref = ref.nextSibling; // check j for shortening case
          if (tags_old_org) debug_refs.push('same');
        } else {
          var idx = (tags_old)? tags_old.indexOf(tag, j) : -1;
          if (idx==-1) { // new
            pn.insertBefore(this.create_tag_node_1(tag, func_click, 'span'), ref);
            sep = true;
            if (tags_old) tags_old.splice(j,0,tag);
            if (tags_old_org) debug_refs.push('new');
          } else if (idx==j+1) { // down
            var tgt = ref;
            var sep2 = ref.nextSibling; // sep must exist, because this is a case of down.
            ref = sep2.nextSibling;
            var idx2 = tags.indexOf(tags_old[j], j+1);
            var tag2 = tags_old.splice(j,1)[0];
            if (idx2==-1) {
              pn.removeChild(sep2);
              pn.removeChild(tgt);
              if (tags_old_org) debug_refs.push('remove');
            } else {
              var ref2 = pn.childNodes[(idx2+1)*2-1] || null; // +1 for myself, because of down
              pn.insertBefore(sep2, ref2); // reverse order
              pn.insertBefore(tgt, ref2);
              tags_old.splice(idx2,0,tag2);
              if (tags_old_org) debug_refs.push('down');
            }
            ref = ref.nextSibling;
            if (ref && j!=tags.length-1) ref = ref.nextSibling; // check j for shortening
          } else { // up
            var tgt = pn.childNodes[idx*2];
            sep = tgt.previousSibling; // sep must exist, because this is a case of up.
            pn.insertBefore(tgt,ref);
            tags_old.splice(j,0,tags_old.splice(idx,1)[0]);
            if (tags_old_org) debug_refs.push('up');
          }
        }
        if (j!=tags.length-1 && (!ref || sep)) pn.insertBefore(sep!==true && sep || document.createTextNode(sep_txt), ref);
        if (tags_old_org) {
          if (tags_old && tags.slice(0,j+1).toString()!==tags_old.slice(0,j+1).toString())
            console.log('ERROR: update_tag_nodes: arr: ', j, tags_old, tags, debug_before, tags_old_org, debug_refs);
          if (tags_old && pn.textContent.indexOf(tags_old.slice(0,j+1).join(sep_txt))!=0)
            console.log('ERROR: update_tag_nodes_1: ', j, pn.textContent, tags_old, debug_before, tags_old_org, debug_refs);
        }
      }
      if (ref) {
        while (ref.nextSibling) pn.removeChild(ref.nextSibling);
        pn.removeChild(ref);
      }
      if (tags_old_org) if (pn.textContent!==this.create_tag_nodes(tags, sep_txt, func_click).textContent)
          console.log('ERROR: update_tag_nodes: ', pn.textContent, tags, tags_old_org);
      return pn;
    },
    create_tag_nodes: function(tags, sep, func_click){ // equivalent function to 'update_tag_nodes', but stateless approach.
      var pn = document.createElement('span');
//      pn.setAttribute('class',pref.cpfx+'tag');
//      pn.setAttribute('name',pref.cpfx+'tag_parent');
      for (var j=0;j<tags.length;j++) {
        if (j!=0 && sep) pn.appendChild(document.createTextNode(sep));
        pn.appendChild(this.create_tag_node_1(tags[j], func_click, 'span'));
      }
      return pn;
    },
    create_tag_node_1: function(tag, func_click, tagname){
      var pn = document.createElement(tagname);
      pn.setAttribute('class',pref.cpfx+'tag');
      pn.textContent = tag;
      this.color_tag_node(pn,tag);
//      pn.onclick = func_click;
//      pn.onmouseover = this.tag_onmouseover;
      return pn;
    },
    tag_node_onclick: function(e){ // must be bound
      var tag = e.target.textContent;
      this.prep_NC(tag);
      this.cbx_changed(tag, pref.liveTag.click_func, null, null, e);
    },
//    extract_tags_in_posts : function(th, tags, name){
//      tag[1] = this.update_tags_in_th(tags, tag[1][0], tag[0][1], pref.liveTag.max-tag[0][0].length, name);
//      this.update_pn_buf.delayed_do();
//    },
  };
//  liveTag.key_dirty = Object.create(liveTag.key_dirty_creation);
//  liveTag.cbx_onchange_entry = liveTag.cbx_onchange_entry(liveTag);
//  liveTag.update_pn_buf = new DelayBuffer(liveTag.update_pn.bind(liveTag), 500);
  liveTag.update_pn_buf = new DelayBuffer(liveTag.update_pn.bind(liveTag), pref.liveTag.disp_delay);
  liveTag.update_pn_1_lazy = new DelayBuffer(liveTag.update_pn_1.bind(liveTag, true), pref.liveTag.lazy_delay);
  liveTag.tags_ci = Object.getPrototypeOf(liveTag.tags);
  if (pref.cli.auto) pref_func.site2_json(['liveTag']); // 'add_rss']);
  if (pref.RSS.str && site2['rss']) pref_func.RSS_json(true);
//  thread_reader_init();


  var hiddenPosts = (!pref.test_mode['162'])? null : (function(){
    function hide_unhide(pn, unhide, pn_done){
//      pn = (site.hasPostContainer)? posts[i].pn.parentNode : posts[i].pn; // this is not slow critically, because filterd posts are not so many.
      if (!pn || pn===pn_done) return;
      if (unhide) pn.classList.remove(pref.cpfx+'hidden');
      else pn.classList.add(pref.cpfx+'hidden');
      return pn;
    }
    function get_hp(lth){
      var hp = lth.hp;
      return (hp===undefined)? (localStorage? JSON.parse(localStorage[site2[lth.domain].ls_key_hiddenPosts+lth.board+lth.no]||null) : null) : hp; // null means 'no hiddenPosts'
    }
    function save(lth, hp){
      var data = Object.keys(hp);
      var ls_key = site2[lth.domain].ls_key_hiddenPosts+lth.board+lth.no;
      if (localStorage) if (data.length===0) delete localStorage[ls_key]; else localStorage[ls_key] = JSON.stringify(hp);
      lth.hp = (data.length>0)? hp : null;
    }
    function search_post_from_data(clg, key, no){
      var posts = clg.threads[key][16].posts;
      for (var i=0;i<posts.length;i++) if (posts[i].no===no) return posts[i].pn;
    }
    function search_post_from_pn(pn){
      while (!pn.classList.contains('post')) pn = pn.parentNode;
      return pn;
    }
    function hide_if_hit(th, posts, redraw_hp, unhide, no){
      var hp = redraw_hp || get_hp(th.lth);
      if (!hp) return;
      var lthq = th.lth.q;
      for (var i=0;i<posts.length;i++) {
        var post = posts[i];
        if (post.no===no) continue;
        if (hp[post.no]>(redraw_hp? 1:0)) {hide_unhide(post.pn); continue;}
        if (post.quotes) for (var j=0;j<post.quotes.length;j++) if (post.quotes[j][0]===lthq && (hp[post.quotes[j][1]]&0x01)) {hp[post.no] = 1; hide_unhide(post.pn); break;}
        if (unhide && (!post.quotes || j===post.quotes.length) && hp[post.no]===1) {delete hp[post.no]; hide_unhide(post.pn, true); continue;}
      }
//      if (hp) for (var i=0;i<posts.length;i++) if (th.posts[i].no in hp) hide_unhide(posts[i].pn);
    }
    function hide_unhide_cmd_entry(clg, key, no, pn, unhide, series){
      var pn_done = hide_unhide(search_post_from_pn(pn), unhide);
      hide_unhide(search_post_from_data(clg, key, no), unhide, pn_done);
      var lth = liveTag.mems.getFromName(key);
      var hp = get_hp(lth)||{};
      var redraw = series || unhide && hp[no]&0x01;
      if (unhide) delete hp[no]; else hp[no] = series? 3 : 2; // 3: head of series,  2: hidden by manual,  1: tails of series,  undefined: not hidden.
      if (redraw) hide_if_hit(clg.threads[key][16], clg.threads[key][16].posts, hp, unhide, no);
      save(lth, hp);
    }
    return {
      hide_if_hit: hide_if_hit,
      cmd: function(clg, key, no, pn_menu, cmd){
        if (cmd==='PDISABLEHIDE' || cmd==='PENABLEHIDE' || cmd==='PCLEARHIDE') {
          var hp = get_hp(liveTag.mems.getFromName(key)) || {};
          var posts = clg.threads[key][16].posts;
          for (var i=0;i<posts.length;i++) if (posts[i].no in hp) hide_unhide(posts[i].pn, cmd!=='PENABLEHIDE');
        } else hide_unhide_cmd_entry(clg, key, no, pn_menu, cmd==='PUNHIDE', cmd==='PHIDEAFTER');
        if (cmd==='PCLEARHIDE') save(liveTag.mems.getFromName(key),{});
      },
    };
  })();

  
//  https://nolanlawson.com/2015/09/29/indexeddb-websql-localstorage-what-blocks-the-dom/    // IndexedDB IS TOO SLOW TO USE, Chrome47@2015.09.29
//  http://stackoverflow.com/questions/28693674/indexeddb-slow-retrieval-issue               // IndexedDB IS TOO SLOW TO USE @2015.02
//  http://stackoverflow.com/questions/10102571/indexeddb-very-slow-compared-to-websql-what-am-i-doing-wrong // IndexedDB IS TOO SLOW TO USE @2012.01.12
//  https://dev.mozilla.jp/2012/07/why-no-filesystem-api-in-firefox/
//  https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria#Storage_limits
//  https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Introduction
//  http://www.html5rocks.com/ja/tutorials/file/filesystem/
  var IDB = (window.indexedDB && pref.features.IDB)? (function(){
    function IDBRequest_onerror(e){
      console.log('IDB: ERROR: ',e);
      indicator.report({err:'IDB_ERROR:'+e.target.error.name});
      for (var key of db_info.keys()) console.log(db_info.get(key));
      console.log('last_closed: ',last_closed);
    }
    function IDBRequest_onblocked(e){
      if (pref.debug_mode['21']) console.log('IDB: BLOCKED: '+e.oldVersion+' -> '+e.newVersion);
    }
    function IDBTransaction_onabort(e){
      console.log('IDBT: ABORT: '+e.target.error.name,e);
      indicator.report({err:'IDBT_ABORT:'+e.target.error.name});
      var tr = e.target;
      var db = tr.db;
//      if (tr.error.name==='TimeoutError') {
//        var info = tr_info.get(tr);
//        tr_info.delete(e.target);
//        console.log(info);
//        if (info && info.req) {
//          add_req(reqs_re, db.name, info.req); // retry
//          console.log('Requeued... '+db.name.replace(/^[^\/]*/,'')+req.no+' '+req.kind);
//        }
//      }
      // http://stackoverflow.com/questions/10477489/what-are-the-details-can-be-obtained-from-webkitstorageinfo-queryusageandquota
//      if (window.webkitStorageInfo) window.webkitStorageInfo.queryUsageAndQuota(webkitStorageInfo.TEMPORARY, console.log.bind(console));
      if (navigator.webkitTemporaryStorage) navigator.webkitTemporaryStorage.queryUsageAndQuota(console.log.bind(console));
      IDBT_rereq(db, tr, 'IDBT_abort:'+tr.error.name);
//      IDB_close(db, null, 'IDBT_abort:'+tr.error.name);
    }
    function IDBTransaction_onerror(e){
      console.log('IDBT: ERROR: '+e.target.error.name,e);
      indicator.report({err:'IDBT_ERROR:'+e.target.error.name});
      var tr = e.target.transaction;
      var db = e.target.db || e.target.transaction && e.target.transaction.db;
      IDBT_rereq(db, tr, 'IDBT_onerror:'+tr.error.name);
//      IDB_close(db, null, true);
    }
    function IDBRequest_onupgradeneeded(e){
      e.target.onsuccess = null; // prevent from being called twice.
      IDBRequest_onsuccess_open(e, true);
    }
    function IDBRequest_onsuccess_open(e, from_upgrade){
      var db = e.target.result;
      version[db.name] = db.version;
      delete waiting_open[db.name+((from_upgrade)? 'vc' : 'rw')];
//      var reqs = (from_upgrade)? reqs_vc[db.name] : reqs_rw[db.name];
      var reqs = from_upgrade && reqs_vc[db.name] || reqs_rw[db.name];
      if (pref.debug_mode['20']) {
        var count = 1;
        var debug_reqs = reqs && reqs.debug_query() || null;
//        var debug_coms = reqs && reqs.ary.slice(0,10).map(function(v){return v.kind;});
        console.log('IDB: opened: '+(db_info.size+1)+' '+db.name.replace(/^[^\/]*/,'')+' v'+db.version+', '+(debug_reqs && debug_reqs.length)+' req(s), '+e.type+', '+(debug_reqs && debug_reqs.coms));
      }
//      if (db_info.has(db)) console.log('IDB: ERROR: the same db was returned.', db_info.get(db));
      var info = {type:e.type, tr_count:1, reqs:reqs, pass:0, wdg:null, crawler:1, oss:{}, tr_info:new Map(), req_kinds:{}, coms:debug_reqs && debug_reqs.coms, done:0, done_put:0}; // tr_count.set(db, 1); // tr_count.set(db, (tr_count.get(db)||0)+1); // returns the same db sometimes.
      db_info.set(db, info);
      if (reqs) {
        for (var i=0;i<db.objectStoreNames.length;i++) info.oss[db.objectStoreNames[i]] = null;
        if (from_upgrade) {
          var tr = e.target.transaction;
          tr_inc(db);
          tr_set(tr, function(){IDBRequest_close({target:{db:db}});});
          if (db.version==1) { // patch for the first creation.
            delete waiting_open[db.name+'rw']; // entried by 'rw' always.
            pref4.archive.IDB_board_sel_options = null;
            db.createObjectStore('Meta'); // dummy write
            return req_end(db, from_upgrade);
          }
          var num_downgraded = 0;
          while (true) {
            indicator_update();
            var req = reqs.shift();
            info.pass++;
            if (!req) break;
            if (pref.debug_mode['20']) info.done++;
            if (pref.debug_mode['22'] && req.kind!=='put') console.log('req_upgrade: '+req.no+', '+req.kind+', '+req.key+', '+reqs.debug_query().length);
            info.req_kinds[req.kind] = null;
            var contains = info.oss[req.no]===null;
            if (req.kind==='put') {
              if (!contains) {
                make_1(db, req, IDBRequest_close);
                info.oss[req.no] = null;
                if (pref.debug_mode['22']) console.log('req_upgrade: '+req.no+', '+req.kind+', '+req.key+', '+reqs.debug_query().length);
              } else {
                add_req(reqs_rw, db.name, req); // downgrade
                if (pref.debug_mode['22']) num_downgraded++;
              }
//              else put_1(db,req, IDBRequest_close);
            } else if (req.kind==='delete_th') {
              if (contains) delete_th_1(db, req, tr); // returns void, not a transaction.
            }
          }
          if (pref.debug_mode['22'] && num_downgraded) console.log('req_upgrade: downgraded: '+num_downgraded);
          req_end(db, from_upgrade);
        } else {
          var loop_func = function complete_func(){req_1(db,reqs,from_upgrade,complete_func);}
          info.complete_func = loop_func;
          req_1(db, reqs, from_upgrade, loop_func);
//          while (info.crawler<pref.archive.IDB.nof_tr && !reqs.isEmpty) { // not sensitive when reqs are added after this.
//            info.crawler++;
//            req_1(db, reqs, from_upgrade, loop_func);
//          }
        }
      } else req_end(db, from_upgrade);
    }
    function req_1(db, reqs, from_upgrade, complete_func){ // multi transaction can be accepted, but not debugged.
      var info = db_info.get(db);
      if (!info) return; // closed by watchdog
      indicator_update();
      var req = (waiting_open[db.name+'vc']!==undefined)? null : reqs.shift();
      info.pass++;
      if (req) {
        if (pref.debug_mode['20']) info.done++;
        if (pref.debug_mode['22'] && req.kind!=='put_m') console.log(db.name.replace(/^[^\/]*/,'')+req.no+', '+req.kind+', '+req.key+', '+reqs.debug_query().length);
        var contains = info.oss[req.no]===null;
        if (req.kind==='get_all_m') {
          var reqs_current = [];
          var nos = {};
          for (var i=0;i<req.reqs.length;i++) if (info.oss[req.reqs[i].no]===null) {
            reqs_current[reqs_current.length] = req.reqs[i];
            nos[req.reqs[i].no] = null;
          }
          if (reqs_current.length!==0) get_all(db,{nos:Object.keys(nos), reqs:reqs_current}, complete_func);
          else complete_func();
//        } else if (req.kind==='get_all') {
//          if (contains) get_all(db,{nos:[req.no], reqs:[req]}, complete_func);
//          else complete_func();
        } else if (req.kind==='list_os') {
          req.obj(site.nickname, db.name.substr(pref.script_prefix.length+1), Array.prototype.slice.call(db.objectStoreNames).filter(function(v){return v!=='Meta';}));
          complete_func();
        } else if (req.kind==='put_m') {
          var reqs_current = [];
          var nos = {};
          for (var i=0;i<req.reqs.length;i++)
            if (info.oss[req.reqs[i].no]!==null) add_req(reqs_vc, db.name, req.reqs[i]); // upgrade
            else {
              reqs_current[reqs_current.length] = req.reqs[i];
              nos[req.reqs[i].no] = null;
            }
          if (pref.debug_mode['22']) console.log(db.name.replace(/^[^\/]*/,'')+(reqs_current[0] && reqs_current[0].no || 'skipped')+'('+Object.keys(nos).length+')'+', '+req.kind+'('+reqs_current.length+'), '+req.key+', '+reqs.debug_query().length);
          if (reqs_current.length>1) {
            req.reqs = reqs_current;
            req.nos = Object.keys(nos);
            put_1(db,req, complete_func);
          } else if (reqs_current.length==1) put_1(db,reqs_current[0], complete_func);
          else complete_func();
        } else if (req.kind==='put') {
          if (!contains) {add_req(reqs_vc, db.name, req);complete_func();} // upgrade
          else put_1(db,req, complete_func);
//        } else if (req.kind==='put_if') {
//          if (contains) put_1(db,req, complete_func);
//          else complete_func();
        } else if (req.kind==='delete') {
          if (contains) delete_1(db,req, complete_func);
          else complete_func();
        } else if (req.kind==='clean_up' || req.kind==='clean_m') {
          if (!times_pruned[db.name]) times_pruned[db.name] = {list:[], write:null};
          var list = times_pruned[db.name].list;
          var list_len_old = list.length;
          var nos = {Meta:null, __proto__:req.obj};
          for (var i=0;i<list.length;i++) nos[list[i].no] = null;
          if (req.kind==='clean_up') {
            var date = Date.now();
            var names = db.objectStoreNames;
            for (var i=0;i<names.length;i++) if (nos[names[i]]!==null) list[list.length] = {no:names[i], pruned_time:date};
          } else {
            for (var i=0;i<req.reqs.length;i++)
              if (info.oss[req.reqs[i].no]===null) if (nos[req.reqs[i].no]!==null) list[list.length] = {no:req.reqs[i].no, pruned_time:req.reqs[i].obj};
          }
          if (times_pruned[db.name].write===null) {
            times_pruned[db.name].write = false;
            clean_up(db, req, complete_func);
          } else clean_up_end_proc(db, null, complete_func, list.length!==list_len_old);
//        } else if (req.kind==='check_clean') { // working code.
//          if (!times_pruned[db.name]) times_pruned[db.name] = [];
//          if (contains) times_pruned[db.name][times_pruned[db.name].length] = {no:req.no, pruned_time:req.obj};
//          clean_up_end_proc(db, null, complete_func, contains);

//          if (req.kind==='clean_up') { // working code.
//            var tgts = [];
//            for (var i=0;i<db.objectStoreNames.length;i++) if (req.obj[db.objectStoreNames[i]]!==null) tgts[tgts.length] = db.objectStoreNames[i];
//            clean_up(db, {tgts:tgts, prune:(req.kind==='clean_up'), __proto__:req}, complete_func); // call 'clean_up_end_proc' in 'clean_up'.
//          } else {
//            check_cleaned[db.name][check_cleaned[db.name].length] = {no:req.no, pruned_time:req.obj};
//            reqs[reqs.length] = {no:-1, key:'pruned_time', obj:check_cleaned[db.name], kind:'put'};
//            clean_up_end_proc(db, null, complete_func);
//          }
////        } else if (req.kind==='clean_up') { // working code.
////          check_cleaned[db.name] = Date.now();
////          var tgts = [];
////          for (var i=0;i<db.objectStoreNames.length;i++) if (req.obj[db.objectStoreNames[i]]!==null) tgts[tgts.length] = db.objectStoreNames[i];
////          clean_up(db, {tgts:tgts, prune:true, __proto__:req}, complete_func);
//////          for (var i=0;i<db.objectStoreNames.length;i++) if (req.obj[db.objectStoreNames[i]]!==null) clean_1(db, {no:db.objectStoreNames[i], prune:true, __proto__:req});
////        } else if (req.kind==='check_clean') {
////          var date = Date.now();
////          if (date - (check_cleaned[db.name] || 0 ) > pref.archive.IDB.check_every * ((pref.test_mode['75'])? 60000 : 3600000)) {
////            check_cleaned[db.name] = date;
////            clean_up(db, {tgts:Array.prototype.slice.call(db.objectStoreNames), __proto__:req}, complete_func);
////          } else complete_func();
//////          for (var i=0;i<db.objectStoreNames.length;i++) clean_1(db, {no:db.objectStoreNames[i], __proto__:req});
        } else complete_func();
        if (info.crawler<pref.archive.IDB.nof_tr && !reqs.isEmpty) {
          info.crawler++;
          req_1(db, reqs, from_upgrade, complete_func);
        }
      } else if (--info.crawler===0) req_end(db, from_upgrade);
    }
    function req_end(db, from_upgrade){
      IDBRequest_close({target:{db:db}});
      var req_parent = from_upgrade && reqs_vc[db.name] && reqs_vc || reqs_rw;
      if (req_parent[db.name] && req_parent[db.name].isEmpty) delete req_parent[db.name];
//      delete (from_upgrade && reqs_vc[db.name] && reqs_vc || reqs_rw)[db.name]; // BUG, might be remade.
      if (reqs_re[db.name]) reqs_re2rw(db.name);
//      if ((!from_upgrade)? reqs_vc[db.name] : reqs_rw[db.name]) open_db(db.name, (!from_upgrade)? db.version+1 : undefined);
//      if (req_parent===reqs_vc) {
      if (from_upgrade) {
        if (reqs_rw[db.name] && !reqs_rw[db.name].isEmpty) open_db(db.name);
      } else if (reqs_vc[db.name] && !reqs_vc[db.name].isEmpty) open_db(db.name, true);
      if (Object.keys(reqs_rw).length===0 && Object.keys(reqs_vc).length===0 && Object.keys(reqs_re).length===0) watchdog.stop();
    }
    function reqs_re2rw(db_name){
      if (reqs_rw[db_name]) reqs_rw[db_name].push_all(reqs_re[db_name]);
      else reqs_rw[db_name] = reqs_re[db_name];
      delete reqs_re[db_name];
    }

    function delete_th_1(db,req, tr){
//      tr_inc(db);
      db.deleteObjectStore(req.no);
//      tr_set(tr, (function(func_prev){
//        return function(e){
//          if (func_prev) func_prev(e);
//          IDBRequest_close({target:{db:db}}); // cause ERROR, probably bug in browser, oncomplete is fired too early at createObjectStore and deleteObjectStore.
//        }
//      })(tr.oncomplete));
if (pref.debug_mode['23']) console.log('IDB: delete_th: '+db.name+req.no+', '+db.objectStoreNames.length);
    }
    function make_1(db,req, complete_func){
      tr_inc(db);
      var tr = db.createObjectStore(req.no).transaction;
      tr_set(tr, (function(func_prev){ // tr belongs to db, transaction.oncomplete is called ONE TIME, so cascade oncomplete funcs.
        return function(e){
if (!pref.test_mode['66']) {
          put_1(db,req, complete_func);
} else {
          add_req(reqs_rw, db.name, req); // downgrade // DOESN'T WORK WHY???  // cause ERROR, probably bug in browser, oncomplete is fired too early at createObjectStore and deleteObjectStore.
          complete_func({target:{db:db}});
}
          if (func_prev) func_prev(e);
        }
      })(tr.oncomplete));
    }
    function put_1(db,reqs_in, complete_func){
      var reqs = reqs_in.reqs || [reqs_in];
      try {
        var IDBT = tr_set(db.transaction(reqs_in.nos || reqs_in.no, 'readwrite', reqs_in), complete_func, reqs_in, db);
        for (var i=0;i<reqs.length;i++) {
          var req = reqs[i];
          if (pref.test_mode['82'] && req.obj instanceof Blob) {
            if (pref.test_mode['83']) req.obj = null;
            else if (pref.test_mode['84']) {
              var str = '';
              while (str.length*2<req.obj.size) str += Math.random();
              req.obj = str;
            } else {
              var codes = [];
              for (var j=0;j*2<req.obj.size;j++) codes[j] = Math.floor(Math.random()*65536);
              req.obj = String.fromCharCode.apply(null,codes);
            }
          }
          if (pref.debug_mode['23']) console.log('put_'+((reqs_in.nos)?'m: ':'1: ')+db.name.replace(/^[^\/]*/,'')+req.no+', '+req.key+((req.key==='posts')? ':'+req.obj.length:'')+((reqs_in.nos)? ' '+(i+1)+'/'+reqs_in.nos.length+'/'+reqs_in.reqs.length:''));
          var IDBR = IDBT.objectStore(req.no).put((req.key!=='posts')? req.obj : JSON.stringify(req.obj, archiver.store_json_func),
                                                  (req.key!=='posts')? req.key : req.key+'_'+req.obj[0].no);
          IDBR.onerror   = IDBRequest_onerror;
//          IDBR.onsuccess = clear_wdg_get_db; // IDBRequest_onsuccess;
        }
        if (pref.debug_mode['20']) db_info.get(db).done_put += reqs.length;
      } catch (e){
//        if (pref.debug_mode['20'])
          console.log('IDB: fail to put: '+e.name+': ' + JSON.stringify(req,['no','key','kind']), e, db_info.get(db));
        if (e.name==='InvalidStateError') { // retry when latter requests catch up with a prior request which creates new objectStore.
          add_req(reqs_re, db.name, req); // retry
          if (pref.debug_mode['20']) console.log('IDB: re-scheduled:');
        }
        complete_func({target:{db:db}});
      }
    }
//    function IDBRequest_onsuccess(e){
//      var db = clear_wdg_get_db(e);
//      var info = db_info.get(db);
//      if (info) info = info.tr_info.get(e.target.transaction);
//      if (info && --info.nof_reqs===0) info.func(e);
//    }
    function delete_1(db,req, complete_func){
      tr_set(db.transaction(req.no,'readwrite'), complete_func).objectStore(req.no).delete(req.key); // .onsuccess = IDBRequest_close;
    }
    function tr_set(tr, complete_func, req, db){
      tr.oncomplete = (req)? IDBTransaction_oncomplete : complete_func;
      tr.onabort    = IDBTransaction_onabort;
      tr.onerror    = IDBTransaction_onerror;
      if (req) db_info.get(db).tr_info.set(tr,{req:req, func:complete_func}); //, nof_reqs:req.reqs && req.reqs.length || 1});
      return tr;
    }
    function IDBTransaction_oncomplete(e){
      var info = db_info.get(e.target.db);
      if (info) {
        var func = info.tr_info.get(e.target).func;
        info.tr_info.delete(e.target);
        if (func) func(e);
      }
    }
    function clean_up(db,req, complete_func){
      var contains = db.objectStoreNames.contains('Meta');
      if (contains) tr_set(db.transaction('Meta','readwrite'), complete_func).objectStore('Meta').get('pruned_time').onsuccess = clean_1_onsuccess;
      else {
        times_pruned[db.name].write = true;
        clean_up_end_proc(db,req, complete_func, true);
      }
    }
    function clean_1_onsuccess(e, prune){
      var db = e.target.transaction.db;
      var src = e.target.result;
      if (src) {
        var info = db_info.get(db);
        var oss = info && info.oss || {};
        src = src.filter(function(v){return oss[v.no]===null;});
        var src_obj = {};
        for (var i=0;i<src.length;i++) {if (src_obj[src[i].no]!==undefined) src.splice(src_obj[src[i--].no],1); src_obj[src[i].no] = i;}
        times_pruned[db.name].list = src.concat(times_pruned[db.name].list.filter(function(v){return src_obj[v.no]===undefined;}));
      }
      times_pruned[db.name].write = true;
      clean_up_end_proc(db, null, null, true); // 'complete_func' will be called by transaction.oncomplete.
    }
//    function clean_up(db,req, complete_func){ // working code.
//      req.complete_func = function(e, tr){
//        if (tr) {
//          if (req.tgts.length===0) return;
//          tr.oncomplete = null;
//        }
//        clean_1(db,req, complete_func);
//      };
////      req.complete_func = function(){clean_1(db,req, complete_func);};
//      req.tr = pref.archive.IDB.nof_tr;
//      for (var i=0;i<req.tr;i++) clean_1(db,req, complete_func);
//    }
//    function clean_1(db,req, complete_func){
//      if (req.tgts.length===0) {
//        if (--req.tr===0) clean_up_end_proc(db,req, complete_func, true);
//        return;
//      }
//      if (db_info.get(db).wdg) delete db_info.get(db).wdg;
//      var no = req.tgts.shift();
//      if (pref.debug_mode['23']) console.log('clean_1: '+no+', '+req.tgts.length);
//      tr_set(db.transaction(no,'readwrite'), req.complete_func).objectStore(no).get('pruned_time').onsuccess = (req.prune)? clean_1_onsuccess_prune : clean_1_onsuccess;
//    }
////    function clean_1(db,req){ // CAUSE ERROR in Chrome, "Internal error: Too many transactions queued." Upper limit is about 400-500.
////      tr_inc(db);
////      var tr = db.transaction(req.no,'readwrite');
////      tr.objectStore(req.no).get('pruned_time').onsuccess = (req.prune)? clean_1_onsuccess_prune : clean_1_onsuccess;
////      tr.oncomplete = IDBRequest_close;
////      tr.onaboart = IDBTransaction_onabort;
////      tr.onerror = IDBTransaction_onerror;
////    }
//    function clean_1_onsuccess_prune(e){
//      clean_1_onsuccess(e, true);
//    }
//    function clean_1_onsuccess(e, prune){
//      var value = e.target.result;
//      if (!value && !prune) return;
//      var date = value || Date.now();
//      var db_name = e.target.transaction.db.name;
//      var board = db_name.replace(pref.script_prefix+'.','');
//      check_cleaned[db_name][check_cleaned[db_name].length] = {no:e.target.source.name, pruned_time:date};
//      if (!value && prune) e.target.source.put(date, 'pruned_time');
//      else if (pref.test_mode['76']) e.target.transaction.oncomplete(null, e.target.transaction);
//    }
//////    function clean_1_onsuccess(e, prune){ // working code.
//////      var value = e.target.result;
//////      var date = Date.now();
//////      if (!value && prune) e.target.source.put(date, 'pruned_time');
//////      else {
//////        if (value + pref.archive.IDB.prune * 3600000 < date) acc(site.nickname, e.target.transaction.db.name.replace(pref.script_prefix+'.',''),
//////                                                                 e.target.source.name, null, null, 'delete_th');
//////      }
//////    }
    function clean_up_end_proc(db,req, complete_func, put_to_IDB){
      var tgts = times_pruned[db.name].list;
//      if (sort) tgts.sort(function(a,b){return a.pruned_time - b.pruned_time;}); // sort is in-place
      var time_prune = Date.now() - pref.archive.IDB.prune * 3600000;
//      var board = db.name.replace(pref.script_prefix+'.','');
      var i=0;
      while (i<tgts.length && tgts[i].pruned_time < time_prune) {
        if (pref.archive.IDB.auto_restore_remove) gClg.Remove_thread_refresh(site.nickname + db.name.replace(/^[^\/]*/,'') + tgts[i].no, null, true);
        if (pref.archive.IDB.prune_flush) add_req(reqs_rw, db.name, {no:tgts[i++].no, key:null, obj:flush_and_prune, kind:'get_all'}, true);
        else add_req(reqs_vc, db.name, {no:tgts[i++].no, key:null, obj:null, kind:'delete_th'}, true);
      }
//      while (i<tgts.length && tgts[i].pruned_time < time_prune) add_req(reqs_vc, db.name, {no:tgts[i++].no, key:null, obj:null, kind:'delete_th'}, true); // acc(site.nickname, board, tgts[i++].no, null, null, 'delete_th');
      if (i>0) tgts.splice(0,i);
      if (times_pruned[db.name].write && (put_to_IDB || i!==0)) add_req(reqs_rw, db.name, {no:'Meta', key:'pruned_time', obj:tgts, kind:'put'});
      if (complete_func) complete_func();
    }
    function flush_and_prune(domain, board, no, result){
      var filename = 'CatChan_archive_pruned_'+domain+'-'+board.slice(1,-1)+'-'+no+'.tar';
      var file_id = 'pruned_'+domain+board+no+Date.now();
      archiver.event_funcs['export_thread'](domain, board, no, result, file_id, filename);
      acc(domain, board, no, null, null, 'delete_th');
    }
    function get_all(db,reqs_in, complete_func){
//      var req = tr_set(db.transaction(req.no,'readonly'), complete_func).objectStore(req.no).getAll(req.key).onsuccess = req.obj;
      var IDBT = tr_set(db.transaction(reqs_in.nos,'readonly'), complete_func);
      for (var i=0;i<reqs_in.reqs.length;i++) {
        var req = reqs_in.reqs[i];
        if (pref.debug_mode['23']) console.log('get_all: '+db.name.replace(/^[^\/]*/,'')+req.no+', '+req.key);
        var IDBR = IDBT.objectStore(req.no).openCursor((req.key==='posts')? IDBKeyRange.bound('posts', 'postt', false, true) : undefined);
        IDBR.onsuccess = get_all_onsuccess;
        req.result = {};
        req_dictionary.set(IDBR,req);
      }
    }
    var req_dictionary = new Map();
    function clear_wdg_get_db(e){
      var db = e.target.transaction.db;
      var info = db_info.get(db);
      if (info) info.pass++; // may be stopped by watchdog.
      return db;
    }
    function get_all_onsuccess(e){
      var db = clear_wdg_get_db(e);
      var IDBR = e.target;
      var req = req_dictionary.get(IDBR);
      var cursor = e.target.result;
      if (cursor) {
        req.result[cursor.key] = (cursor.key.indexOf && cursor.key.indexOf('posts')===0)? JSON.parse(cursor.value) :
//                                 (req.key==='posts')? null :
                                 (cursor.value instanceof ArrayBuffer)? comf.arraybuffer2blob(cursor.key, cursor.value) : cursor.value;
        cursor.continue();
      } else {
        req_dictionary.delete(IDBR);
        var board = e.target.transaction.db.name.replace(pref.script_prefix+'.','');
        var no = e.target.source.name;
        setTimeout((function(req,board,no){return function(){req.obj(site.nickname, board, no, req.result);};})(req,board,no),0);
        if (pref.debug_mode['29']) {
          var sum = 0;
          for (var i in req.result) sum += (i.indexOf('posts')===0)? JSON.stringify(req.result[i]).length*2 : req.result[i] && req.result[i].size;
          console.log('IDB: '+board+no+': '+Object.keys(req.result).length+' files, '+sum.toLocaleString()+' bytes.');
        }
      }
    }
    function IDBRequest_close(e){
      var db = e.target.source && e.target.source.transaction.db || e.target.db || e.target.result;
      var info = db_info.get(db);
      if (!info) return; // closed by WATCHDOG.
      if (info.tr_count!==1) info.tr_count -= 1; //      if (tc===1) {db.close();tr_count.delete(db);}
      else IDB_close(db, info);
    }
    function IDBT_rereq(db, tr, message){
      var info = db_info.get(db);
      if (info && tr.mode!=='readonly') {
        var req = info.tr_info.get(tr).req;
        add_req((req.kind==='delete_th')? reqs_vc : reqs_re, db.name, req);
        console.log(message+': close and re-req, '+req.kind);
      }
    }
    function IDB_close(db, info, force){
      if (!info) info = db_info.get(db);
      if (force && info) for (var i of info.tr_info.keys()) IDBT_rereq(db, i, force);
      db_info.delete(db);
      var case_of_test_mode78 = !force && info.req_kinds['delete_th']!==undefined && Object.keys(info.req_kinds).length===1 || db.version==1;
      if (pref.debug_mode['26'] && case_of_test_mode78) console.log('Hit the case of test_mode[78]');
//      if (Object.keys(info.req_kinds).length===0 && db.version===1) console.log('test: v1');
//      else
      if (!case_of_test_mode78 || pref.test_mode['78']) db.close();
      else setTimeout(function(){db.close();},0);
      if (pref.debug_mode['20']) console.log('IDB: closed: '+db_info.size+' '+db.name.replace(/^[^\/]*/,'')+' v'+db.version+', '+((info)? info.type+', done:'+info.done+', done_put:'+info.done_put+', '+info.coms : ''));
      if (info) info.db = db;
      last_closed = info;
      indicator_update();
    }
    var last_closed = null; // for debug
    var db_info = new Map();
    function tr_inc(db){
      db_info.get(db).tr_count += 1;
    }
    var times_pruned = {};
    var version = {};
    var waiting_open = {}; // exclusive request,(closing a transaction without any acceess may cause an IDB ABORT ERROR)
    var reqs_vc = {};
    var reqs_rw = {};
    var reqs_re = {};
    var sub_callbacks = {};
    function acc(domain,board,no,key,obj, kind){
//      var db_name = pref.script_prefix+'.'+domain+board;
//      if (site2[domain].domain_IDB_check) domain = site2[domain].domain_IDB_check(board);
      if (domain===site.nickname) {
        var db_name = pref.script_prefix+'.'+board;
        var req_obj = {no:no, key:key, obj:obj, kind:kind};
        var reqs = (kind==='delete_th')? reqs_vc : reqs_rw;
        add_req(reqs, db_name, req_obj, true);
//      if (!reqs[db_name]) reqs[db_name] = [req_obj]; // not improved.
//      else reqs[db_name].push(req_obj);
//      open_db(db_name, ver);
      } else {
        if (typeof(obj)==='function') {
          sub_callbacks[domain+board+no] = obj;
          obj = 'sub_callback';
        }
//try {
        if (pref.test_mode['86'] && (obj instanceof ArrayBuffer)) send_message(domain,['IDB',['ACC',[domain,board,no,key,null, kind]]], null, [obj]); // https://bugs.chromium.org/p/chromium/issues/detail?id=334408
        else send_message(domain,['IDB',['ACC',[domain,board,no,key,obj, kind]]]);
//} catch (e) { // BUG. pn is attatched in some case.
//  console.log(e);
//}
      }
    }
    function open_db(db_name, ver, from_watchdog){
      var key = db_name+((ver)? 'vc' : 'rw');
      if (waiting_open[key]!==undefined && (!from_watchdog || ++waiting_open[key]<db_info.size)) {
        if (pref.debug_mode['27']) if (from_watchdog) console.log('IDB: WATCHDOG: '+key+', '+waiting_open[key]);
        return;
      }
      waiting_open[key] = 0;
      var req = window.indexedDB.open(db_name, ver && version[db_name]+1 || undefined); // version[db_name]+1 is NaN at initial.
      req.onerror = IDBRequest_onerror;
      req.onsuccess = IDBRequest_onsuccess_open;
      req.onupgradeneeded = IDBRequest_onupgradeneeded;
      req.onblocked = IDBRequest_onblocked;
    }
    function req_raw(domain,db_name,os_name,key,obj, kind){
      if (domain===site.nickname) {
        var req_obj = {no:os_name, key:key, obj:obj, kind:kind};
        var reqs = (kind==='delete_th')? reqs_vc : reqs_rw;
        add_req(reqs, db_name, req_obj, true);
      }
    }
    function add_req(reqs, db_name, req, call){
      indicator_req_count++;
      if (!indicator && indicator_parent) {
        indicator = indicator_parent.shift('limegreen', 'w', 'IDB', 0);
        indicator.report({start:Date.now(), prog_str:'Opening'});
      } else indicator_update();
      if (!reqs[db_name]) {
        reqs[db_name] = new ReqFifo(db_name, reqs!==reqs_vc, req);
        if (call) open_db(db_name, reqs===reqs_vc);
      } else {
        var spawn = reqs===reqs_rw && reqs[db_name].isEmpty;
        reqs[db_name].push(req);
        if (spawn) {
          for (var i of db_info.keys()) {
            var info = db_info.get(i);
            if (info.crawler<pref.archive.IDB.nof_tr && i.name===db_name && info.type==='success') {
              info.crawler++;
              setTimeout(info.complete_func, 100);
              break;
            }
          }
        }
      }
      watchdog.start(pref.archive.IDB.watchdog*1000);
    }
    var ReqFifo = function(db_name, allow_multi, init_val){
      this.ary = [];
      this.ary_ro = [];
//      this.count = 0;
      this.allow_multi = allow_multi;
      if (!this.reqs[db_name]) this.reqs[db_name] = {};
      this.reqs = this.reqs[db_name]; // shared
      this.cls = {}; // req clusters
      if (init_val) this.push(init_val);
      this.cl = null;
      this.cl_idx = 0;
    };
    ReqFifo.prototype = {
      shift: function(){
        var max = (this.allow_multi)? pref.archive.IDB.nof_cl_max : 1;
        var i=0;
        while (i<this.ary_ro.length && i<max && this.ary_ro[i].kind==='get_all') i++;
        if (i!=0) return {kind:'get_all_m', reqs:this.ary_ro.splice(0,i)};
        else if (this.ary_ro.length!==0) return this.ary_ro.shift();
//        if (this.ary_ro.length>0) return this.ary_ro.shift();
        if (this.ary.length===0 && !this.cl) return null;
if (!pref.test_mode['79']) {
        var retval = [];
  if (!pref.test_mode['81']) {
        var ref_no;
        while (retval.length<max) {
          var req = this.ary[i];
          if (!req || req.kind!=='put') break;
          if (ref_no!==undefined && (!req.no || ref_no!==req.no)) break;
          if (req.cl) {
            if (req.idx===undefined) {
              this.ary[i] = {kind:'put', cl:[req].concat(req.cl), idx:0, no:req.no};
              req.cl = null;
              req = this.ary[i];
              this.cls[req.no] = req;
            }
            if (req.idx>=req.cl.length-1) {
              this.cls[req.no] = null;
              i++;
            }
            req = req.cl[req.idx++]; // all reqs are 'put'.
          } else {
            this.cls[req.no] = null;
            i++;
          }
          retval[retval.length] = req;
          delete this.reqs[req.no + req.key]; // shared, so must use delete.
          if (retval.length===pref.archive.IDB.nof_cl-1) ref_no = req.no;
        }
        if (this.ary[i] && this.ary[i].cl && this.ary[i].idx<this.ary[i].cl.length) { // round robin
          var cl = this.ary.splice(i,1)[0];
          this.ary[this.ary.length] = cl;
        }
  } else {
        while (retval.length<max) { // working code.
          var req;
          if (this.cl===null) {
            req = this.ary[i];
            if (!req || req.kind!=='put') break;
            i++;
            if (req.cl) {
              this.cls[req.no] = null;
              this.cl = req.cl;
              this.cl_idx = 0;
              req.cl = null; // for retry.
            }
          } else {
            req = this.cl[this.cl_idx++]; // all reqs are 'put'.
            if (this.cl_idx>=this.cl.length) this.cl = null;
          }
          retval[retval.length] = req;
          delete this.reqs[req.no + req.key]; // shared, so must use delete.
        }
  }
        if (i>0) this.ary.splice(0,i);
        if (i===0 && this.ary[0] && this.ary[0].kind==='check_clean') this.cls['check_clean'] = null;
        return (retval.length>1)? {kind:'put_m', reqs:retval} : (retval.length>0)? retval[0] : this.ary.shift();
} else {
        while (i<max) { // working code // not debugged enough
          var req = this.ary[i];
          if (!req) break;
          if (req.cl) {
            this.ary = this.ary.slice(0,i+1).concat(req.cl,this.ary.slice(i+1));
            req.cl = null; // for retry.
          }
          if (req.kind==='put') {
            this.cls[req.no] = null;
            delete this.cls[req.no + req.key]; // shared, so must use delete.
            i++;
          } else break;
        }
////        while (i<max && this.ary[i] && this.ary[i].kind==='put') {delete this.cls[this.ary[i].no + this.ary[i].key]; i++;} // working code.
////        this.count += i;
        return (i>1)? {kind:'put_m', reqs:this.ary.splice(0,i)} :
                      this.ary.shift();
}
      },
      push: function(req){
        if (req.kind==='get_all' || req.kind==='list_os') {
          this.ary_ro[this.ary_ro.length] = req;
          return;
        }
        if (req.kind==='put') {
          var key = req.no + req.key;
          var req_old = this.reqs[key];
        }
        if (req_old) req_old.obj = (req.key==='posts')? req_old.obj.concat(req.obj) : req.obj;
        else if (key) {
          if (req.key==='posts' || req.key==='pruned_time' || req.key==='posts_deleted') this.reqs[key] = req;
//          var idx = this.reqs[req.no] - count; // maybe NaN // not debugged.
//          if (idx>1 && idx!==this.ary.length) {
//            this.ary.splice(idx,req);
//            this.reqs[req.no] = count + idx +1;
//          } else {
//            this.ary[this.ary.length] = req;
//            this.reqs[req.no] = count+this.ary.length;
          var req_cl = this.cls[req.no];
          if (req_cl) { // 'reqs' is used by 'put_m'.
            if (req_cl.cl) req_cl.cl[req_cl.cl.length] = req;
            else req_cl.cl = [req];
          } else {
            this.ary[this.ary.length] = req;
            this.cls[req.no] = req;
          }
        } else if (req.kind==='check_clean') {
          var key = null + req.key;
          var req_old = this.cls[key];
          if (req_old) req_old.reqs[req_old.reqs.length] = req;
          else {
            req = {kind:'clean_m', reqs:[req], no:null};
            this.cls[key] = req;
            this.ary[this.ary.length] = req;
          }
        } else this.ary[this.ary.length] = req;
      },
      push_all: function(reqs){
        while (!reqs.isEmpty) this.push(reqs.shift());
      },
//      get length(){return this.ary.length - this.idx;},
//      get length(){return this.ary.length + this.ary_ro.length;},
//      get length(){return this.ary.length + this.ary_ro.length + (this.cl && this.cl.length-this.cl || 0);}, // approx.
      get length(){return this.ary.length + this.ary_ro.length;},
      get length_whole(){
        var sum = 0;
        for (var i=0;i<this.ary.length;i++) sum += (this.ary[i].cl)? this.ary[i].cl.length-(this.ary[i].idx||0) : 1;
        return sum + this.ary_ro.length;
      },
      get isEmpty(){return this.ary.length===0 && !this.cl && this.ary_ro.length===0;},
      reqs: {},
      debug_query: function(){
        var whole_array = Array.prototype.concat.apply(this.ary_ro, this.ary.map(function(v){return (v.cl)? ((!pref.test_mode['81'])? v.cl.slice(v.idx || 0) : [v].concat(v.cl)) : v;}));
        var coms = [];
        var count = 1;
        var i=0;
        while (coms.length<20 && i++<whole_array.length) {
          if (whole_array[i] && whole_array[i-1].kind===whole_array[i].kind) count++;
          else {
            coms[coms.length] = whole_array[i-1].kind+((count!=1)? '*'+count:'');
            count = 1;
          }
        }
        if (i<whole_array.length) coms[coms.length-1] += '...';
//        var coms = whole_array.map((function(){ // working code.
//          var count = 1;
//          return function(v,i,a){
//            if (a[i+1] && v.kind===a[i+1].kind) {
//              count++;
//              return null;
//            } else {
//              var retval = v.kind+((count!=1)? '*'+count:'');
//              count = 1;
//              return retval;
//            }
//          }
//        })()).filter(function(v){return v;});
        return {coms:coms, length:this.ary_ro.length+'/'+this.ary.length+'/'+whole_array.length};
      }
    };
    var indicator_parent = null;
    var indicator = null;
    var indicator_req_count = 0;
    var indicator_update = function(){};
    var indicator_update = DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(function(){
      if (!indicator) return;
      var stats = get_status();
      indicator.report({prog_str:stats.str});
      if (stats.total===0) {
        indicator.report({end:Date.now()});
        indicator = null;
        indicator_req_count = 0;
      }
    },100));
    function get_status(){
      var nof_crawlers = 0;
      for (var db of db_info.keys()) nof_crawlers += db_info.get(db).crawler;
      var sums = [0, 0, 0, 0, 0, 0, Object.keys(reqs_vc).length, Object.keys(reqs_rw).length, Object.keys(reqs_re).length, nof_crawlers, db_info.size, Object.keys(waiting_open).length];
      var tgts = [reqs_vc,reqs_rw,reqs_re];
      for (var i=0;i<3;i++) {
        if (sums[i+6]) for (var j in tgts[i]) {
          sums[i+3] += tgts[i][j].length;
          sums[i]   += tgts[i][j].length_whole;
        }
      }
      return {sums:sums, total:sums.reduce(function(a,b){return a+b;}), str:sums.join('/')+'/'+indicator_req_count};
    }
    var watchdog = new Watchdog(function (){
      var stats = get_status();
      if (pref.debug_mode['27']) console.log('IDB: '+stats.str);
      var inactivated = {};
      for (var db of db_info.keys()) {
        var info = db_info.get(db);
        if (pref.debug_mode['27']) console.log(info);
        if (info.wdg!==info.pass) {
          info.wdg = info.pass;
//        if (tgt.req && (!tgt.wdg || tgt.wdg!==tgt.req)) tgt.wdg = tgt.req;
//        if (!tgt.wdg || tgt.wdg!==tgt.req) tgt.wdg = tgt.req; // BUG. lock when tgt.req===unefined or tgt.req===null. // This was patched in 'req_1'
          inactivated[db.name] = null;
        } else {
          indicator_report();
          IDB_close(db, info, 'IDB: WDG');
        }
      }
      for (var i in reqs_vc) if (inactivated[i]!==null && !reqs_vc[i].isEmpty) open_db(i,true, true);
      for (var i in reqs_rw) if (inactivated[i]!==null && (!reqs_vc[i] || reqs_vc[i].isEmpty) && !reqs_rw[i].isEmpty) open_db(i, undefined, true);
      for (var i in reqs_re) if (inactivated[i]!==null && (!reqs_vc[i] || reqs_vc[i].isEmpty) && (!reqs_rw[i] || reqs_rw[i].isEmpty) && !reqs_re[i].isEmpty) {
        reqs_re2rw(i);
        open_db(i, undefined, true);
      }
      for (var db_name_key in waiting_open) {
        var db_name = db_name_key.replace(/(rw|vc)$/,'');
        if (inactivated[db_name]!==null && !reqs_vc[db_name] && !reqs_rw[db_name]) {
          indicator_report();
          open_db(db_name, db_name_key.search(/vc$/)!=-1, true);
        }
      }
      if (stats.total!==0) watchdog.start(pref.archive.IDB.watchdog*1000);
      function indicator_report(){
        if (stats.str) {
          indicator.report({err:'WDG: '+stats.str});
          stats.str = '';
        }
      }
    }, 60000);
    return {
      req: acc, // write: function(domain, board, no, key, data,     cmd)
                // read : function(domain, board, no, key, callback, cmd)
      req_raw: req_raw, // read : function(domain, db_name, os_name, key, callback, cmd)
      clean_up: function(domain, board, nos){
        if (version[pref.script_prefix+'.'+board]) {
          acc(domain,board,null,null,nos, 'clean_up');
          liveTag.mems[domain][board].IDB_synced = 1;
        }
      },
      set_indicator: function(indicator){
        if (!indicator_parent) indicator_parent = indicator;
//        indicator_parent.shift().set('Red','IDB:');
      },
      sub_funcs: function(args, list){
        if (args[0]==='ACC') {
          if (args[1][4]==='sub_callback') args[1][4] = IDB.sub_ack;
          else if (list.length>0) args[1][4] = list[0];
          acc.apply(this,args[1]);
        } else if (args[0]==='ACK') {
          var dbtk = args[1].slice(0,3).join('');
          if (sub_callbacks[dbtk]) sub_callbacks[dbtk].apply(this,args[1]);
          delete sub_callbacks[dbtk];
        }
      },
      sub_ack: function(domain, board, no, result){
        send_message('parent',['IDB',['ACK',[domain,board,no,result]]]);
      },
    };
  })() : null;

  var archiver = (function(){
// http://www.redout.net/data/tar.html
// dollchan
// https://github.com/beatgammit/tar-js/blob/master/lib/tar.js
// https://gist.github.com/kig/417483
    var tar = (function(){
      function make_header(blob, lth, suffix){
        var header = new Uint8Array(512); // UInt8 for checksum.
        suffix = (suffix.substr(0,3)==='tn_')?  'thumbs/' + encode_filename(suffix.substr(3)) :
                 (suffix.substr(0,4)==='img_')? 'images/' + encode_filename(suffix.substr(4)) :
                 encode_filename(suffix);
        var name = lth.domain + '/' + encode_filename(lth.board.slice(1,-1)) + '/' + lth.no + '/' + suffix; // not to encode '/'
        set_header(header,   0, name, 100);  // name
        set_header(header, 100, '100777', 8);  // mode
        set_header(header, 108, '0', 8);       // uid
        set_header(header, 116, '0', 8);       // gid
        set_header(header, 124, (blob.byteLength||blob.size||0).toString(8), 12);  // fileSize, octal // blob may be arraybuffer.
        set_header(header, 136, Math.floor(Date.now()/1000).toString(8), 12);  // mtime, octal
        set_header(header, 148, '        ', 8);  // checksum 
        header[156] = 0x30;  // type 0 as a normal file.
        var checksum = 0;
        for(i=0;i<157;i++) checksum += header[i];
        set_header(header, 148, checksum.toString(8), 8); // checksum, octal
        return header;
      }
      function set_header(header, pos, numstr, len) {
        var j=0;
        while (j<numstr.length && j<len) header[pos++] = numstr.charCodeAt(j++) & 0xff;
        if (j<len) header[pos++] = 0x00;
      }
      function encode_filename(str){
        return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
          return '%' + c.charCodeAt(0).toString(16);});
      }
      var tars = {};
      var count = 0;
      return {
        add_blob: function(blob, lth, suffix, tarfile, ignore_limit){ // blob may be arraybuffer.
          if (!tars[tarfile]) tars[tarfile] = {files:[], size:0};
          var tfile = tars[tarfile];
          var size = blob.byteLength||blob.size;
          tfile.files[tfile.files.length] = make_header(blob, lth, suffix);
          tfile.size += 1;
          tfile.files[tfile.files.length] = blob;
          if (size%512!==0) tfile.files[tfile.files.length] = new Uint8Array(512-size%512);
          tfile.size += Math.floor((size+511)/512);
          if (!ignore_limit && (tfile.size>pref.archive.tarsize*2*1024 || pref.test_mode['71'])) this.flush(tarfile);
        },
        flush: function(tarfile, filename){
          if (!tarfile) {for (var i in tars) this.flush(i); return;}
          if (tars[tarfile]) { // called from both 'httpd.check_timestamp_and_flush' and 'archiver.flush_req', so the file might be flushed.
            tars[tarfile].files[tars[tarfile].files.length] = new Uint8Array(1024);
            download_url4(new Blob(tars[tarfile].files, {type:'application/x-tar'}), filename || 'CatChan_archive_updates_'+site.nickname+'_'+tarfile+'_'+(count++)+'.tar');
            delete tars[tarfile];
          }
        },
      };
    })();
    function store_posts(content, type, lth, suffix){
      if (!window.URL) return;
      if (!timestamp) set_timestamp();
      download_url3(new Blob([content], {type:type}), lth, suffix, timestamp);
    }
    function download_url3(blob, lth, suffix, timestamp){
      if (pref.archive.tar) tar.add_blob(blob, lth, suffix, timestamp);
      else download_url4(blob, 'CatChan_'+lth.key.replace(/\//g,'_') + '_' + suffix);
    }
    function download_url4(blob, filename){ // blob may be arraybuffer.
      if (blob instanceof ArrayBuffer) blob = comf.arraybuffer2blob(filename, blob);
      if (brwsr.ff && !httpd.isLocal) {
        send_message('parent',['ARCHIVER', ['DOWNLOAD', [blob, filename]]]);
        return;
      }
      var url = window.URL.createObjectURL(blob);
      download_url(url, filename);
      window.URL.revokeObjectURL(url);
//      if (!brwsr.ff || httpd.isLocal) window.URL.revokeObjectURL(url);
    }
    function download_url(url, filename){ // this doesn't work if url is cross-origined, for example, i.4cdn.org from boards.4chan.org in 4chan.
      if (pref.test_mode['70']) return;
//      if (brwsr.ff && !httpd.isLocal) {
//        send_message('parent',['ARCHIVER', ['DOWNLOAD', [url, filename, site.nickname]]]); // doesn't work
//        return;
//      }
      var a_link = document.createElement('div');
      a_link.innerHTML = '<a href=' + url + ' download="' + filename + '">';
      if (brwsr.ff) site.script_body.appendChild(a_link);
      a_link.childNodes[0].click();
      if (brwsr.ff) site.script_body.removeChild(a_link);
    }
    var proto_archive = {initiator:'archive', get responseType(){return (pref.test_mode['85'])? 'blob' : 'arraybuffer';}, archive:true, callback_1:httpd.onload_archive, callback_1_fail:httpd.onload_archive_fail,
                         get max(){return this.tgts.length;},
                         get tgt(){return this.key + this.url.substr(this.url.lastIndexOf('/'));},
                        };
    var timestamp = null;
    var month = {Jan:'01', Feb:'02', Mar:'03', Apr:'04', May:'05', Jun:'06', Jul:'07', Aug:'08', Sep:'09', Oct:'10', Nov:'11', Dec:'12'};
    function set_timestamp(){
      var arr = new Date().toString().split(' ').slice(1,5);
      timestamp = [arr[2], month[arr[0]], arr[1], arr[3].replace(/:/g,'')].join('');
      httpd.req({tgts:[], timestamp:timestamp, __proto__:proto_archive},0);
    }
    var flush_req = DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(function(){
//      if (reqs.length==0 && xhrs.size==0) tar.flush();
      httpd.check_timestamp_and_flush(timestamp);
    },5000));
    
    var xhrs = new Map();
    var reqs = [];
//    function download_url2(url, lth, kind, to_file, to_idb){ // for test
//      if (!timestamp) set_timestamp();
//      var req = {url:url, lth:lth, kind:kind, to_file:to_file, to_idb:to_idb, timestamp:timestamp};
    function download_url2(reqs_in){
      httpd.req_add({tgts:reqs_in, timestamp:timestamp, __proto__:proto_archive},0);
      return;
    }
////    function download_url2(reqs_in){ // working code.
////      for (var i=0;i<reqs_in.length;i++) {
////        if (xhrs.size>=10) reqs.push(reqs_in[i]);
////        else {
////          var xhr = new XMLHttpRequest();
////          xhr.responseType = 'blob';
////          xhr.onload  = download_url2_1_onload;
////          xhr.onerror = download_url2_1_onload;
////          xhr.onabort = download_url2_1_onload;
////          download_url2_1(xhr, reqs_in[i]);
////        }
////      }
////    }
////    function download_url2_1(xhr, req){ // for test
////      xhrs.set(xhr, req);
////      xhr.open('GET', req.url);
////      xhr.send();
////      return xhr;
////    }
////    function download_url2_1_onload(e){
////      var xhr = e.target;
////      var req = xhrs.get(xhr);
////      if (req.from) delete list_all_obj_downloading[req.from];
////      if(xhr.status === 200 && e.type==='load') {
////        var suffix = req.kind + '_'+ xhr.responseURL.replace(/.*\//g,'');
////        if (req.to_file) download_url3(xhr.response, req.lth, suffix, req.timestamp);
////        if (pref.features.IDB) if (req.to_idb) IDB.req(req.lth.domain, req.lth.board, req.lth.no, suffix, xhr.response, 'put');
////      }
////      if (reqs.length>0) {
////        req = reqs.shift();
////        download_url2_1(e.target, req);
////      } else xhrs.delete(xhr);
////      if (xhrs.size===0) tar.flush();
////    }
    function img_dl(kind, th, lth, posts, img, img_idb, webm, webm_idb, reqs){
      for (var i=0;i<posts.length;i++) {
        var url = (kind==='tn')? site2[th.domain].catalog_json2html3_thumbnail(posts[i],th.board) : site2[th.domain].catalog_json2html3_src(posts[i],th.board);
        if (url) {
          if (!timestamp) set_timestamp();
//          var proto = {lth:lth, kind:kind, to_file:img||webm, to_idb:img_idb||webm_idb, timestamp:timestamp};
          var proto = {domain:th.domain, board:th.board, no:th.no, key:th.key, kind:kind, to_file:img||webm, to_idb:img_idb||webm_idb, timestamp:timestamp, __proto__:proto_archive};
          if (site2[th.domain].archive_patch_domain) site2[th.domain].archive_patch_domain(proto);
          if ((url.substr(-5,5)==='.webm')? webm || webm_idb : img || img_idb) reqs[reqs.length] = {url:url, __proto__:proto};
          if (posts[i].extra_files) for (var j=0;j<posts[i].extra_files.length;j++) {
            url = (kind==='tn')? site2[th.domain].catalog_json2html3_thumbnail(posts[i].extra_files[j],th.board) : site2[th.domain].catalog_json2html3_src(posts[i].extra_files[j],th.board);
            if ((url.substr(-5,5)==='.webm')? webm || webm_idb : img || img_idb) reqs[reqs.length] = {url:url, __proto__:proto};
          }}}
    }

    var url_funcs_wrapped = false;
    function url_funcs_wrap(){
      for (var d in site2) {
        if (site2[d].hasOwnProperty('catalog_json2html3_thumbnail')) site2[d].catalog_json2html3_thumbnail = wrap(site2[d].catalog_json2html3_thumbnail);
        if (site2[d].hasOwnProperty('catalog_json2html3_src'))       site2[d].catalog_json2html3_src       = wrap(site2[d].catalog_json2html3_src);
        if (site2[d].hasOwnProperty('parse_funcs')) {
          var tgts = ['catalog_html','page_html','thread_html','post_html'];
          for (var i=0;i<tgts.length;i++)
            if (site2[d].parse_funcs.hasOwnProperty(tgts[i]) && site2[d].parse_funcs[tgts[i]].hasOwnProperty('op_img_url'))
              site2[d].parse_funcs[tgts[i]].op_img_url = wrap(site2[d].parse_funcs[tgts[i]].op_img_url);
        }
      }
      function wrap(func){
        return function(th, board){
          var url = func.call(this, th, board);
          return (url && th.localArchive)? archiver.url2file(th.localArchive, url) : url;
        }
      }
    }
    var archive_no = 0;
    function restore(file, result, archive, IDB, refresh_tgt){
      if (!window.URL) return;
      var th_obj = (IDB)? result :
                   (file.type==='text/html')? new DOMParser().parseFromString(result, 'text/html') : JSON.parse(result);
      var dbt = (pref.archive.format==='auto')? comf.name2domainboardthread(file.name.replace(/\.[^\.]*$/,'').split('_')[0].split('-').slice(0,3).join('/'),true) :
                                                [site0.domains[pref.archive.domain],
                                                 '/'+pref.archive.board.replace(/\//g,'')+'/'];
      if (pref.test_mode['80']) dbt[1] = dbt[1].slice(0,-1) + ((IDB)? '_IDB/' : '_File/');
      if (pref.archive.fix_inconsistency && dbt[0]!=='meguca') {
        if (th_obj.posts[0].no!=dbt[2]) { // lain/drg/4654
          th_obj.posts = [comf.deep_copy(th_obj.posts[0])].concat(th_obj.posts);
          th_obj.posts[0].no = parseInt(dbt[2],10);
          console.log('Archiver: Fixed: added dummy OP: '+dbt.join(''));
        }
        for (var i=0;i<th_obj.posts.length;i++) if (th_obj.posts[i].filename===false) {
          th_obj.posts[i].filename = 'LOST_AT_ARCHIVE';
          console.log('Archiver: Fixed: filename was lost: '+dbt.join('')+'#'+th_obj.posts[i].no);
        }
      }
      if (!url_funcs_wrapped) {
        url_funcs_wrap();
        url_funcs_wrapped = true;
      }
      var ths = cataLog.scan_boards_keyword_callback2(dbt[0]+','+dbt[1]+','+ (archive_no++)+',thread_' + ((file.type==='text/html')? 'html' : 'json'),
                                                      {date:Date.now(), status:200, response:th_obj},
                                                      ['archive_restore',{localArchive:archive, page:(IDB)?'IDB':'File', archiveFile:(IDB)?'IDB':file, refresh:refresh_tgt || pClg,
                                                                          __proto__:cataLog.scan_boards_keyword_callback2_default_args}]);
//      cataLog.threads[ths[0].key][16].archiveFile = (IDB)? 'IDB' : file;
      return ths[0];
    }
    function start_1(req, lth, footer, from_check_op){
      if (req==='ARC' || req==='ARC1') {
        lth.archived |= (req==='ARC')? 6 : 1;
//        lth.archived |= (req==='ARC')? 6 : 5;
        if (!from_check_op) scan.list_nup.add_scan(lth);
      } else if (req==='UNARC') {
        lth.archived = 0;
        archiver.clean_list(lth.domain, lth.board, lth.no);
      }
      if (footer && pref.catalog.footer.archived) cataLog.Footer.update_force(lth.key);
    }
    var blist = {pn:null, time_bumped:null, time_created:null, time_posted:null, flag:null, footer:null, time_tu:null, pn_menu:null,
                 nof_posts:null, nof_files:null, tags:null, page:null, extracted:null,
                 parse_funcs:null, // THIS IS REDUNDANT, but this caused a bug, so this is for safe.
                 quotes:null, backlinks:null, reply_to_me:null, reply_of_mine:null, op_img_url:null, _links:null}; // BUG. meguca has 'backlinks'.
    function store_json_func(key,value){
      return (blist[key]===null)? undefined : value;
    }

// https://www.w3.org/TR/html5/scripting-1.html#the-script-element
//   Note: When inserted using the document.write() method, script elements execute (typically synchronously), but when inserted using innerHTML and outerHTML attributes, they do not execute at all.
    var sanitize = (function(){
      var funcs = {
        html: (function(){
          var pn = document.createElement('div');
          var wlist = {'A':null, 'SPAN':null, 'DIV':null, 'BLOCKQUOTE':null, 'BR':null, 'WBR':null,
                       'U':null, 'S':null, 'STRIKE':null, 'B':null, 'I':null, 'BIG':null, 'SMALL':null, 'TT':null, 'SUB':null, 'SUP':null, 'EM':null, 'STRONG':null};
          return function (key,value,obj){
            pn.innerHTML = value;
            var tags = pn.getElementsByTagName('*');
            for (var i=0;i<tags.length;i++) if (wlist[tags[i].tagName]!==null) {obj[key] = '';return;}
          }
        })(),
        string: function (key,value,obj){
          if (value.search(/[<>"'&]/)!=-1) obj[key] = '';
        }
      };
      return function(ary){
        if (ary) for (var j=0;j<ary.length;j++) {
          var obj = ary[j];
          for (var i in obj)
            if (typeof(obj[i])==='string')
              if (i==='com') funcs.html(i, obj[i], obj);
              else funcs.string(i, obj[i], obj);
        }
        return ary;
      }
    })();
    var ls_key_archive = pref.script_prefix + '.archived';
    var list_all_obj = {};
    if (localStorage) load_list();
    if (pref.debug_mode['28']) console.log('list_all_obj: ',list_all_obj);
    var list_all_obj_downloading = {};
    function list_all(){
      if (pref.debug_mode['28']) console.log('list_all_obj_downloading: ',list_all_obj_downloading);
      for (var d in liveTag.mems) {
        var time_unit = site2[d].parse_funcs.post_json.time_unit;
        for (var b in liveTag.mems[d]) {
          var tgt     = list_all_obj[d] && list_all_obj[d][b];
          var tgt_tmp = tgt || {};
          for (var t in liveTag.mems[d][b]) {
            var lth = liveTag.mems[d][b][t];
            var time_checked = list_all_obj_downloading[lth.key] || lth.time_checked/time_unit;
            if (lth.ed_t) time_checked = (pref.archive.editing_timeout)? bug_patch_for_meguca_editing_timeout(lth) || time_checked : lth.ed_t[lth.ed_t.length-1].time - 1; // rewinds to oldest editing post and retry after reload.
            if (lth.archived) {
              if (list_all_obj_downloading[lth.key]) tgt_tmp[t] = list_all_obj_downloading[lth.key];
              else if (!tgt_tmp[t] || tgt_tmp[t]<time_checked) tgt_tmp[t] = time_checked;
            } else if (lth.posts_saved) tgt_tmp[t] = - time_checked; // 'lth.time_checked/time_unit;' can be used here.
//            if (lth.archived && !(lth.key in pref3.archive.list_obj2)) list_all_obj[lth.key] = lth.time_checked; // BUG. NOT implemented yet.
          }
          if (!tgt && Object.keys(tgt_tmp).length!==0) {
            if (!list_all_obj[d]) list_all_obj[d] = {};
            list_all_obj[d][b] = tgt_tmp;
          } else if (tgt && Object.keys(tgt).length===0) delete list_all_obj[d][b];
        }
        if (list_all_obj[d] && Object.keys(list_all_obj[d])===0) delete list_all_obj[d];
      }
    }
    function bug_patch_for_meguca_editing_timeout(lth){
      var time_now = Date.now()/1000;
      for (var i=lth.ed_t.length-1;i>=0;i--) if (typeof(lth.ed_t[i])!=='string') {
        var time = lth.ed_t[i].time;
        if (time_now-time<3600) return time;
      }
      return null;
    }
    function load_list(e){
      if (!e || e.key===ls_key_archive) list_all_obj = JSON.parse(e && e.newValue || localStorage[ls_key_archive] || '{}');
    }
    function save_list(){
      if (pref3.archive.working && localStorage) {
        list_all();
        if (Object.keys(list_all_obj).length===0) delete localStorage[ls_key_archive];
        else localStorage[ls_key_archive] = JSON.stringify(list_all_obj);
        if (pref.debug_mode['28']) console.log('list_all_obj: ',list_all_obj);
      }
    }
    window.addEventListener('storage', load_list, false);
    window.addEventListener('beforeunload', save_list, false);
    return {
//      clean_list_all: function(domain, board, nos){
//        if (list_all_obj[domain] && list_all_obj[domain][board]) {
//          var list_all_obj_bd = list_all_obj[domain][board];
//          for (var no in list_all_obj_bd) if (nos[no]===undefined) this.clean_list(domain,board,no);
//        }
//      },
      clean_list: function(domain,board,no){
        if (list_all_obj[domain] && list_all_obj[domain][board]) delete list_all_obj[domain][board][no]; // Since 4chan has 'delayed pruning', final archiving should be done here.
      },
      check_op: function(th,lth){
        var kwd = pref.archive.kwd;
        if (pref.archive.store_auto && kwd.str && (kwd.sub || kwd.name || kwd.com) &&
            cataLog.threads && cataLog.catalog_filter_query_keyword.kwd(pref.archive.kwd, th.posts, th.domain) ||
            this.check_archived_time(th, th.parse_funcs.time_unit, true)!==false) start_1('ARC', lth, false, true);
      },
      check_archived_time: function(th, time_unit, init){ // time_check_old is undefined at rescan_dp, inherited value must be returned at !init.
        var val = init && pref.archive.list && pref_func.merge_obj5(th.key,pref3.archive.list_obj2,null); // find all expresion
        if (val && val.time) return val.time/time_unit || 0; // val may be {} if targets are specified by board or domain like 'lain' in list picker
        return (pref.archive.list_inherit || !init || val) && list_all_obj[th.domain] && list_all_obj[th.domain][th.board] && list_all_obj[th.domain][th.board][th.no] || false;
      },
//      check_archived_time: function(key, domain, board, no, time_unit){
//        if (pref.archive.list) {
//          var val = pref3.archive.list_obj2[key]; // find d/b/t@xxxx expression
//          if (val && val.time) return val.time;
//        }
//        var val_inherited = list_all_obj[domain] && list_all_obj[domain][board] && list_all_obj[domain][board][no];
//        if (pref.archive.list_inherit && val_inherited) return val_inherited;
//        if (pref.archive.list) val = pref_func.merge_obj5(key,pref3.archive.list_obj2,null); // find all expresion
//        return (val)? val.time/time_unit || val_inherited || 0 : false; // val may be undefined or null.
//      },
      update_archived_time: function(name){
        pref3.archive.working = true;
        if (pref3.archive.list_obj2[name] && pref3.archive.list_obj2[name].time) {
          var key = new RegExp('(^|,)'+name.replace(/\+/,'\\+')+'([\\^@!][^,\n]*)*(,|\n|$)','mg');
//          var millisec = datetime%1000;
//          var time_str = '@' + new Date(datetime).toLocaleString() + ((datetime%1000==0)? '' : '.'+millisec);
//          str = str.value.replace(key,',') + ',' + name + time_str + '\n';
          pref.archive.list_str = pref_func.fmt4str2(pref.archive.list_str.value.replace(key,',') + ',' + name + '\n'); // remove time_str
          pref3.archive.list_obj2[name].time = null;
        }
      },
      store_entry: function(req, downloaded_files){
        if (cataLog.threads) {
//          if (pref.archive.src==='shown') for (var name in cataLog.threads) if (cataLog.threads[name][9][0]===null) cataLog.threads[name][9] = cataLog.catalog_filter_query(name, true);
          for (var name in cataLog.threads) {
            var lth = liveTag.mems.getFromName(name);
            if (pref.archive.src==='stored' ||
                pref.archive.src==='watched' && lth.watched ||
                pref.archive.src==='shown' && pClg.filter_test(name, pClg.threads[name])) start_1(req, lth, true);
//               (pref.archive.src==='shown' && cataLog.threads[name][9][0])) start_1(req, lth, true);
          }
        }
      },
      start_1: start_1,
      restore_posts_from_IDB: function(value, th,  domain, board, no, result){
        var obj = this.event_funcs['consolidate_IDB_result'](result);
        if (obj.posts.length>1) {
          var lth = th.lth;
////          if (th.posts.length==1) {
////            lth.ta = {
////              posts: obj.posts,
////              nof_posts:obj.posts.length,
////              time_loaded:obj.posts[obj.posts.length-1].time,
////            };
//////          this.store(value, th, lth, null, null, true);
////          } else {
            obj.time_loaded = obj.posts[obj.posts.length-1].time;
            var deletedPosts = this.check_deleted_posts(value, th, lth, obj, true);
            if (lth.archived && deletedPosts && (lth.archived&0x02) && pref.archive.live.post) {
              var suffix_dbt = th.key.replace(/\//g,'-');
              store_posts(JSON.stringify(site2[th.domain].parse_funcs.thread_json.prep_to_archive(deletedPosts)), 'text/plain', lth, suffix_dbt+'_'+'deleted.json');
            }
////          }
        }
      },
      store_th_to_mem: function(value, th,lth, pf_store, editing_finished){ // for deleted posts detection, search, page mode (, archive).
        if (pf_store!=='no') {
          if (editing_finished && lth.ta.posts) {
            var ta_posts = lth.ta.posts;
            var j=ta_posts.length-1;
            for (var i=editing_finished.length-1;i>=0;i--) {
              while (j>=0 && ta_posts[j].no!=editing_finished[i].no) j--;
              if (j>=0) ta_posts[j] = editing_finished[i];
              else j=ta_posts.length-1;
            }
          }
          var pf_d = pref[cataLog.embed_mode].deleted_posts.detect;
          var posts = (th.posts && th.posts.length>1 && th.nof_posts>th.posts.length && lth.ta && pf_d!=='passive')?
              (th.posts[0].type_data!==lth.ta.posts[0].type_data? site2[th.domain].wrap_to_parse.posts(th) : null, 
                 site2[th.domain].update_posts_replace_prep(th.posts, lth.ta.posts, typeof(pf_store)==='number'? pf_store : -1)) :
              (th.parse_funcs.has_posts)? th.posts : lth.ta && lth.ta.posts || th.posts;
          if (pref.features.IDB && (lth.archived && pref.archive.live.post_idb || pf_d==='full_IDB')) {
            if (!lth.ta && pf_d==='full_IDB') IDB.req(th.domain, th.board, th.no, 'posts', this.restore_posts_from_IDB.bind(this, value, {posts:th.posts, __proto__:th}), 'get_all');
            var time_checked;
            if (lth.ta) time_checked = lth.ta.posts[lth.ta.posts.length-1].time;
            else {
              time_checked = list_all_obj[th.domain] && list_all_obj[th.domain][th.board] && list_all_obj[th.domain][th.board][th.no];
              if (time_checked<0) time_checked = - time_checked;
            }
            var posts_saved;
            if (time_checked && (!lth.ta || lth.ta.posts.length===lth.ta.nof_posts || th.posts.length < th.nof_posts)) {
              var i=th.posts.length;
              while (i>0 && th.posts[i-1].time>time_checked) i--;
              posts_saved = th.posts.slice(i);
            } else posts_saved = th.posts;
            if (editing_finished) posts_saved = editing_finished.concat(posts_saved);
            if (posts_saved.length>0) {
              if (th.type_data==='html') cataLog.format_html.prepare_html_prep_posts({posts:posts_saved, __proto__:th}, true); // prep_mimic requires getters
//              if (th.type_data==='html') cataLog.format_html.prepare_html_extract_params({posts:posts_saved, __proto__:th});
              IDB.req(th.domain, th.board, th.no, 'posts', posts_saved, 'put');
              lth.posts_saved = 1;
            }
            if (pref.debug_mode['28'] && posts_saved.length>0) console.log('posts_saved: '+th.key+', '+time_checked+', '+posts_saved[posts_saved.length-1].time+' '+new Date(posts_saved[posts_saved.length-1].time*th.parse_funcs.time_unit).toLocaleString()+', LS:'+(list_all_obj[th.domain] && list_all_obj[th.domain][th.board] && list_all_obj[th.domain][th.board][th.no]));
          }
//          if (pref.features.IDB && pref.archive.oneshot.post_idb && (lth.archived&0x01)) IDB.req(th.domain, th.board, th.no, 'posts', posts, 'put');
          lth.ta = {
            posts: posts,
            nof_posts:th.nof_posts,
            time_loaded:value.date, // th.time_loaded,
//            __proto__:th  // for debug.
          };
        } else if (lth.ta) delete lth.ta;
      },
      check_deleted_posts: function(value, th, lth, th_old, fill_IDB){
        if (pref[cataLog.embed_mode].deleted_posts.detect!=='no' && th_old && th_old.posts.length>1) {
          var thp = th.posts;
          var othp = th_old.posts;
          var flag = th.nof_posts === th.posts.length || th.type_source==='thread'; // 'thread' is for safe if inconsistent data is returned.
          var i=1;
          var j=1;
//          if (flag) {i=1;j=1;} // patch for 4chan's inconsistency between catalog_json and thread_json, thread_json returns old data and th_old.posts contains blank in it..
          if (flag) lth.rescan_dp = 0;
          else {
            i = th_old.nof_posts - othp.length +1 -(th.nof_posts - thp.length); // for short posts in 4chan catalog.
            j=1;
            if (i<1) {j+=1-i;i=1;}
          }
          var k=0;
          var m=0;
          var othpd = lth.pd || [];
          var post_deleted;
          var posts_offline = (fill_IDB)? [] : null;
          while (i<thp.length && (j<othp.length || k<othpd.length)) {
//          while (j<othp.length) {
//            if (i<thp.length) {
              while (k<othpd.length && thp[i].no>othpd[k].no) k++;
              if (k<othpd.length && thp[i].no==othpd[k].no) { // for inconsistent data between thread_json and catalog_json in 4chan.
                post_deleted = othpd.splice(k,1)[0];
                if (post_deleted.deleted_after) { // These 5 lines was the function recover(post)
                  delete post_deleted.deleted_after;
                  delete post_deleted.deleted_before;
                  if (post_deleted.pn) post_deleted.pn.classList.remove('CatChan_deleted');
                }
//              /*if (!th.native_prep)*/ post_deleted = recover(othpd.splice(k,1)[0]);/* else k++;*/
                continue;
              }
            if (j>=othp.length) if (!pref.test_mode['156']) continue; else break; // accurate synchronization is required for test_mode['136'] between lth.nof_posts and lth.pd.length
//              if (j+1<othp.length && othp[j].no === othp[j+1].no) {j++;continue;} // BUG PATCH.
              if (thp[i].no === othp[j].no) {i++;j++;flag=true;continue;}
              if (thp[i].no <   othp[j].no) {if (flag && fill_IDB) posts_offline[posts_offline.length] = thp[i]; // console.log('fill: '+th.no+'#'+thp[i].no); 
                                             i++;              continue;}
//            }
            while (m<othpd.length && othp[j].no>othpd[m].no) m++;
            if (m<othpd.length && othp[j].no === othpd[m].no) {j++;continue;}
            if (!flag) { // passive detection, deletion of some posts was detected, but I don't know which.
              if (pref[cataLog.embed_mode].deleted_posts.detect.indexOf('full')===0) {lth.rescan_dp=1; scan.list_nup.add_scan(lth);} // can't fild all of them.
              j++;
              continue;
            }

            post_deleted = othp[j++];
//            while (k<othpd.length && post_deleted.no>=othpd[k].no) k++; // BUG at serial posts are removed.
            if (!post_deleted.deleted_after) {
              if (pref.debug_mode['33'] && (post_deleted.no===undefined || post_deleted.no===null)) alert('Stopped by illegal deleted posts,(BUG)'); // can stop without developer tool
              post_deleted.deleted_after  = th_old.time_loaded;
              post_deleted.deleted_before = value && value.date || null;
//              post_deleted.deleted_debug  = th.type_parse + ', '+ th_old.type_parse + ', last' + (othp.length-j);
              cataLog.format_html.prepare_html_prep_posts({posts:[post_deleted], __proto__:th}, true); // extraction for being updated by thread_reader which uses html(test_mode['136'])
//              site2[th.domain].wrap_to_parse.posts({posts:[post_deleted], __proto__:th}); // for merge
//              var posts_wrapped = {posts:[th_old.posts[0]].concat(post_deleted), __proto__:th}; // BUG, post may NOT be derived from th.posts[0], html or json. time_unit may differ.
//              site2[th.domain].wrap_to_parse.posts(posts_wrapped); // for merge
            }
            othpd.splice(m++,0,post_deleted);
          }
//          if (!pref.test_mode['156']) while (k<othpd.length && i<thp.length) { // accurate synchronization is required for test_mode['136'] between lth.nof_posts and lth.pd.length
//            while (k<othpd.length && thp[i].no>othpd[k].no) k++;
//            if (k<othpd.length && thp[i].no==othpd[k].no) {/*if (!th.native_prep)*/ post_deleted = recover(othpd.splice(k,1)[0]);/* else k++;*/ continue;} // for inconsistent data between thread_json and catalog_json in 4chan.
//            i++;
//          }
          lth.pd = (othpd.length>0)? othpd : null;
          if (post_deleted) {
            if (pref[cataLog.embed_mode].deleted_posts.store!=='no' && th.domain===site.nickname) {
              var storage = (pref[cataLog.embed_mode].deleted_posts.store==='LS')? localStorage : sessionStorage;
              if (storage) {
                var ls_key = site2[th.domain].ls_key_deletedPosts + th.board + th.no;
                if (othpd.length>0) storage[ls_key] = JSON.stringify(othpd, store_json_func);
                else delete storage[ls_key];
              }
            }
            if (pref.features.IDB && pref[cataLog.embed_mode].deleted_posts.detect==='full_IDB') this.store_deleted_posts_to_IDB(th, othpd);
//            if (pref.features.IDB && (lth.archived && pref.archive.live.post_idb || pref[cataLog.embed_mode].deleted_posts.detect==='full_IDB'))
//              IDB.req(th.domain, th.board, th.no, 'posts_deleted', JSON.stringify(othpd, store_json_func), (othpd.length>0)? 'put' : 'delete');
          }
          if (posts_offline && posts_offline.length>0) {
            if (th.domain!==site.nickname) posts_offline = JSON.parse(JSON.stringify(posts_offline, store_json_func)); // for structual clone, remove pn.
            IDB.req(th.domain, th.board, th.no, 'posts', posts_offline, 'put');
          }
          if (pref.debug_mode['19'] && post_deleted && lth.pd)
            for (var i=0;i<lth.pd.length-1;i++)
              if (lth.pd[i].no>=lth.pd[i+1].no)
                console.log('BUG: inorder of posts_deleted: '+th.key+', '+i+', '+lth.pd[i].no+', '+lth.pd[i+1].no);
        }
        return (post_deleted)? othpd : null; // return only if it's changed.
      },
      store_deleted_posts_to_IDB: function(th, othpd){
        IDB.req(th.domain, th.board, th.no, 'posts_deleted', JSON.stringify(othpd, store_json_func), (othpd.length>0)? 'put' : 'delete');
      },
      store_json_func: store_json_func,
      store: function(value, th, lth, posts_new, time_check_old, pf_store, editing_finished, no_archive){
        var deletedPosts = this.check_deleted_posts(value, th, lth, lth.ta);
        this.store_th_to_mem(value, th,lth, pf_store, editing_finished); // MUST BE AFTER 'check_deleted_posts', because it uses lth.ta for old version of posts and this revises them.
        if (lth.archived && !no_archive) {
          var time_unit = th.parse_funcs.time_unit;
          var time_checked = this.check_archived_time(th, time_unit, lth.archived&0x04) || 0; // time_check_old is undefined at rescan_dp, inherited value must be returned.
          time_check_old /= time_unit;
          if (time_checked<time_check_old) time_checked = time_check_old;
//          var time_checked = time_check_old/time_unit || this.check_archived_time(th.key, th.domain, th.board, th.no, time_unit) || 0; // BUG, because time_check_old brings pref.filter.time_str
          if (!(lth.archived&0x01) && (lth.archived&0x04)) if (th.posts[th.posts.length-1].time<=time_checked || th.time_posted<=time_checked*time_unit) if (!editing_finished) {
            lth.archived &= 0x02; // Should I remove this safety???
            return;
          }
//          if (!(lth.archived&0x01) && (lth.archived&0x04)) if (th.posts[th.posts.length-1].time*th.parse_funcs.time_unit<=time_checked || th.time_posted<=time_checked) if (!editing_finished) return;
          if (th.parse_funcs.posts_full) th.parse_funcs.posts_full(th);
          var live_active    = lth.archived&0x02;
          var oneshot_active = lth.archived&0x01;
          var post = live_active && pref.archive.live.post || oneshot_active && pref.archive.oneshot.post;
          var post_idb = live_active && pref.archive.live.post_idb || oneshot_active && pref.archive.oneshot.post_idb;
          var suffix_dbt = th.key.replace(/\//g,'-');
          if (deletedPosts) {
            if (post) store_posts(JSON.stringify(site2[th.domain].parse_funcs.thread_json.prep_to_archive(deletedPosts)), 'text/plain', lth, suffix_dbt+'_'+'deleted.json');
            if (post_idb && pref[cataLog.embed_mode].deleted_posts.detect!=='full_IDB') this.store_deleted_posts_to_IDB(th, deletedPosts);
          }
//          if (deletedPosts && post) store_posts(JSON.stringify(site2[th.domain].parse_funcs.thread_json.prep_to_archive(deletedPosts)), 'text/plain', lth, suffix_dbt+'_'+'deleted.json');
          var type = (th.type_data==='html')? 'text/html' : 'text/plain';
          var posts_all = (th.type_source==='thread' || th.posts.length===th.nof_posts)? th.posts : (lth.ta && lth.ta.posts.length===th.nof_posts)? lth.ta.posts : null;
          if (post) {
            var sub = (th.posts[0].sub || '').replace(/\..*/,'');
            var suffix = suffix_dbt + ((pref.archive.sub_in_filename && sub)? '_'+sub : '') + ((th.type_data==='html')? '.html' : '.json');
            if (posts_all) store_posts((th.type_source==='thread')? value.responseText :
                                                                    JSON.stringify(site2[th.domain].parse_funcs.thread_json.prep_to_archive(posts_all), store_json_func),
                                       type, lth, suffix);
            else {this.store_rescan(lth); return;}
          }
          if (posts_new || (lth.archived&0x05) || editing_finished) {
            var tn       = live_active && pref.archive.live.tn       || oneshot_active && pref.archive.oneshot.tn;
            var tn_idb   = live_active && pref.archive.live.tn_idb   || oneshot_active && pref.archive.oneshot.tn_idb;
            var img      = live_active && pref.archive.live.img      || oneshot_active && pref.archive.oneshot.img;
            var img_idb  = live_active && pref.archive.live.img_idb  || oneshot_active && pref.archive.oneshot.img_idb;
            var webm     = live_active && pref.archive.live.webm     || oneshot_active && pref.archive.oneshot.webm;
            var webm_idb = live_active && pref.archive.live.webm_idb || oneshot_active && pref.archive.oneshot.webm_idb;
            if (tn || tn_idb || img || img_idb || webm || webm_idb || post_idb) {
              if (lth.archived&0x05) {
                if (!posts_all && (oneshot_active || (!th.posts[1] || th.posts[1].time>time_checked) && (!lth.ta.posts[1] || lth.ta.posts[1].time>time_checked))) {this.store_rescan(lth); return;}
                if (oneshot_active) posts_new = posts_all;
                else {
                  var posts_src = (th.posts[1] && th.posts[1].time<=time_checked)? th.posts : lth.ta.posts;
                  var i = posts_src.length;
                  while (i>0 && posts_src[i-1].time>time_checked) i--;
                  posts_new = posts_src.slice(i); // posts_src contains posts_new always.
                }
              }
              if (editing_finished) posts_new = editing_finished.concat(posts_new || []);
              if (posts_new.length!=0) {
                if (pref.debug_mode['28']) console.log('archive: '+th.key+', '+posts_new.length+'/'+th.nof_posts);
                var reqs = [];
                if (tn || tn_idb)                       img_dl('tn',  th, lth, posts_new, tn,  tn_idb,  webm, webm_idb, reqs);
                if (img || img_idb || webm || webm_idb) img_dl('img', th, lth, posts_new, img, img_idb, webm, webm_idb, reqs);
                if (reqs.length!=0) {
                  reqs[reqs.length-1].from = th.key;
                  list_all_obj_downloading[th.key] = time_checked;
                  download_url2(reqs);
                }
                if (pref.features.IDB && post_idb && lth.archived&0x05) IDB.req(th.domain, th.board, th.no, 'posts', posts_new, 'put');
              }
            }
          }
          if (post && (deletedPosts || posts_all) && (!reqs || reqs.length===0 || th.domain!==site.nickname) && pref.archive.tar) flush_req();
          lth.archived &= 0x02;
          this.update_archived_time(th.key);
        }
      },
      store_rescan: function(lth){
        scan.list_nup.add_scan(lth);
        lth.archived |= 0x04;
      },
//      test_dl: function(url){
//        var xhr = download_url2(url, '4chan_test_xxxx_image_'+ url.replace(/.*\//g,''));
//        xhr.onprogress = function(e){console.log(e.target.responseURL+': '+e.loaded+'/'+e.total);};
//      },
      restore_entry: function(files, imgs, clear_func, posts_deleted){
        var archive = {};
        for (var i=0;i<imgs.length;i++) {
          if (archive[imgs[i].name]===undefined) archive[imgs[i].name] = {file:imgs[i]}; // force to remake url because of having different lifetimes. Old version was '= imgs[i];'
          else if (Array.isArray(archive[imgs[i].name])) archive[imgs[i].name].push({file:imgs[i]});
          else archive[imgs[i].name] = [archive[imgs[i].name], {file:imgs[i]}];
        }
//        for (var i=0;i<imgs.length;i++) archive[imgs[i].name] = {file:imgs[i]}; // force to remake url because of having different lifetimes. Old version was '= imgs[i];'
        var deleted = {};
        if (!pref.test_mode['64'] && pref[cataLog.embed_mode].deleted_posts.merge) for (var i=0;i<posts_deleted.length;i++) deleted[posts_deleted[i].name] = posts_deleted[i];
        var count = 0;
        if (pref.archive.clear_threads && cataLog.threads) pClg.clear_threads(0);
        var fileReader = new FileReader();
        var name_deleted;
        fileReader.onerror = function (e){console.log('ERROR at loading json file, '+e);};
        fileReader.onload = function(e){
          if (!name_deleted) {
            restore(files[count-1], fileReader.result, archive);
            if (!pref.test_mode['64'] && pref[cataLog.embed_mode].deleted_posts.merge) {
              name_deleted = files[count-1].name.replace(/(_|\.).*/,'')+'_deleted.json';
              if (deleted[name_deleted]) {fileReader.readAsText(deleted[name_deleted]); return;}
            }
          } else {
            var name = name_deleted.slice(0,-13).replace(/\-/g,'/');
            if (pref.test_mode['80']) name = name.replace(/\/\d+$/,'_File$&');
            var lth = liveTag.mems.getFromName(name);
            archiver.prep_deleted_posts(lth.th, lth, fileReader.result);
          }
          name_deleted = null;
          if (files.length>count) fileReader.readAsText(files[count++]);
          else if (pref.archive.clear_files) clear_func();
        }
        fileReader.readAsText(files[count++]);
      },
      url2file: function(archive, url){
        var file = archive[url.replace(/[^\/]*\//g,'')];
        if (file) {
          if (Array.isArray(file)) file = (url.search(/thumb/)!=-1)? file[0] : file[file.length-1];
          if (!file.url) file.url = window.URL.createObjectURL(file.file);
          return file.url + ((file.file.type==='video/webm')? '" data-ext=".webm' : '');
        } else return url;
      },
//      url2file: function(archive, th){ // cause network access because post_json2html makes url begore this.
//        if (!window.URL) return;
//        var tgts = ['src','href'];
//        var all = th.pn.getElementsByTagName('*'); //'*[src],*[href]'
//        for (var i=0;i<all.length;i++)
//          for (var j=0;j<tgts.length;j++) {
//            var url = all[i].getAttribute(tgts[j]);
//            if (url) {
//              var file = archive[url.replace(/[^\/]*\//g,'')];
//              if (file) {
//                if (!file.url) file.url = window.URL.createObjectURL(file);
//                all[i].setAttribute(tgts[j], file.url);
//                if (file.type==='video/webm') all[i].setAttribute('data-ext','.webm');
//              }
//            }
//          }
//      },
      prep_deleted_posts: function(th,lth,src, src_obj, refresh_tgt){
        var posts_deleted = (src)? src_obj || JSON.parse(src) :
          (pref[cataLog.embed_mode].deleted_posts.store==='LS' && localStorage)? sanitize(JSON.parse(localStorage[site2[th.domain].ls_key_deletedPosts + th.board + th.no] || null)) :
          (pref[cataLog.embed_mode].deleted_posts.store==='SS' && sessionStorage)?      JSON.parse(sessionStorage[site2[th.domain].ls_key_deletedPosts + th.board + th.no] || null) : null;
        if (!pref.test_mode['123'] && posts_deleted) { // BUG PATCH
          var nos = {};
          for (var i=0;i<th.posts.length;i++) nos[th.posts[i].no] = null;
          for (var i=posts_deleted.length-1;i>=0;i--) if (!posts_deleted[i].no || nos[posts_deleted[i].no]===null) posts_deleted.splice(i,1);
          if (posts_deleted.length===0) posts_deleted = null;
        }
        if (posts_deleted) {
//          for (var i=0;i<posts_deleted.length;i++) delete posts_deleted[i].pn;
          posts_deleted = site2[th.domain].parse_funcs.thread_json.rip_from_archive(posts_deleted);
          var th_deleted = {posts:[].concat(posts_deleted), // posts:[th.posts[0]].concat(posts_deleted),
                            parse_funcs: site2[th.domain].parse_funcs.thread_json,
////                            parse_funcs_html: site2[th.domain].parse_funcs.thread_html,
                            type_parse: 'thread_json',
                            type_source: 'thread',
                            type_data: 'json',
////                            type_html: 'thread_html',
////                            domain_html: th.domain,
//                            nof_posts:th.nof_posts - th.posts.length + posts_deleted.length+1,
                            __proto__:th};
          site2[th.domain].wrap_to_parse.posts(th_deleted);
          if (src && lth.th) {  // NOT DESCRIBED ALL, UNDER IMPLEMENTATION....
            lth.pd = (lth.pd)? site2[th.domain].update_posts_merge_prep(th_deleted.posts, lth.pd, -1, true).slice(1) : posts_deleted;
            var clg = refresh_tgt || cataLog;
            if ((cataLog.embed_mode==='page' || cataLog.embed_mode==='thread') && !pref.test_mode['64'] && pref[cataLog.embed_mode].deleted_posts.merge) {
              th.posts = site2[th.domain].update_posts_merge_prep(th.posts, lth.pd, -1, true);
//              cataLog.format_html.update_posts_in_page(th,th.key, null, true);
              clg.threads[th.key][16].th = th;
//              clg.show_catalog(th.key);
            }
            clg.footer.update_force(th.key);
            // CALL UPDATE_POSTS HERE.
          } else lth.pd = posts_deleted;
//          if (src) archiver.check_deleted_posts({date:date_load}, th, {archived:true}, th_deleted); // sourced from pipe.
//          else lth.pd = posts_deleted;
        }
      },
      refresh_start: function(){
        save_list(); // for browser's crash, but this make conflicts when multiple window is opened.
        timestamp=null;
      },
      list_all_obj_downloading: list_all_obj_downloading,
      download_url3: download_url3,
      download_url4: download_url4,
      tar: tar,
      sub_funcs: function(args, callback){
        if (args[0]==='SUB_INIT') {
          pClg.clear_threads(0);
          cataLog.auto_update.stop_if_running();
          pref.stats.auto_acquisition = false;
          pref[cataLog.embed_mode].t2h_sel='All';
          pref.common.blur_404 = false;
          if (args[1].IDB) IDB.req(args[1].domain, args[1].board, args[1].no, null,
            (callback)? function(d,b,n,r){archiver.event_funcs['restore3'](d,b,n,r);callback();} : archiver.event_funcs['restore3'].bind(archiver.event_funcs), 'get_all');
          else {
            var tgt = args[1];
            var sel = parseInt(tgt.files_sel,10);
            var files = window.opener.document.getElementsByClassName(pref.script_prefix)[0].querySelectorAll('span[name="FILES_ARCHIVE'+sel+'"]')[0].querySelectorAll('input[type="file"]');
            for (var i=0;i<files.length;i++)
              for (var j=0;j<files[i].files.length;j++) {
                var src = files[i].files[j];
                if (src.name===tgt.name && src.size===tgt.size && src.lastModified===tgt.lastModified) {
                  archiver.event_funcs['restore2'](null, Array.prototype.slice.call(files[i+sel].files), [src]);
                  return;
                }
              }
          }
        } else if (args[0]==='DOWNLOAD') {
          download_url4(args[1][0], args[1][1]);
//          download_url(args[1][0], args[1][1]); // doesn't work
//          send_message(args[1][2],['ARCHIVER', ['REVOKE_URL', args[1][0]]]);
//        } else if (args[0]==='REVOKE_URL') window.URL.revokeObjectURL(args[1]);
        }
      },
      restore: restore,
      event_funcs:pref_func.settings.onchange_funcs.archive,
    };
  })();
 
  var recovery = (pref.features.recovery && localStorage)? (function(){
    var key;
    function init(){
      key = site2[site.nickname].ls_key_comment +site.board+site.no;
//      var key_old = pref.script_prefix+'.comment.'+site.nickname+site.board+site.no; // patch for half year.
      if (pref.recovery.comment) {
        var comment = localStorage[key] || ''; // localStorage[key_old] || '';
        if (comment) {
          if (site.components.postform_comment ) site.components.postform_comment.value  = comment;
          if (site.components.postform_comment2) site.components.postform_comment2.value = comment;
        }
      }
    }
    init();
//    function clear(){if (localStorage) delete localStorage[key];}
    var src = null;
    var com = null;
    function com_changed(){
      src = this;
      delayed_check();
    }
    var delayed_check = new DelayBuffer(check_com,1000).get_bound_func();
    function check_com(){
      var com_new = src.value;
      if (!com || com.length<=com_new.length) com = com_new;
      src = null;
      delayed_save();
    }
    var delayed_save = new DelayBuffer(save, pref.recovery.interval*1000).get_bound_func();
    function save(){
      if (com) localStorage[key] = com;
      else delete localStorage[key];
      com = null;
    }
    function save_at_exit(){
      if (src) com = src.value;
      if (com!==null) save();
    }
    function setup(rec_inst,postform_comment,postform_submit){
      if (!rec_inst && pref.recovery.comment && postform_comment) {
        var func = com_changed.bind(postform_comment);
        postform_comment.addEventListener('change',func,false);
        postform_comment.addEventListener('keyup',func,false);
        postform_submit.addEventListener('click',func,false);
        rec_inst = [postform_comment, postform_submit, func];
      } else if (rec_inst && (!pref.recovery.comment || !postform_comment)) {
        rec_inst[0].removeEventListener('change',rec_inst[2],false);
        rec_inst[0].removeEventListener('keyup',rec_inst[2],false);
        rec_inst[1].removeEventListener('click',rec_inst[2],false);
        rec_inst = null;
      }
      return rec_inst;
    }
    var rec_inst = setup(null, site.components.postform_comment, site.components.postform_submit);
    var rec_inst2 = null;
    window.addEventListener('beforeunload', save_at_exit, false);
    return {
      setup: function(){
        rec_inst  = setup(rec_inst, site.components.postform_comment, site.components.postform_submit);
        rec_inst2 = setup(rec_inst2,site.components.postform_comment2,site.components.postform_submit2);
      },
      setup2: function(){rec_inst2 = setup(rec_inst2,site.components.postform_comment2, site.components.postform_submit2);},
//      clear: clear,
      reentry: function(){
        save_at_exit();
        init();
      },
    }
  })() : null;

  var scan = (function(){
////    list_nup_boards : Object.create(null), // list of next updates of boards.
////    list_nup : Object.create(null), // list of next updates.
    var list_nup = {
      // th.u: 0:ready, >0:requested, <0:blacklisted
      add: function(th, priority, force_thread){ // -1:locked, [1:0]:req, [2]:req_thread, [3]:force_json, [7:4]:priority,
        if (typeof(th)==='string') th = this.get_th(th);
        priority = (priority || 0) <<4;
        if (th.u===0) th.u = priority | ((force_thread)? 7 : 3);
        else if (th.u>0 && th.u<priority) th.u = priority | (th.u&0x0f);
      },
      add_scan: function(th){
        this.add(th);
        scan.scan_threads_delayed_do();
      },
      issued: function(th){
        if (typeof(th)==='string') th = this.get_th(th);
        if (th) th.u = (((th.u&0x03)===1)? 0xffffff00 : 0) | ((th.u===0)? 2 : th.u-1); // 'if (th)' for page(p0, p1...) // keep upper bits.
        return th && th.no && th.archived;
      },
      got_200: function(th, lth){
//        var lth = this.get_th(th.key);
        if (lth.u!==0 && (!(lth.u&0x04) || th.type_source==='thread')) lth.u = 0; // not create instances for future implementation.
      },
      got_404: function(th){
        if (typeof(th)==='string') th = this.get_th(th);
        if (th) th.u = -1;
      },
      get_th: function(key){
        var dbt = comf.fullname2dbt(key);
        if (dbt[2].search(/(p|q)[0-9]+$/)!=-1) return null; // prevent board to be blacklisted when over page loading.
        if (dbt[2][0]==='t') dbt[2] = dbt[2].substr(1);
        else if (dbt[2].search(/^[0-9]/)==-1) dbt[2] = '';
        return liveTag.mems.init({domain:dbt[0], board:dbt[1], no:dbt[2]});
      },
      priority_th: [0, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0x100],
      get_list_thread: function(d, priority){
        if (site2[d].prohibitThreadScan) return null;
        var req_th = this.priority_th[priority || 0];
//        var req_th_u = this.priority_th[(priority || 0)+1];
        var count = 0;
        for (var b in liveTag.mems[d])
          for (var t in liveTag.mems[d][b])
           if (liveTag.mems[d][b][t].u>req_th) count++;            
//           if (liveTag.mems[d][b][t].u>req_th && liveTag.mems[d][b][t].u<req_th_u) count++;
        return (count===0)? null : [count, (function*(req_th){
          for (var b in liveTag.mems[d])
            for (var t in liveTag.mems[d][b])
              if (liveTag.mems[d][b][t].u>req_th) yield liveTag.mems[d][b][t]; // for getting robustness
//              if (liveTag.mems[d][b][t].u>req_th && liveTag.mems[d][b][t].u<req_th_u) yield liveTag.mems[d][b][t];
        })(req_th)];
      },
      get_list_thread2: function(d, priority){ // working code.
        if (site2[d].prohibitThreadScan) return [];
        var req_th = this.priority_th[priority || 0];
        var list = [];
//        for (var d in liveTag.mems)
        for (var b in liveTag.mems[d])
          for (var t in liveTag.mems[d][b])
            if (liveTag.mems[d][b][t].u>req_th) list[list.length] = liveTag.mems[d][b][t];
        return list;
      },
      get_list_board: function(d, priority){
        var req_th = this.priority_th[priority || 0];
//        var req_th_u = this.priority_th[(priority || 0)+1];
        var count = 0;
        for (var b in liveTag.mems[d])
          if (liveTag.mems[d][b].u>req_th) count++;
//          if (liveTag.mems[d][b].u>req_th && liveTag.mems[d][b].u<req_th_u) count++;
        return (count===0)? null : [count, (function*(req_th){
          for (var b in liveTag.mems[d])
            if (liveTag.mems[d][b].u>req_th) yield liveTag.mems[d][b];
//            if (liveTag.mems[d][b].u>req_th && liveTag.mems[d][b].u<req_th_u) yield liveTag.mems[d][b];
        })(req_th)];
      },
      get_list_board2: function(d, priority){
        var req_th = this.priority_th[priority || 0];
        var list = [];
        for (var b in liveTag.mems[d])
          if (liveTag.mems[d][b].u>req_th) list[list.length] = liveTag.mems[d][b];
        return list;
      },
//      add_domain: function(domain){liveTag.mems[domain].u = 1;},
      add_domain: function(domain, force_thread, boards){
        var max_boards = pref4.scan.max; // invoke getter
        if (!liveTag.active.pk){
          if (!boards) {
            for (var b in liveTag.mems[domain])
              if (liveTag.mems[domain][b].o!==null && liveTag.mems[domain][b].o < max_boards) this.add_board(domain+b, null, force_thread);
          } else for (var i=0;i<boards.length;i++) this.add_board(domain+boards[i], null, force_thread);
        } else {
          if (boards) {
            for (var b in liveTag.mems[domain]) { // working code for '>'
              if (liveTag.tags[liveTag.mems[domain][b].btag].pk) this.add_board(domain+b, null, force_thread);
              var btag2 = liveTag.mems[domain][b].btag2;
              if (btag2) {
                for (var i=0;i<btag2.length;i++)
                  if (liveTag.tags[btag2[i]].pk) this.add_board(domain+b, null, force_thread);
              }
            }
          } else {
            for (var i=0;i<max_boards;i++) if (liveTag.tags_array_old[i]) {
              for (var bt of liveTag.tags[liveTag.tags_array_old[i].key].mems.keys())
                if (!bt.no) this.add_board(bt, null, force_thread);
                else this.add(bt, null, force_thread);
            }
          }
        }
      },
      add_board: function(bd, priority, force_thread, use_boards_json, force_json){
        if (typeof(bd)==='string') bd = this.get_th(bd);
        if (force_thread===2) { // preparation read for search posts
          for (var t in bd) if (!bd[t].ta || bd[t].nof_posts!==bd[t].ta.posts.length) this.add(bd[t], priority, true);
          return;
        }
        if (!use_boards_json || bd.read_max<bd.max) {
          var upper_bits = ((priority || 0) <<4) | ((force_json)? 8 : 0) | ((force_thread)? 4 : 0);
          if (bd.u===0) bd.u = upper_bits | 3;
          else if (bd.u>0 && bd.u<priority) bd.u = upper_bits | (bd.u&0x0f);
        }
//        if (force_thread) for (var t in bd) this.add(bd[t], priority, force_thread); // BUG, bd had no members at initial, threads scan doesn't scheduled.
      },
////      add_board: function(bd, use_boards_json, force_thread, priority){ // working code.
////        if (typeof(bd)==='string') bd = this.get_th(bd);
//////        if (bd.u===0) bd.u = 3;
////        if (bd.u===0 && (!use_boards_json || bd.read_max<bd.max)) bd.u = 3;
//////        bd.u = 3;
//////        if (time) {
//////          if (bd.max) bd.read_max = bd.max;
//////          bd.read_time = time;
//////        }
////        if (force_thread) for (var t in bd) this.add(bd[t], force_thread);
////      },
      got_200_board: function(d, b){
        var bd = liveTag.mems[d][b];
//        if (typeof(bd)==='string') bd = this.get_th(bd);
        if (bd.u&0x04) for (var t in bd) this.add(bd[t], bd.u&0x00f0, true);
        if (bd.max) bd.read_max = bd.max;
        bd.u = 0;
      },
//      add_board: function(bd, time){ // working code.
//        if (typeof(bd)==='string') bd = this.get_th(bd);
////        if (bd.u===0) bd.u = 3;
//        bd.u = 3;
//        if (bd.max) bd.read_max = bd.max;
//        bd.read_time = time;
//      },
////      get_list_board: function(d){ // working code.
////        var list = [];
//////        var time_th = Date.now()-pref.liveTag.pickup_interval*1000;
//////        for (var d in liveTag.mems) {
////////          if (liveTag.mems[d].u) { // for 'add_domain' // working code.
////////            for (var b in liveTag.mems[d]) if (liveTag.mems[d][b].o!==null && liveTag.mems[d][b].o < pref.scan.max) this.add_board(d+b);
////////            liveTag.mems[d].u = 0;
////////          }
//////          if (domain && d!==domain) continue;
////        for (var b in liveTag.mems[d]) {
////          var tgt = liveTag.mems[d][b];
//////          if (tgt.u>0) if (tgt.read_time<time_th && (tgt.max===undefined || !pref.liveTag.utilize_boards_json || tgt.read_max<tgt.max)) list[list.length] = tgt;
//////          if (tgt.u>0) if (tgt.max===undefined || !pref.liveTag.utilize_boards_json || tgt.read_max<tgt.max) list[list.length] = tgt;
////          if (tgt.u>0) list[list.length] = tgt;
////        }
////        return (list.length!==0)? list : null;
////      },
//      query_list_board: function(domain){ // working code.
//        var d = liveTag.mems[domain];
//        if (d)
//          for (var b in d)
//            if (d[b].u>0) return true;
//        return false;
//      },
      scan_boards_enumerate: function(domain, func, ex_list){
        var thread_or_domain = pref.virtualBoard.scan_domains[domain]==='thread' || ex_list.has(liveTag.mems[domain]);
        var max_boards = pref4.scan.max; // invoke getter
        for (var b in liveTag.mems[domain])
          if (thread_or_domain || Object.keys(liveTag.mems[domain][b]).length!=0)
//            if (ex_list[liveTag.mems[domain][b].key]===undefined) // prevent multiple scan.
            if (!ex_list.has(liveTag.mems[domain][b])) // prevent multiple scan.
              if (liveTag.mems[domain][b].o!==null && liveTag.mems[domain][b].o < max_boards)
                if (!func || func(liveTag.mems[domain][b])) return true;
      },
    };
    var Scanner = function(type, domain, priority, key, done_list){ // type: 'b':boards, 't':threads
      this.type = type;
      this.domain = domain;
      this.key = key;
      this.priority = priority || 0;
      this.scanners[key] = this; // and also this.wdg, this.req
      this.done_list = done_list || new DoneList(); // scan_refresh_ex_list
    };
    Scanner.prototype = {
      scanners: Object.create(null),
      func_entry: function(){
        var tgts;
if (!pref.test_mode['51']) { // 1-3 times faster than generator.
        tgts = (this.type==='b')? scan.list_nup.get_list_board2(this.domain, this.priority) : scan.list_nup.get_list_thread2(this.domain, this.priority);
        tgts = tgts.filter(function(v){return !this.has(v);}, this.done_list);
        if (tgts.length!=0) this.func_scan(tgts,null);
        else this.exit();
} else {
        tgts = (this.type==='b')? scan.list_nup.get_list_board(this.domain, this.priority) : scan.list_nup.get_list_thread(this.domain, this.priority);
        if (tgts) this.func_scan(tgts,{tgts_iterator:tgts[1], max:tgts[0]});
        else this.exit();
}
      },
      func_scan: function(tgts,options, multi_entry){
        if (tgts.length==0) return;
        for (var i=0;i<tgts.length;i++) this.done_list.add(tgts[i]);
        if (!this.wdg) this.wdg = new Watchdog(this.abort.bind(this),30000);
        cataLog.scan_init(this.key, tgts, {callback: this.func_cont.bind(this),
                                           watchdog: this.wdg.restart.bind(this.wdg),
                                           priority: this.priority,
                                           done_list: this.done_list,
                                           __proto__: options
                                          }, multi_entry);
      },
      func_cont: function() {
        if (this.req) {
          this.req = false;
          this.func_entry();
        } else this.exit();
      },
      exit: function(){
        if (this.wdg) this.wdg.stop();
        this.scanners[this.key] = null
        if (this.type==='b') {
          if (!this.aborted) scan.scan('t', this.domain, this.priority, this.done_list);
        } else if (this.type==='t' && cataLog.catalog_refresh_end) cataLog.catalog_refresh_end();
        if (this.callback) this.callback();
        var priority_next = get_highest_priority(this.type, this.domain);
        var key_next = get_key(this.type, this.domain, priority_next);
        if (Array.isArray(this.scanners[key_next])) new Scanner(this.type, this.domain, priority_next, key_next, this.scanners[key_next][0]).func_entry(); // do scheduled task, but this may not occur.
      },
      abort: function(){
        if (pref.debug_mode['7']) console.log('Scanner_abort: '+this.key);
        cataLog.scan_boards.scan_abort(this.key);
        if (this.aborted===undefined) {
          this.aborted = true;
          this.wdg.restart();
        } else this.exit();
      },
    };
    var ScannerUI = function(key, arg, done_list){ // arg is {tgts:, options:}
      this.key = key;
      this.arg = arg;
      this.scanners[key] = this;
      this.callback = this.arg.options && this.arg.options.callback;
      this.done_list = done_list || new DoneList(); // scan_refresh_ex_list
    };
    ScannerUI.prototype = {
      scanners: Object.create(null),
      priority: 8,
      func_entry: function(){
        this.func_scan(this.arg.tgts, this.arg.options, true);
      },
      func_cont: function() {
        if (this.queue && this.queue.length>0) {
          this.arg = this.queue.shift();
          this.func_entry();
        } else this.exit();
      },
      __proto__: Scanner.prototype
    };
    function get_key(type, domain, priority){
      return ((type==='b')? 'scan' : 'scan_threads')+ '_' + domain + ':' + (priority || 0);
    }
    function get_highest_priority(type, domain){
      var key_pre = get_key(type, domain, 0).slice(0,-1);
      for (var p=8;p>=0;p--) {
        var scanner = Scanner.prototype.scanners[key_pre+p];
        if (scanner || scanner===false) return p;
      }
      return -1;
    }
//    var scan_refresh_ex_list = new Set();
    var DoneList = function(tgt, tgt_domains){
      this.o = new Set();
      this.prep(tgt,tgt_domains);
    };
    DoneList.prototype = {
      has: function(i){return this.o.has(i);},
      add: function(i){return this.o.add(i);}, // for lth or ldb
      test_and_add: function(tgt){ // for rss, tgt must be string. DoneList.has() can be used directly when tgt is lth or ldb.
        if (this.o.has(tgt)) return true;
        var t = comf.fullname2dbt(tgt);
        if ((t[2]==='c0' || t[2]==='p0') && (liveTag.mems[t[0]] && liveTag.mems[t[0]][t[1]] && this.o.has(liveTag.mems[t[0]][t[1]]) || this.o.has(t[0]+t[1]))) return true;
        this.add_txt(tgt);
      },
      add_txt: function(tgt){
        var dbt = comf.name2domainboardthread(tgt,true);
        if (dbt[2].search(/^[cjpq]/)===0 || dbt[2]==='') this.o.add(liveTag.mems.init({domain:dbt[0], board:dbt[1]})); // for making prior to actual access.
        else if (dbt[2].search(/^t*[0-9]+/)===0) this.o.add(liveTag.mems.init({domain:dbt[0], board:dbt[1], no:(dbt[2][0]==='t')? dbt[2].substr(1) : dbt[2]})); // redundant.
//        if (dbt[2].search(/^[cj]/)===0 || dbt[2]==='') this.o.add(liveTag.mems[dbt[0]][dbt[1]]);
//        else if (dbt[2].search(/^t*[0-9]+/)===0) this.o.add(liveTag.mems[dbt[0]][dbt[1]][(dbt[2][0]==='t')? dbt[2].substr(1) : dbt[2]]); // redundant.
        else this.o.add(tgt); // for rss, ldb can't have its page info.
      },
      prep: function(tgts, tgt_domains){
        if (tgts) for (var i=0;i<tgts.length;i++) this.add_txt(tgts[i]);
        if (tgt_domains) for (var i in tgt_domains) this.o.add(liveTag.mems[i]);
      },
    };
    return {
      DoneList: DoneList,
      scan_refresh: function(health_indicator, done_list, priority){
//        var done_list = /*scan_refresh_ex_list =*/ this.done_list_add(new Set(), list, list_domains);
//        scan_refresh_ex_list.clear();
//        for (var i=0;i<list.length;i++) {
//          var dbt = comf.name2domainboardthread(list[i],true);
//          if (dbt[2].search(/^[cj]/)===0 || dbt[2]==='') scan_refresh_ex_list.add(liveTag.mems[dbt[0]][dbt[1]]);
//          else if (dbt[2].search(/^t*[0-9]+/)===0) scan_refresh_ex_list.add(liveTag.mems[dbt[0]][dbt[1]][(dbt[2][0]==='t')? dbt[2].substr(1) : dbt[2]]); // redundant.
//        }
//        for (var i in list_domains) scan_refresh_ex_list.add(liveTag.mems[i]);
        scan.scan('t', null, priority, done_list);
        if (!pref.liveTag.use)
          for (var tag in liveTag.tags) // activate selected tags for NOT liveTag mode. In this case, tags are not many.
            if (liveTag.tags[tag].pk)
              for (var bt of liveTag.tags[tag].mems.keys())
                if (!done_list.has(bt))
                  if (bt.no) scan.list_nup.add(bt,2);
                  else scan.list_nup.add_board(bt,2);
        for (var d in liveTag.mems) {
          if (pref.liveTag.use) {
            if (site2[d].utilize_boards_json && pref.pref2[d].utilize_boards_json) {
              if (list_nup.scan_boards_enumerate(d, null, done_list))
                site2[d].get_boards_json('boards_'+d,(function(domain){return function(){scan.scan_refresh_1(domain, false, done_list);}})(d),true,health_indicator);
              else scan.scan('t', d, 0, done_list);
            } else scan.scan_refresh_1(d, false, done_list);
          } else scan.scan('b', d, 0, done_list);
        }
      },
      scan_refresh_1: function(domain, use_boards_json, done_list){
        list_nup.scan_boards_enumerate(domain, function(bd){scan.list_nup.add_board(bd, null, null, use_boards_json);}, done_list);
        scan.scan('b', domain, 0, done_list);
      },
//      scan_refresh_move_req: function(list){ // working code.
//        for (var i=0;i<list.length;i++) {
//          var dbt = comf.name2domainboardthread(list[i],true);
//          if (dbt[2].search(/^[cj]/)===0) {
//            scan.list_nup.add_board(dbt[0]+dbt[1]);
//            list.splice(i--,1);
//          } else if (dbt[2].search(/^t*[0-9]+/)===0) {
//            scan.list_nup.add(dbt[0]+dbt[1]+((dbt[2][0]==='t')? dbt[2].substr(1) : dbt[2]));
//            list.splice(i--,1);
//          }
//        }
//      },
      scan: function(type, domain, priority, done_list){
        if (!domain) for (var d in liveTag.mems) this.scan(type, d, priority);
        else {
          var key = get_key(type, domain, priority);
          var scanner = Scanner.prototype.scanners[key];
          if (scanner) scanner.req = true; // multi entry.
          else if (get_highest_priority(type, domain)>=priority) Scanner.prototype.scanners[key] = [done_list]; // schedule
          else if (type==='b' && (scanner = Scanner.prototype.scanners[get_key('t', domain, priority)]))
            scanner.callback = function(){scan.scan(type, domain, priority, done_list);}; // schedule
          else new Scanner(type, domain, priority, key, done_list).func_entry();
        }
      },
      abort: function(){
        for (var s in Scanner.prototype.scanners) {
          var scanner = Scanner.prototype.scanners[s];
          if (scanner) {
            if (scanner.wdg) scanner.wdg.stop();
            scanner.abort();
          }
        }
      },
      scan_ui: function(key, arg){
        new ScannerUI(key, arg).func_entry();
//        var scanner = ScannerUI.prototype.scanners[key];
//        if (scanner) {
//          if (!this.queue) this.queue = [];
//          this.queue[this.queue.length] = arg; // multi entry.
//        } else new ScannerUI(key, arg).func_entry();
      },
      scan_threads_delayed_do: DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(function(){scan.scan('t');}, 200)),
      keyword_load: function(force, health_indicator){
        var flag = true;
        for (var domain in pref.virtualBoard.scan_domains) {
          if (pref.virtualBoard.scan_domains[domain]!=='none') {
            this.keyword_load_1(domain, force, null, health_indicator);
            flag = false;
          }
        }
        if (force && flag) this.keyword_load_1(site.nickname, force, null, health_indicator);
      },
      keyword_load_1: function(domain, force, boards, health_indicator){
//          if (!site3[domain].boards && (domain===site.nickname || pref.virtualBoard.scan_domains[domain]!=='none')) {
        if ((!site3[domain].boards || site2[domain].nativeVirtualBoard) && (pref.virtualBoard.scan_domains[domain]!=='none' ||
                                      force && liveTag.active.pk)) {
          site2[domain].get_boards_json('kwd_load',function(){scan.keyword_load_2(domain, force, boards, health_indicator)},false,health_indicator);
          return;
        }
        this.keyword_load_2(domain, force, boards, health_indicator);
      },
      keyword_load_2: function(domain, force, boards, health_indicator){
//          if (domain===site.nickname || pref.virtualBoard.scan_domains[domain]==='thread') {
//          if (pref.virtualBoard.scan_domains[domain]!=='none' || force) {
        if (pref.virtualBoard.scan_domains[domain]==='thread' || force) {
          scan.list_nup.add_domain(domain, pref.filter.kwd.post && force, boards);
          scan.scan('b',domain);
        }
      },
      list_nup: list_nup,
    };
  })();

  function make_catalog_obj(){ // pn12_button){
    var catalog_func = null;
    function show_hide(e, mode){
      if (catalog_func===null) {
        if (mode) set_embed_xxxx(mode)
        catalog_func = make_catalog();
      } else if (!pref.test_mode['114']) catalog_func.make_watcher();
      else catalog_func = catalog_func.destroy();
    }
    var embed_catalog;
    var embed_page;
    var embed_frame;
    var embed_mode;
    var embed_embed;
    function set_embed_xxxx(mode){
      embed_catalog = pref.catalog.embed && site.whereami==='catalog' || pref.catalog.embed_archive && site.whereami==='archive';
      embed_page    = pref.catalog.embed_page && site.whereami==='page';
      embed_frame   = pref.catalog.embed_frame && site.whereami==='frame';
      embed_mode = (embed_catalog)? 'catalog' :
                   (embed_page)? 'page' :
                   ((pref.thread.embed || mode==='thread') && site.whereami==='thread')? 'thread' : 'float';
      embed_embed = embed_mode!=='float';
      cataLog.embed_mode = embed_mode; // this will be done in later, but do here also for meguca reentry.
    }
    cataLog.set_embed_xxxx = set_embed_xxxx;
    set_embed_xxxx();
//    if (pn12_button) pn12_button.addEventListener('click', show_hide, false); // show_hide
    styleSheet.init(embed_mode); // patch, but WHY???
//if (!(brwsr.ff && embed_mode==='catalog' && site.nickname==='4chan')) styleSheet.init(); // patch, but WHY???
////////if (!(brwsr.ff && embed_mode==='catalog' && site.nickname==='4chan')) { // patch, but WHY??? // working code.
//////////    var ss = document.styleSheets[document.styleSheets.length-1];
//////////    if (!ss) {
//////////      var ss = document.createElement('style');
//////////      document.head.appendChild(ss);
//////////      ss = ss.sheet;
//////////    }
////////    var ss = document.head.appendChild(document.createElement('style')).sheet;
////////    ss.insertRule('.catalog_triage_parent {pointer-events: none;}',0); 
////////    ss.insertRule('.catalog_triage_button {pointer-events: auto;}',1);
////////    pref_func.style_sheet = ss;
////////    pref_func.settings.onchange_funcs['catalog.click_area_add_rule'](); // rule 2.
////////    ss.insertRule('.'+pref.cpfx+'tag {cursor:pointer;}',3);
////////}

//    function scan_tags_common(ths,html_str,tags_obj){ // working code
//      var acc = true;
//      if (tags_obj===undefined) {
//        tags_obj = {cs:{}, ci:{}};
//        acc = false;
//      }
//      for (var i in ths) {
//        var tags_th = ths[i].tags;
//        if (tags_th) {
//          var dbt = cnst.name2domainboardthread(i,true);
//          var tags_th_uniq = {};
//          for (var j=tags_th.length-1;j>=0;j--)
//            if (tags_th_uniq[tags_th[j]]===undefined) tags_th_uniq[tags_th[j]] = null; // BUG. #aaa and #AAA are counted as 2 in case insensitive mode.
//            else tags_th.splice(j,1);
//          if (tags_th.length<=pref.catalog.tag.max) {
//            var end = (pref.catalog.tag.ignore<tags_th.length)? pref.catalog.tag.ignore : tags_th.length;
//            for (var j=0;j<end;j++) {
//              for (var k in tags_obj) {
//                var tag_test = (k==='cs')? tags_th[j] : tags_th[j].toLowerCase();
//                if (tags_obj[k][tag_test]===undefined) tags_obj[k][tag_test] = {num:1,mem:{}};
//                else tags_obj[k][tag_test].num++;
//                if (!tags_obj[k][tag_test].mem[dbt[0]+dbt[1]]) tags_obj[k][tag_test].mem[dbt[0]+dbt[1]] = 1;
//                else tags_obj[k][tag_test].mem[dbt[0]+dbt[1]]++;
//      }}}}}
//      if (!acc) return scan_tags_common_b(tags_obj,html_str,{cs:[],ci:[]})[0];
//    }
//    function scan_tags_common_b(tags_obj, html_str, tags){ // CAN'T FIND 1->0 AT A BOARD.
//      for (var i in tags_obj) {
//        for (var j in tags_obj[i]) {
//          var tag_ref = (i==='cs')? j : j.toLowerCase();
//          for (var n=0;n<tags[i].length;n++) {
//            var tag_test = (i==='cs')? tags[i][n].key : tags[i][n].key.toLowerCase();
//            if (tag_ref===tag_test) {
//              for (var k in tags_obj[i][j].mem) tags[i][n].mem[k] = tags_obj[i][j].mem[k]; // update.
////var old = tags[i][n].num;
//              tags[i][n].num = 0;
//              for (var k in tags[i][n].mem) tags[i][n].num += tags[i][n].mem[k];
////if (old!=tags[i][n].num) console.log(i+', '+j+', '+n+', '+tag_test+', '+old+', '+tags[i][n].num);
//              delete tags_obj[i][j]; // works in Chrome and FF, BUT DANGEROUS?
//              break;
//            }
//          }
//        }
//        for (var j in tags_obj[i]) tags[i].push({key:j, num:tags_obj[i][j].num, mem:tags_obj[i][j].mem});
////        tags[i].sort(function(a,b){return b.num - a.num;});
//        tags[i].sort(function(a,b){return (b.num!=a.num)? b.num - a.num : (b.key > a.key)? -1:1;});
//      }
//      for (var i=0;i<tags.ci.length;i++) {
//        var key = tags.ci[i].key.toLowerCase();
//        for (var j=0;j<tags.cs.length;j++)
//          if (key===tags.cs[j].key.toLowerCase()) {tags.ci[i].key=tags.cs[j].key; break;}
//      }
//      return scan_tags_common_c(tags, html_str);
//    }
//    function scan_tags_common_c(tags, html_str){
//      var tags_tgt = (pref.filter.tag_ci)? tags.ci : tags.cs;
//      var str2 = '';
//      for (var i=0;i<tags_tgt.length;i++) {
//        var item = tags_tgt[i].num + ': ' + tags_tgt[i].key;
//        str2 = str2 + '<input type="checkbox"' + html_str + '> '+item + '<br>';
//      }
//      return [str2, tags];
//    }
////    function scan_tags_common(ths,html_str,tags_obj){
////      var acc = true;
////      if (tags_obj===undefined) {
////        tags_obj = {cs:{}, ci:{}};
////        acc = false;
////      }
////      for (var i in ths) {
////        var tags_th = ths[i].tags;
////        if (tags_th) {
////          var dbt = cnst.name2domainboardthread(i,true);
////          var tags_th_uniq = {};
////          for (var j=tags_th.length-1;j>=0;j--)
////            if (tags_th_uniq[tags_th[j]]===undefined) tags_th_uniq[tags_th[j]] = null;
////            else tags_th.splice(j,1);
////          if (tags_th.length<=pref.catalog.tag.max) {
////            var end = (pref.catalog.tag.ignore<tags_th.length)? pref.catalog.tag.ignore : tags_th.length;
////            for (var j=0;j<end;j++) {
////              for (var k in tags_obj) {
////                var tag_test = (k==='cs')? tags_th[j] : tags_th[j].toLowerCase();
////                if (tags_obj[k][tag_test]===undefined) tags_obj[k][tag_test] = {num:0,mem:{}};
////                tags_obj[k][tag_test].num++;
////                tags_obj[k][tag_test].mem[dbt[0]+dbt[1]] = null;
////      }}}}}
////      if (!acc) return scan_tags_common_b(tags_obj,html_str)[0];
////    }
////    function scan_tags_common_b(tags_obj,html_str){
////      var tags = [];
////      for (var i in tags_obj.cs) tags.push({key:i, num:tags_obj.cs[i].num, mem:tags_obj.cs[i].mem});
////      tags.sort(function(a,b){return b.num - a.num;});
////      if (pref.filter.tag_ci) {
////        for (var i=0;i<tags.length-1;i++) {
////          var key = tags[i].key.toLowerCase();
////          tags[i].num = tags_obj.ci[key].num;
////          tags[i].mem = tags_obj.ci[key].mem;
////          for (var j=tags.length-1;j>i;j--) if (key===tags[j].key.toLowerCase()) tags.splice(j,1);
////        }
////        tags.sort(function(a,b){return b.num - a.num;});
////      }
////      var str2 = '';
////      for (var i=0;i<tags.length;i++) {
////        var item = tags[i].num + ': ' + tags[i].key;
////        str2 = str2 + '<input type="checkbox"' + html_str + '> '+item + '<br>';
////      }
////      return [str2, tags];
////    }
//////    function scan_tags_common(ths,html_str,tags_obj,acc){
//////      if (tags_obj===undefined) tags_obj = {};
////////      var tags_obj = {};
//////      for (var i in ths) {
//////        var tags_th = ths[i].tags;
//////        if (tags_th) {
//////          var tags_th_uniq = {};
//////          for (var j=tags_th.length-1;j>=0;j--)
//////            if (tags_th_uniq[tags_th[j]]===undefined) tags_th_uniq[tags_th[j]] = null;
//////            else tags_th.splice(j,1);
//////          if (tags_th.length<=pref.catalog.tag.max) {
//////            var end = (pref.catalog.tag.ignore<tags_th.length)? pref.catalog.tag.ignore : tags_th.length;
//////            for (var j=0;j<end;j++) {
//////              if (tags_obj[tags_th[j]]===undefined) tags_obj[tags_th[j]] = [0,{}];
//////              tags_obj[tags_th[j]][0]++;
//////              tags_obj[tags_th[j]][1][i] = 0;
//////            }
//////          } 
//////        }
//////      }
//////      if (!acc) return scan_tags_common_b(tags_obj,html_str);
//////    }
//////    function scan_tags_common_b(tags_obj,html_str){
//////      var tags = [];
//////      for (var i in tags_obj) tags.push({key:i, val:tags_obj[i][0], mem:tags_obj[i][1]});
//////      tags.sort(function(a,b){return b.val - a.val;});
//////      if (pref.filter.tag_ci) {
//////        for (var i=0;i<tags.length-1;i++) {
//////          var key = tags[i].key.toLowerCase();
//////          for (var j=tags.length-1;j>i;j--) {
//////            if (key===tags[j].key.toLowerCase()) {
////////console.log(tags[i].key+', '+tags[i].val +' + '+tags[j].key+', '+tags[j].val);
//////              tags[i].val += tags[j].val;
//////              for (var k in tags[j].mem) tags[i].mem[k] = 0;
//////              tags.splice(j,1);
//////            }
//////          }
//////        }
//////        for (var i in tags) tags[i].val = Object.keys(tags[i].mem).length;
//////        tags.sort(function(a,b){return b.val - a.val;});
//////      }
//////      var str2 = '';
//////      for (var i=0;i<tags.length;i++) {
//////        var item = tags[i].val + ': ' + tags[i].key;
//////        str2 = str2 + '<input type="checkbox"' + html_str + '> '+item + '<br>';
//////      }
//////      return str2;
//////    }
    function make_catalog(){
      var healthIndicator = (function(){
        var his = [];
        var count = 0;
        var status = {};
        var HealthIndicator = function(dual){
          this.pn = document.createElement('span');
          this.pn_countdown = this.pn.appendChild(cnst.dom('<span style="color:limegreen"></span>'));
          this.pn_hi = this.pn.appendChild(document.createElement('span'));
          if (dual) {
            this.pn2 = this.pn.cloneNode(true);
            this.pn2_countdown = this.pn2.childNodes[0];
            this.pn2_hi = this.pn2.childNodes[1];
          }
          if (!pref.healthIndicator.show) this.pn_hi.style.display = 'none';
          his.push(this);
          this.elems = [];
        }
        HealthIndicator.prototype = {
          shift: function(col,str,initiator,priority){
//            while (this.elems.length>=pref.healthIndicator.max) this.elems.shift().remove_from_parent();
            for (var i=this.elems.length-pref.healthIndicator.max;i>=0;i--) if (this.elems[i].remove_from_parent()) this.elems.splice(i,1);
            var elem = new HealthIndicatorElement(col,str,initiator,priority);
            this.pn_hi.insertBefore(elem.pn, this.pn_hi.firstChild);
            this.elems.push(elem);
            return elem;
          },
          destroy: function(){
            this.pn.parentNode.removeChild(this.pn);
            his.splice(his.indexOf(this),1);
          },
          countdown: function(str){
            this.pn_countdown.textContent = str;
            if (this.pn2) this.pn2_countdown.textContent = str;
          },
//          remove: function(tgt){this.elems.splice(this.elems.indexOf(tgt),1)[0].remove();},
//          set: function(col,str){this.elems[this.elems.length-1].set(col,str);}, // PATCH
        };
        var HealthIndicatorElement = function(col,str,initiator,priority){
          this.pn = document.createElement('span');
          this.name = 'healthIndicator.' + (count++);
          this.pn.setAttribute('name',this.name);
          this.set(col,str);
          this.pn.onmouseover = mouseover;
          this.pn.onmouseout  = mouseout;
          this.pn.onclick     = click;
          status[this.name] = {initiator:initiator, priority:priority};
          this.show_tooltip_delayed = new DelayBuffer(show_tooltip.bind(this), 100).get_bound_func();
//          this.status = status[this.name]; // patch for faster execution.
        }
        var src = null;
        HealthIndicatorElement.prototype = {
          set: function(col,str){
            if (str) this.pn.textContent = str;
            if (col) this.pn.setAttribute('style','color:'+col);
          },
          remove_from_parent: function(){ // must be called from parent to remove elems[this].
            if (pref.healthIndicator.dont_retire_running && !status[this.name].end) return false;
            delete status[this.name];
            this.pn.parentNode.removeChild(this.pn);
            if (this.pn===src) mouseout();
            return true;
          },
          remove: function(){
            if (this.remove_from_parent())
              for (var i=0;i<his.length;i++) {
                var idx = his[i].elems.indexOf(this);
                if (idx!=-1) {
                  his[i].elems.splice(idx,1);
                  return;
                }
              }
          },
          report: function(obj){
            var stat = status[this.name];
            if (!stat) return; // when indicator was pruned.
//            var prog = stat.prog; // prepare for being overwritten.
            if (obj) for (var i in obj) {
              if (i==='err') {
                if (!stat.errs) this.set('orange');
                stat.errs = (stat.errs||0) + 1;
                stat.err_str = (stat.err_str||'') + ', ' + obj[i];
              } else stat[i] = obj[i];
              var prog = stat.prog;
              if (i==='end') { // end must NOT come with prog.
                if (prog) {
                  stat.prog_str = make_prog_str(stat.prog);
//                  stat.prog_str = (stat.prog.IDX)? prog.IDX+'/'+prog.tgts.length :
//                                                   prog.found_threads+'/'+prog.scanned+', '+prog.found_boards+'/'+prog.max;
                  stat.prog = null; // remove reference loop.
                }
                var result = (!stat.err || stat.initiator==='IDB')? '\u25cf' :
                             (prog && prog.SUC)? '\u25b2' : 'X';
//                             (prog && Object.keys(prog.error_obj).length<prog.max)? '\u25b2' : 'X';
                this.set((result==='X')?'red':null, result);
              }
            }
            if (src || pref.healthIndicator.expand_running) this.show_tooltip_delayed();
          },
        };
        function make_prog_str(prog, running){
          return (prog.IDX)? prog.IDX+'/'+prog.tgts.length
                           : prog.found_threads+'/'+prog.scanned+', '+prog.found_boards+(running?'/'+prog.idx:'')+'/'+prog.max;
        }
        function mouseover(e){
          if (!pref.tooltips['info'].show) return;
          src = this;
          Tooltips.req_show('info', e, show_tooltip, true);
          e.stopPropagation();
//          show_tooltip.call(this,e);
        }
        function show_tooltip(e){
          var myself = e && e.target || this;
          var name = myself.name || myself.getAttribute('name');
          var stat = status[name];
          if (!stat) return; // retired already.
          if (stat.prog) stat.prog_str = make_prog_str(stat.prog, true) + ', ' + stat.tgt;
//          if (stat.prog) stat.prog_str = (stat.prog.IDX)? stat.prog.IDX+'/'+stat.prog.tgts.length+', ' + stat.tgt :
//                                                          stat.prog.found_threads+'/'+stat.prog.scanned+', '+stat.prog.found_boards+'/'+stat.prog.idx+'/'+stat.prog.max+', ' + stat.tgt;
          if (!e && pref.healthIndicator.expand_running && stat.start && !stat.end) myself.set(null,' :'+stat.prog_str);
          if (src && src.getAttribute('name')===name) {
            var cancel_button = !stat.end && !pref.healthIndicator.cancel;
            var html = 'Initiator: '+stat.initiator+'&emsp;'+
                       ((cancel_button)? '<button type="button" name="'+name+'">Cancel</button>' : '')+ '<br>' +
                       ((stat.start)? 'Start: '+ new Date(stat.start).toLocaleTimeString()+'<br>' : '')+
                       ((stat.prog_str)? 'Progress: '+ stat.prog_str+'<br>' : '')+
                       'Priority: '+ stat.priority+'<br>'+
                       ((stat.start && !stat.end && stat.prog)? 'Crawler: '+ stat.prog.crawler+'<br>' : '')+
                       ((stat.errs)? '<span style="color:red">Errors: '+ stat.errs +': '+ stat.err_str.slice(2) +'</span><br>' : '')+
                       ((stat.abort_str)? '<span style="color:red">Abort: '+ stat.abort_str+'</span><br>' : '')+
                       ((stat.end)? 'End: '+ new Date(stat.end).toLocaleTimeString()+'<br>' : '');
            var retval = (!cancel_button)? html : {html:html, callback:tooltip_func}
            if (!e) Tooltips.show_2(null, retval); // push
            else return retval; // for initial, pull
          }
        }
        function mouseout(e){
          if (src) Tooltips.hide();
          src = null;
//          if (pref.tooltips['info'].show) Tooltips.hide2();
        }
        function tooltip_func(pn){
          pn.getElementsByTagName('button')[0].onclick = cancel_scan;
        }
        function click(){
          if (pref.healthIndicator.cancel) cancel_scan.call(this);
        }
        function cancel_scan(){
          var name = this.getAttribute('name');
          var stat = status[name];
          if (stat && !stat.end && stat.prog) stat.prog.ABORT = true; // NOT DEBUGGED YET!!!
//          if (status[name] && !status[name].end) cataLog.scan_boards.scan_abort(status[name].initiator);
        }
        function show(){
          var disp = (pref.healthIndicator.show)? '' : 'none';
          for (var i=0;i<his.length;i++) his[i].pn.childNodes[1].style.display = disp;
        }
        function shrink_running(){
          if (!pref.healthIndicator.expand_running)
            for (var i=0;i<his.length;i++) 
              for (var j=0;j<his[i].elems.length;j++) if (his[i].elems[j].pn.textContent.length>1) his[i].elems[j].set(null,'r');
        }
        return {
          HealthIndicator: HealthIndicator,
          show: show,
          shrink_running: shrink_running,
        }
      })();
      cataLog.healthIndicator = healthIndicator;
      var health_indicator = new healthIndicator.HealthIndicator(true);
      httpd.set_health_indicator(health_indicator);

////      var health_indicator = (function(){ // working code.
////        var pn_hi = document.createElement('span');
//////        if (!(embed_catalog)) pn_hi.style['font-size'] = '24px';
////        if (!pref.catalog.health_indicator.on) pn_hi.style.display = 'none';
////        pn12_0.childNodes[3].appendChild(pn_hi);
////        function insert_node(col,str){
////          var max = pref.catalog.health_indicator.max;
////          while (pn_hi.childNodes.length>=max) pn_hi.removeChild(pn_hi.lastChild);
////          var pn = document.createElement('span');
////          if (col) pn.setAttribute('style','color:'+col);
////          if (str) pn.innerHTML = str;
////          pn_hi.insertBefore(pn,pn_hi.firstChild);
////          return pn;
//////          while (pn_hi.childNodes.length>=max) pn_hi.removeChild(pn_hi.childNodes[pn_hi.childNodes.length-1]);
//////          pn_hi.innerHTML = '<span' + ((col)? ' style="color:'+col+'"' : '' ) + '>'+str+'</span>' + pn_hi.innerHTML;
////        }
////        pref_func.health_indicator = pn_hi;
////        return {
////          pn_hi : pn_hi,
////          set: function(pn,col,str){
//////            if (!pn) pn = pn_hi.childNodes[0];
////            if (str) pn.textContent = str;
////            if (col) pn.style.color = col;
////          },
////          shift: function(col,str){return insert_node(col,str);},
////          remove: function(pn){
////            pn.parentNode.removeChild(pn);
////          }
////        }
////      })();

      var Footer = {
        clg: null,
        pref_fmt: null,
        view_changed: function(init){ // must be called with all thread's tgt_th[0] == invalid.
//          var pf_old = this.pref_fmt;
          this.mode_fmt = this.clg.mode==='float'? 'float' : this.clg.view;
          this.pref_fmt = pref[this.mode_fmt].footer;
//          var pn_menu_old = this.pn_menu;
//          var pn_triage_old = this.pn_triage;
          this.compile();
//          if (init) return;
//          this.add_remove_menu(pn_menu_old, pn_triage_old, this.pref_fmt.menu_str!==pf_old.menu_str, this.pref_fmt.triage_str!==pf_old.triage_str); // this is NOT needed. this function is always called without !tgt_th[0], so footer is remaked always
        },
        mode: embed_mode,
        mode_fmt: null, // embed_mode=='float'? 'float' : 'catalog',
        threads: threads,
        mg_footers: new Set(),
//        timestamp_trial: 16,
        timestamp: embed_mode==='thread'? 16 : 0,
        clean_idx: 0,
        timestamp_set: function(){
//          if (this.timestamp) {
          if (this.realtime) {
            this.timestamp += 32; // dirtify all // 16 and 32 is the magic number here, search it. This is used in literal forms in all cases for update timestamp
            this.clean_idx = 0;
          }
//            this.clg.drawn_idx = 0;
//            this.clg.show_catalog(); // update footers, this is too heavy, but required.
////            this.update_all_lazy(null, true);
//          }
        },
//        timestamp_trial_prep: function(){this.timestamp_trial = this.timestamp + 16;},
//        timestamp_inc: function(){this.timestamp = this.timestamp_trial;},
//        update_all_flags_force: function(){
//          for (var name in this.threads) this.update(name, true, null, true);
//        },
        update_all_force: function(e,src, flags, tags){
          if (src.indexOf('liveTag.style')==-1) {
            if (src==='proto.footer.merged_tag') {if (!this.pref_fmt.tag) return; else tags = true;}
            else if (src.slice(0,5)!=='proto' && src.indexOf(this.mode_fmt)!==0) return;
//            if (src==='proto.footer.merged_tag') {if (!pref[this.mode_fmt].footer.tag) return; else tags = true;}
//            else if (this.mode_fmt==='float' ^ src.indexOf('float')!=-1) return;
            var tgt = src.split('.')[2];
            var pn_menu_old = this.pn_menu;
            var pn_triage_old = this.pn_triage;
            if (!flags) this.compile(); // remake triage here
            if (tgt==='use' || tgt==='triage' || tgt==='menu' || tgt==='triage_str' || tgt==='menu_str') {
              this.add_remove_menu(pn_menu_old, pn_triage_old, tgt==='menu_str', tgt==='triage_str');
              if (tgt!=='use') return;
            }
//            if (tgt==='use' || tgt==='triage' || tgt==='menu') {
//              this.add_remove_menu(pn_menu_old, pn_triage_old);
//              if (tgt==='triage' || tgt==='menu') return;
//            }
//            if (tgt==='triage_str' || tgt==='menu_str') {this.replace_menu(tgt);return;}
          } else tags = true;
          for (var name in this.threads) this.update_force(name, flags, tags, true);
//          this.update_all_lazy(flags);
        },
//        update_all_lazy: function(flags, only_shown){
//          var idxs = this.clg.idxs;
//          var drawn_idx = this.clg.drawn_idx; // drawn_idx doesn't track exactly, it points meaningless point when tgts_in are given for show_catalog(tgts_in).
//          if (drawn_idx===true) drawn_idx = idxs.length;
//          var end = (only_shown)? drawn_idx : idxs.length;
//          for (var i=0;i<end;i++) if (idxs[i].slice(0,4)!==':DL:') this.update(idxs[i], flags, null, i<drawn_idx);
//        },
        update_force: function(name,flags,tags, merge_consolidate){
          var tgt_th = this.threads[name];
          var footer = tgt_th[24];
          if (footer && tgt_th[1]) this.insert_footer3(footer, name, /*0x10,*/ flags, tags, merge_consolidate); // keep force_network_update flag
        },
        update: function(name,flags,tags){ // , idx){ // from network only
          var footer = this.threads[name][24];
          if (!footer) return;
//          if (this.threads[name][1] && (footer[4]&0x10)) { // 0x10 is mark for force_network_update, which is used by mode==='thread' and triage_rewind_watch
////          if (this.threads[name][1] && (footer[4]>=this.timestamp || force)) { // BUG, this should be footer[4]>=this.timestamp_TRIAL to track clean area.
//            this.insert_footer3(footer, name, 0, flags, tags); // clear flag for force_network_update
////            footer[4] &= 0xfffffff0; // keep timestamp
//          } else {
            footer[4] |= (flags? 0x01 : 0) | (tags? 0x04 : 0) | 0x08; // | (page? 0x02 : 0); // 0x08 is dirty mark
////            if (this.clean_idx>idx) this.clean_idx = idx;
//          }
        },
//        queue_update_force: function(name){ // working code
//          var footer = this.threads[name][24];
//          if (footer) footer[4] |= 0x10; // 0x10 is mark for force_network_update
//        },
//        seek_clean_idx: function(idxs, idx){ // working code
//          var ts = this.timestamp;
//          if (idx===0 || this.realtime && this.real_ts!==ts) {this.real_ts = ts; return 0;}
//          var i = idx;
//          while (--i>=0) {
//            var footer = this.threads[idxs[i]][24];
//            if (footer && footer[4]!==ts) idx = i;
//          }
//          return idx;
////          return this.realtime && this.real_ts!==this.timestamp? (this.real_ts = this.timestamp, 0) : this.clean_idx;
//        },
//        query_update_without_data: function(page_info){ // working code
//          return this.realtime || page_info && pref[this.mode_fmt].footer.page;
//        },
////        update_without_updated_data: function(page_info){ // working code
////          if (!this.realtime && (!page_info || !pref[this.mode_fmt].footer.page)) return;
////          var ts_ref = this.real_ts; // real_ts is stored timestamp of last lazy_draw. // this.timestamp - 32;
////          for (var name in this.threads) {
////            var footer = this.threads[name][24];
////            if (footer && this.threads[name][1] && footer[4]>=ts_ref) this.insert_footer3(footer, name, 0x10, null, null, true); // keep force_network_update flag
////          }
////        },
        try_dirtifying_tag: function(name){
          var footer = this.threads[name][24];
          if (!footer) true;
          if (footer[4] & 0x0f) {
            footer[4] |= 0x04;
            return true;
          }
        },
        test_dirty: function(name){ // for debug
          var footer = this.threads[name][24];
          return footer && (footer[4] & 0x0f) && footer;
        },
        draw: function(tgt_th, name){ // must update even if footer has no updated data.
          var footer = tgt_th[24];
//          var footer = this.threads[name][24];
//          if (!footer) return;
          if (footer[4]!==this.timestamp) this.insert_footer3(footer, name, null, null, pref.test_mode['214'] && 0x02); // , 0x10); // keep force_network_update flag
//          if (footer[4] & 0x0f) this.insert_footer3(footer, name);
//          footer[4] = this.timestamp_trial; // keep updating while lazy drawing is not completed, but this is too much.
        },
        draw_merge: function(tgt_th, name, merge){ // update both footers in both cases.
          var footer = tgt_th[24];
          if (!merge) this.insert_footer3(footer, name, /*0x10,*/ null, true, pref.test_mode['214'] && 0x02); // for unmerge, update both footers.
          footer[6] = merge;
          if ( merge) this.insert_footer3(footer, name, /*0x10,*/ null, true, pref.test_mode['214'] && 0x02); // for merge, update both footers.
        },
        factory: function(clg, init0){
          if (init0) {
            this.clg = clg;
            if (!pref_func.settings.pn13) pref.settings.footer_sel = clg.mode==='float'? 3 : clg.view==='catalog'? 1 : clg.view==='page'? 2 : 0;
          }
          var retval = init0? this : {
            mg_footers: new Set(),
            threads: clg.threads,
//            timestamp_trial: this.timestamp,
            timestamp: 0, // updated by compile() just below
            mode: clg.mode,
            mode_fmt: null, // clg.mode=='float'? 'float' : 'catalog',
            pref_fmt: null,
            clean_idx: 0,
            clg: clg,
            realtime: 0,
//            real_ts: 0,
            pn_menu: null,
            pn_triage: null,
            __proto__: this
          };
          retval.view_changed(true);
//          retval.compile();
          return retval;
        },

        pn: document.createElement('div'),
        prep_flag_dom: function(dom){
          if (dom.flag && (dom.flag instanceof Element)) return document.importNode(dom.flag);
          if (!site2[site.nickname].post_flag2html) return undefined;
          this.pn.innerHTML = site2[site.nickname].post_flag2html(dom); // site2[dom.domain_html].post_flag2html(dom); // patch, mimic always
          return this.pn.childNodes[0];
        },
//        prep_flag_dom: function(flags, idx){ // working code
//          var dom = flags[idx];
//          if (dom instanceof Element) return dom;
//          if (dom.flag && (dom.flag instanceof Element)) flags[idx] = dom.flag; // for KC
//          else {
//            this.pn.innerHTML = site2[site.nickname].post_flag2html(dom); // site2[dom.domain_html].post_flag2html(dom); // patch, mimic always
//            flags[idx] = this.pn.childNodes[0];
//          }
//          return flags[idx];
//        },
        ts_refresh: null,
        refresh_start: null,
        formatted_arr: null,
//        format_relative_time: null,
//        format_time: null,
        get_str: null,
        prep_footer3: function(th, footer_old){
          var footer = th.footer;
          footer.classList.add(pref.cpfx+'footer');
          var footer_native = th.type_parse==='catalog_html' && th.parse_funcs_html.footer_prep(th, footer) || null;
//          var str = footer.innerHTML.replace(/ *R:[0-9 \/]*I:[0-9 \/]*/,'');
//          str = str.replace(/P:[0-9 \/]*/,'');
//          str = str.replace(/ *\(sticky\) */,'');
//          if (str!=='') {
//            footer.innerHTML = '<span></span>' + str;
//            footer = footer.childNodes[0];
//          }
//          if (pref[this.view].footer_br) { // working code
//            var footer_style = footer.getAttribute('style');
//            footer_style = ((footer_style)? footer_style + ';' : '') + 'clear:both';
//            footer.setAttribute('style',footer_style);
//          }
          this.add_menu(footer);
          return [footer, footer_native, null, null, 0x06 | (th.country? 0x01 : 0), th.footer2? [th.footer2, null, null, null] : null];
//          return [footer, null, null, null, 0x06 | (th.country? 0x01 : 0), footer_native, null, null]; // [7] for test_mode['119']
//          return (footer_old)? [footer, th.country, footer_old[2], null,          0x07, null, null] // for reentry
//          return (footer_old)? [footer, th.country, footer_old[2], footer_old[3], 0x07, null, null] // for reentry
//                             : [footer, th.country, null,          null,          0x07, null, null];
        },
        add_remove_menu: function(pn_menu_old, pn_triage_old, replace_menu, replace_triage){ // stateful approach is required for loading default (of this page), it changes multiple values at a time.
          var func_menu = this.add_remove_sel(this.menu, pn_menu_old, this.pn_menu, replace_menu);
          var func_triage = this.add_remove_sel(this.triage, pn_triage_old, this.pn_triage, replace_triage);
//          var func_menu = (!pn_menu_old &&  this.pn_menu)? this.add_menu_1.bind(this)
//                        : ( pn_menu_old && !this.pn_menu)? this.remove_menu_1.bind(this)
//                        : ( pn_menu_old &&  this.pn_menu && replace_menu)? this.replace_menu_1.bind(this) : null;
//          var func_triage = (!pn_triage_old &&  this.pn_triage)? this.add_triage_1.bind(this)
//                          : ( pn_triage_old && !this.pn_triage)? this.remove_triage_1.bind(this)
//                          : ( pn_triage_old &&  this.pn_triage && replace_triage)? this.replace_triage_1.bind(this): null;
          if (!func_menu && !func_triage) return;
//          this.add_remove_menu_iterate(func_menu, func_triage);
////          var pf = pref[this.mode_fmt].footer;
////          var val_changed = pf[tgt];
////          for (var name in this.threads) { // working code // stateful approach is required for loading default (of this page), it changes multiple values at a time.
////            var footer = this.threads[name][24];
////            if (!footer) continue; 
////            if (!pn_menu_old && this.pn_menu) this.add_menu_1(footer[0]);
////            else if (pn_menu_old && !this.pn_menu) footer[0].removeChild(footer[0].lastChild);
////            if (!pn_triage_old && this.pn_triage) this.add_triage_1(footer[0]);
////            else if (pn_triage_old && !this.pn_triage) footer[0].removeChild(footer[0].firstChild);
//////            if (tgt!=='triage') { // called from 'use', 'menu' and 'triage'.
//////              if (pf.use && pf.menu) this.add_menu_1(footer[0]);
//////              else if (!val_changed && (pf.use ^ pf.menu)) footer[0].removeChild(footer[0].lastChild); // BUG, this doesn't allow multiple values to be changed at once, ERROR at loading default of this page
//////            }
//////            if (tgt!=='menu') {
//////              if (pf.use && pf.triage) this.add_triage_1(footer[0]);
//////              else if (!val_changed && (pf.use ^ pf.triage)) footer[0].removeChild(footer[0].firstChild); // BUG, this doesn't allow multiple values to be changed at once, ERROR at loading default of this page
//////            }
////          }
//        },
//        add_remove_menu_iterate: function(func_menu, func_triage){
          for (var name in this.threads) {
            var footer = this.threads[name][24];
            if (footer) this.add_remove_menu_1(footer, func_menu, func_triage);
          }
          var pf = this.clg.pref[this.clg.mode];
          if (pf.merge || pf.merge_list) {
            var mb = this.clg.merge_bases;
            var bases_iter = mb.bases_unique().values(); // bases_unique returns (value,key) pair instead of (key,balue) pair
            for (var name of bases_iter) {
              var footer = mb.bases[name].footer;
              if (footer) this.add_remove_menu_1(footer, func_menu, func_triage);
            }
          }
        },
        add_remove_menu_1: function(footer, func_menu, func_triage){
          if (func_menu) func_menu(footer[0], this.pn_menu); // https://stackoverflow.com/questions/17638305/why-is-bind-slower-than-a-closure
          if (func_triage) func_triage(footer[0], this.pn_triage);
        },
        add_menu: function(footer_0){ // for initial only
          if (this.pn_menu) this.menu.add(footer_0, this.pn_menu);
          if (this.pn_triage) this.triage.add(footer_0, this.pn_triage);
          return footer_0;
        },
//        replace_menu: function(tgt){ // working code
//          var func_menu = (tgt==='menu_str' && this.pn_menu)? function(footer){this.remove_menu_1(footer);  this.add_menu_1(footer);}.bind(this) : null;
//          var func_triage = (tgt==='triage_str' && this.pn_triage)? function(footer){this.remove_triage_1(footer); this.add_triage_1(footer);}.bind(this) : null;
//          this.add_remove_menu_iterate(func_menu, func_triage);
//        },
////        replace_menu: function(tgt){ // working code
////          for (var name in this.threads) {
////            var footer = this.threads[name][24];
////            if (!footer) continue; 
////            if (tgt==='menu_str' && this.pn_menu) {this.remove_menu_1(footer[0]);  this.add_menu_1(footer[0]);}
////            if (tgt==='triage_str' && this.pn_triage) {this.remove_triage_1(footer[0]); this.add_triage_1(footer[0]);}
////          }
////        },
        add_remove_sel: function(funcs,old,now,replace){
          return (!old &&  now)? funcs.add
               : ( old && !now)? funcs.remove
               : ( old &&  now && replace)? funcs.replace : null;
        },
        pn_menu: null, // initialized in compile()
        menu: {
          add:     function(footer,pn){footer.appendChild(pn.cloneNode(true));},
          replace: function(footer,pn){footer.replaceChild(pn.cloneNode(true), footer.lastChild);},
          remove:  function(footer){footer.removeChild(footer.lastChild);}},
        pn_triage: null, // initialized in compile()
        triage:{
          add:     function(footer,pn){footer.insertBefore(pn.cloneNode(true), footer.firstChild);},
          replace: function(footer,pn){footer.replaceChild(pn.cloneNode(true), footer.firstChild);},
          remove:  function(footer){footer.removeChild(footer.firstChild);}},
        color_tag_node: function(name,tag){
          var footer = this.threads[name] && this.threads[name][24];
          if (!footer) return;
//if (pref.test_mode['119']) { // doesn't work with multiple footers
//          var tag_ci = tag.toLowerCase();
//          var tags = footer[7];
//          if (tags) for (var j=0;j<tags.length;j++) if (tags[j]===tag || (pref.liveTag.ci && tags[j].toLowerCase()===tag_ci)) return footer[3].childNodes[j*2];
//} else {
          if (this.try_dirtifying_tag(name)) return; // this is ok as long as liveTag.create_tag_nodes is used in insert_footer3
          var pn = this.query_tag_node(footer, tag);
          if (pn) {
            this.liveTag.color_tag_node(pn,tag);
//            if (footer[5]) this.liveTag.color_tag_node(this.query_tag_node(footer[5], tag), tag);
            if (footer[6] && footer[6].footer) {
              var pn_merged = this.query_tag_node(footer[6].footer, tag);
              if (pn_merged) this.liveTag.color_tag_node(pn_merged, tag);
            }
          }
//}
        },
        query_tag_node: function(footer, tag){
          var pn = footer[3] && footer[3].firstChild;
          while (pn) if (pn.textContent===tag) return pn; else pn = pn.nextElementSibling;
        },
        remove_nullify: function(footer,idx){
          footer[0].removeChild(footer[idx]);
          footer[idx] = null;
        },
        insert_footer3: function(footer, name, /*ts_mask,*/ flags, tags, merge_consolidate){
          var tgt_th = this.threads[name];
          var lth = liveTag.mems.getFromName(name);
          flags = flags || (footer[4]&0x01);
          tags = tags || (footer[4]&0x04);
          this.draw_exe_1(footer, flags, tags, tgt_th, lth);
          if (footer[5]) this.draw_exe_1(footer[5], flags, tags, tgt_th, lth);
          if (footer[6] && footer[6].footer)
            if (pref.test_mode['214'] && (merge_consolidate&0x02)) this.draw_merge_consolidated_req(footer[6], flags, tags);
            else if (!merge_consolidate || footer[6].lths[0]===lth) this.draw_exe_1(footer[6].footer, null, tags, footer[6], footer[6]); // tricky, see site2['DEFAULT'].update_posts_merge_bases.base_factory
          footer[4] = this.timestamp; //  + (footer[4]&ts_mask); // ts_mask: 0x10 for keeping flag for force_network_update, 0 for clearing it.
        },
        draw_merge_consolidated_req: function(mg, flags, tags){
          mg.footer[4] |= (flags? 0x01 : 0) | (tags? 0x04 : 0) | 0x08; // copied from this.update
          this.mg_footers.add(mg);
        },
        draw_merge_consolidated_exe: function(){
          for (var mg of this.mg_footers) {
            var ft = mg.footer;
            if (ft) this.draw_exe_1(ft, null, (ft[4]|0)&0x04, mg, mg);
          }
          this.mg_footers.clear();
        },
        draw_exe_1: function(footer, flags, tags, tgt_th, lth){
          var pf = this.pref_fmt; // pref[this.mode_fmt].footer;
          var str = pf.use && this.get_str(tgt_th, lth);
//          if (pf.use) {
//            flags = flags || (footer[4]&0x01);
//            tags = tags || (footer[4]&0x04);
//            var str = this.get_str(tgt_th, lth);
////            if (pf.design==='custom') str = this.get_str(tgt_th, lth); // working code
////            else {
////              var nums2  = tgt_th[8];
////              var page = tgt_th[14] || '?';
////              var str_nr = ((pf.nrtm)? lth.nrtm +'/' : '' ) + lth.nr;
////              var str_nl = (pf.nl && lth.ta && lth.ta.posts)? lth.ta.posts.length+'/' : '';
////              var str_ns = (pf.ns && lth.sg)? '*'+lth.sg.size : '';
////              var str_nd = (pf.nd && lth.pd)? '+'+lth.pd.length : '';
////              str     = ((pf.archived && lth.archived)? 'A ' : '') +
////                        ((pf.design==='native')?
////                          ((pf.nr)? ((lth.watched)? 'U: '+ str_nr + ' / ' : ''):'') + 'R: '+ str_nl+(nums2[2]-1)+str_ns+str_nd +  ' / I: '+nums2[3] + ((pf.page)? ' / P: '+page : '')
////                        : ((pf.nr)? ((lth.watched)? str_nr + '/' : ''):'') + str_nl+nums2[2]+str_ns+str_nd + '/'+nums2[3]+ ((pf.page)? '/'+page : '')); // trial.
////              if (str) str += ' ';
////              if (pf.nf || pf.nid) {
////                var str_nc = pf.nf && lth.sID && lth.sID.nc;
////                var str_nid = pf.nid && lth.sID && (lth.sID.nid || lth.sID.np);
////                str += (str_nid || '') + ((str_nid && str_nc)? '/' : '') + (str_nc || '') + ((str_nid || str_nc)? ' ' : '');
////              }
////  
////              if (pf.ctime) str += this.format_time(nums2[1]) + ' ';
////              if (pf.rctime) str += this.format_relative_time(nums2[1]) + ' ';
////              if (pf.btime && nums2[0]) str += this.format_time(nums2[0]) + ' ';
////              if (pf.rbtime && nums2[0]) str += this.format_relative_time(nums2[0]) + ' ';
////              if (pf.ptime && nums2[4]) str += this.format_time(nums2[4]) + ' ';
////              if (pf.rptime && nums2[4]) str += this.format_relative_time(nums2[4]) + ' ';
////  
////              if (pf.prate) str += Math.floor(nums2[2]/((this.ts_refresh - nums2[1])/86400000)) + ' ';
////              if (pf.board || pf.no || pf.domain) {
////                str += ((pf.domain)? lth.domain : '')+
////                       ((pf.board)? lth.board : '')+
////                       ((pf.no)? lth.no : '') + ' ';
////              }
////            }
//          }
          if (str) {
            if (footer[1]) {
              if ((footer[4]&0x08) || this.realtime!=1 || footer[1].innerHTML!==str) footer[1].innerHTML = str; // textContent = str;
            } else {
              var footer1 = document.createElement('span');
              footer1.innerHTML = str;
              footer[1] = footer[0].insertBefore(footer1, pf.triage? footer[0].childNodes[1] : footer[0].firstChild); // document.createTextNode(str), footer[0].firstChild);
            }
          } else if (footer[1]) this.remove_nullify(footer,1);
  
          if (pf.tag && pf.use) {
            if (tags || !footer[3]) {
//if (pref.test_mode['119']) { // stateful approach
//              var tags_new = lth.tags;
//              var pn = this.liveTag.update_tag_nodes(lth.tags, ', ', this.tag_node_onclick, footer[3], footer[7]); // referred from query_tag_node
//              footer[7] = tags_new; // stateful approach, TAGS MUST BE UPDATED BY REPLACING INSTEAD OF SPLICING
//              if (!footer[3]) footer[3] = footer[0].insertBefore(pn, footer[2]);
////              footer[7] = lth.tags;
////              var pn = liveTag.create_tag_nodes(footer[7], ', ', liveTag.tag_node_onclick); // referred from query_tag_node
////              if (footer[3]) footer[3].parentNode.removeChild(footer[3]);
////              footer[3] = footer[0].insertBefore(pn, footer[2]);
//} else {
              var pn = this.liveTag.create_tag_nodes(lth.tags, ', ', this.tag_node_onclick);
              footer[3] = (footer[3])? (footer[0].replaceChild(pn, footer[3]), pn)
                                     : footer[0].insertBefore(pn, footer[2] || pf.menu? footer[0].lastChild : null);
//              if (footer[3]) footer[3].parentNode.removeChild(footer[3]);
//              footer[3] = footer[0].insertBefore(pn, footer[2] || pref[this.mode].footer_triage? footer[0].lastChild : null);
//}
            }
          } else if (footer[3]) this.remove_nullify(footer,3); // footer[7] = null;}
//          if (pf.tag && pf.use) { // working code
//            tags = tags && lth;
//            if (!footer[3]) {
//              footer[3] = footer[0].insertBefore(document.createElement('span'), footer[6]);
//              tags = lth;
//            } else if (tags) footer[3].removeChild(footer[3].firstChild);
//            if (tags) footer[3].appendChild(liveTag.update_tag_string(lth.tags, ', ', liveTag.tag_node_onclick));
//          } else if (footer[3]) Footer.remove_nullify(footer,3);
  
          if (pf.flag && pref[this.mode_fmt].t2h_num_of_posts>=0 && pf.use) {
            if (flags || !footer[2]) flags = tgt_th[16].recent_posts(); // .slice(); // EMBED_MODE IS USED IN THIS, MUST BE CHANGED.
            if (!footer[2]) footer[2] = footer[0].insertBefore(document.createElement('span'), pf.menu? footer[0].lastChild : null);
//            if (!footer[2]) footer[2] = footer[0].appendChild(document.createElement('span'));
            if (flags && flags.length>0) {
              for (var i=footer[2].childNodes.length-1;i>=0;i--) footer[2].removeChild(footer[2].firstChild);
              for (var i=0;i<flags.length;i++) if (flags[i]) {
                var flag = Footer.prep_flag_dom(flags[i]);
                if (flag) {
                  if (flags[i].deleted_after) flag.classList.add('CatChan_deleted');
                  footer[2].appendChild(flag);
                }
              }
//              var i = flags.length - pref[embed_mode].t2h_num_of_posts; // working code
//              if (i<1) i=1;
//              if (flags[0] && Footer.prep_flag_dom(flags,0)) footer[6].appendChild(flags[0]);
//              while (i<flags.length) {
//                if (flags[i] && Footer.prep_flag_dom(flags,i)) footer[6].appendChild(flags[i]);
//                i++;
//              }
//              footer[1] = flags;
            }
          } else if (footer[2]) this.remove_nullify(footer,2);

          if (pref.debug_mode['32'] && footer[2]) {
            var flags_debug = tgt_th[16].recent_posts(); // .slice();
            var pn = document.createElement('span');
            for (var i=0;i<flags_debug.length;i++) if (flags_debug[i]) {
              var flag = Footer.prep_flag_dom(flags_debug[i]);
              if (flag) {
                if (flags_debug[i].deleted_after) flag.classList.add('CatChan_deleted');
                pn.appendChild(flag);
              }
            }
            var pns = [footer[2], pn].map(function(v){
              return Array.prototype.map.call(v.getElementsByClassName('flag'),function(v){return v.getAttribute('class').match(/flag\-(..)/)[1];}).toString();});
            if (pns[0]!=pns[1]) console.log('Error: Flags: '+lth.key+': '+'\n'+pns[0]+'\n'+pns[1]);
          }
        },
        format_relative_time: function(rt){
          var sign = (rt<0)? '-' : '+';
          var at = Math.floor(Math.abs(rt)/1000);
          var s = at%60;
          var m = (at>=60)? Math.floor((at%3600)/60) : 0;
          var h = (at>=3600)? Math.floor((at%86400)/3600) : 0;
          var d = (at>=86400)? Math.floor(at/86400)+'d' : '';
          return sign + d + ((d && h<10)? '0':'') + ((at>=3600)? h+':':'') + ((at>=3600 && m<10)?'0':'') + ((at>=60)? m+':':'') + ((at>=60 && s<10)?'0':'') + s;
//          return (sat<60)? '0:00:'+at
//            : (at<3600)? '0:Math.floor(at/60)+'mins'
//            : Math.floor(at/3600)+':'+Math.floor((at%3600)/60);
        },
      };
      Footer.get_str = (function(parent){
        function format_relative_time(time){
          return Footer.format_relative_time(time - ts_refresh);
        }
        var ts_refresh = Date.now();
//        var ts_refresh_rx = null;
        function format_time(time){
          return (ts_refresh-time>=86400000)? new Date(time).toLocaleString() : new Date(time).toLocaleTimeString();
//          if (!ts_refresh_rx) ts_refresh_rx = new RegExp(new Date(ts_refresh).toLocaleDateString(),'g');
//          return new Date(time).toLocaleString().replace(ts_refresh_rx,'') + ' ';
        }
        function style_gt0(func, style){
          return function(){
            var n = func();
            return n>0? '<span style="'+style+'">'+n+'</span>' : n;};
        }
        var tgt_th = null;
        var lth = null;
        var nums2 = null;
        var func_tmp;
        var funcs = {
          pg: function(pf){return (pf.page)? (pf.pgp? function(){return tgt_th[14] || '?';} : function(){return tgt_th[14].slice(0,tgt_th[14].indexOf('.')) || '?';}) : null;},
          nm: function(pf){return (!pf.nrtm)? null
                                : ((func_tmp = pf.nrtm0? function(){return lth.watched && lth.nrtm || '';} : function(){return lth.watched? lth.nrtm : '';}),
                                  !pf.nrtm1? func_tmp : style_gt0(func_tmp, pref_func.sanitize(pf.nrtm1_style)));},
//          nm: function(pf){return (pf.nrtm)? (pf.nrtm0? function(){return lth.watched && lth.nrtm || '';} : function(){return lth.watched? lth.nrtm : '';}) : null;},
          nr: function(pf){return (!pf.nr)? null
                                : ((func_tmp = pf.nr0? function(){return lth.watched_p && lth.nr || '';} : function(){return lth.watched_p? lth.nr : '';}),
                                  !pf.nr1? func_tmp : style_gt0(func_tmp, pref_func.sanitize(pf.nr1_style)));},
//          nr: function(pf){return (pf.nr)? (pf.nr0? function(){return lth.watched && lth.nr || '';} : function(){return lth.watched? lth.nr : '';}) : null;},
          rp: function(pf){return (pf.rp)? (pf.rOP? (pf.rp0? function(){return nums2[2]>1? nums2[2] : '';} : function(){return nums2[2];})
                                                  : (pf.rp0? function(){return nums2[2]-1 || '';}          : function(){return nums2[2]-1;})) : null;},
          RP: function(pf){var f = this.rp(pf); return f? paste_wrap('<b>', f, '</b>') : null;},
//          rp: function(pf){return (pf.rp)? (pf.rOP? function(){return nums2[2];} : function(){return nums2[2]-1;}) : null;},
//          RP: function(pf){return (pf.rp)? (pf.rOP? function(){return '<b>'+nums2[2]+'</b>';} : function(){return '<b>'+(nums2[2]-1)+'</b>';}) : null;},
          im: function(pf){return (pf.im)? (pf.iOP? function(){return nums2[3];} : function(){return nums2[3]>1? nums2[3]-1 : '';}) : null;},
          IM: function(pf){var f = this.im(pf); return f? paste_wrap('<b>', f, '</b>') : null;},
//          IM: function(pf){return (pf.im)? (pf.iOP? function(){return '<b>'+nums2[3]+'</b>';} : function(){return nums2[3]>1? '<b>'+(nums2[3]-1)+'</b>' : '';}) : null;},
//          rp: function(pf){return (pf.rp)? function(){return nums2[2];} : null;},
//          r1: function(pf){return (pf.rp)? function(){return nums2[2]-1;} : null;},
//          im: function(pf){return (pf.im)? function(){return nums2[3];} : null;},
//          i1: function(pf){return (pf.im)? function(){return nums2[3]>1? nums2[3]-1 : '';} : null;},
          lp: function(pf){return (pf.nl)? function(){return (lth.ta && lth.ta.posts)? lth.ta.posts.length : '';} : null;},
          dp: function(pf){return (pf.nd)? function(){return lth.pd? lth.pd.length : '';} : null;},
          ar: function(pf){return (pf.archived)? function(){var ar = lth.archived; return ar>=8? 'a' : ar? 'A' : '';} : null;}, // >=8 for merge
          nf: function(pf){return (pf.nf)?  function(){return lth.sID && lth.sID.nc || '';} : null;},
          ni: function(pf){return (pf.nid)? function(){return lth.sID && (lth.sID.nid || lth.sID.np) || '';} : null;},
          ct: function(pf){return (pf.ctime)?  function(){return format_time(nums2[1]);} : null;},
          cT: function(pf){return (pf.rctime)? function(){return format_relative_time(nums2[1]);} : null;},
          bt: function(pf){return (pf.btime)?  function(){return nums2[0]? format_time(nums2[0]) : '';} : null;},
          bT: function(pf){return (pf.rbtime)? function(){return nums2[0]? format_relative_time(nums2[0]) : '';} : null;},
          pt: function(pf){return (pf.ptime)?  function(){return nums2[4]? format_time(nums2[4]) : '';} : null;},
          pT: function(pf){return (pf.rptime)? function(){return nums2[4]? format_relative_time(nums2[4]) : '';} : null;},
          pr: function(pf){return (pf.prate)?  function(){return Math.floor(nums2[2]/((ts_refresh - nums2[1])/86400000));} : null;},
          dn: function(pf){return (pf.domain)? function(){return lth.domain;} : null;},
          bd: function(pf){return (pf.board)?  function(){return lth.board;} : null;},
          no: function(pf){return (pf.no)?     function(){return lth.no;} : null;},
          sp: function(pf){return (pf.ns)?     function(){return lth.sg? lth.sg.size : '';} : null;},
        };
        var funcs_and_tags = { // for dangling tags
//          IB: function(pf){return function(){return lth.th && lth.th.bumplimit? '<i>' : '';}}, // obsolete
//          Ib: function(pf){return function(){return lth.th && lth.th.bumplimit? '</i>' : '';}},
//          II: function(pf){return function(){return lth.th && lth.th.imagelimit? '<i>' : '';}},
//          Ii: function(pf){return function(){return lth.th && lth.th.imagelimit? '</i>' : '';}},
//          '<iB' : function(pf){return function(){return lth.th && lth.th.bumplimit? '<i' : '';}}, // test_mode['138']
//          '</iB': function(pf){return function(){return lth.th && lth.th.bumplimit? '</i' : '';}}, // for NOT enclosed pair in ()
//          '<iI' : function(pf){return function(){return lth.th && lth.th.imagelimit? '<i' : '';}},
//          '</iI': function(pf){return function(){return lth.th && lth.th.imagelimit? '</i' : '';}}, // for NOT enclosed pair in ()
          __proto__: funcs
        };
        var tagFuncs = { // can accept <(span|[isub])[BIZ]>, for paired tag
          B : function(){return lth.th && lth.th.bumplimit;},
          I : function(){return lth.th && lth.th.imagelimit;},
          Z : function(){return true;},
          condTag: function(stag_in, f_str, etag_in){
            var f_cond = this[stag_in[2]] || this[stag_in[5]]; // stag_in[5] for <spanB
            var func_Z = f_cond===this['Z'];
            var stag = stag_in.replace(/<(span|[siub])[BIZ]/,'<$1');
            var etag = etag_in.replace(/<\/(span|[siub])[BIZ]/,'</$1');
            return func_Z? function(){
                var str = f_str();
                return str>0? stag + str + etag : str;}
              : function(){
                var str = f_str();
                return str!==''? (f_cond()? stag + str + etag : str) : '';}},
          factory: function(t,f_cond){return function(pf){return function(){return f_cond()? t : '';}}}
        };
        ['s','i','u','b','span'].forEach(t=>['<','</'].forEach(se=>['B','I','Z'].forEach(c=>funcs_and_tags[se+t+c] = tagFuncs.factory(se+t, tagFuncs[c]))));
//        var tags_allowed = ['s','i','u','b','span'];
//        var conds = ['B','I'];
//        for (var i=0;i<tags_allowed.length;i++) {
//          var t = tags_allowed[i];
//          for (var j=0;j<conds.length;j++) {
//            var c = conds[j];
//            funcs_and_tags['<'+t+c] = tagFuncs.factory('<'+t, tagFuncs[c]);
//          }
//        }
//        var fmts = {
//          native: pref0.footer.native,
//          condensed: '(ar )(nm/)(nr/)(lp/)(rp)(*sp)(+dp)(/im)(/pg) (ni/nf )(ct )(cT )(bt )(bT )(pt )(pT )(pr )(dn)(bd)(no) ',
//          meguca:    '(ar )(dn)(bd)(no) (nm/)(nr/)(lp/)(rp)(*sp)(+dp)(/im)(/pg) (ni/nf )(ct )(cT )(bt )(bT )(pt )(pT )(pr ) ',
////          get custom_str(){return pref[myself.mode].footer.custom_str;}, // patch for minify. This line hits a bug in minifier.
//        };
        function paste_wrap(p,func,l){ // bind is slower than closure, https://stackoverflow.com/questions/17638305/why-is-bind-slower-than-a-closure, https://www.measurethat.net/Benchmarks/Show/1328/0/bind-vs-closure-declaration
          return function(){
            var str = func();
            return str!==''? p+str+l : '';
          };
        }
        parent.compile = function(){
          var pf = this.pref_fmt; // pref[this.mode_fmt].footer;
          var fmt = (!pref.test_mode['138'])? (pref0.footer[pf.design] || pf[pf.design] || '')
              // .split(/(\([^)]*?\))/).map(function(v,i){return i%2==0? ('('+v+')').replace('<',')(<').replace('>','>)(').replace(/\(\)/g,'') : v;}).join('') // add () to all
              : pref_func.sanitize(pref0.footer[pf.design] || pf[pf.design] || ''); // '' for safety
          var rx = /(ar|n[mrfio]|pg|rp|im|[lds]p|[cbp][tT]|pr|dn|bd|RP|IM|I[BbIi]|<\/?span[BIZ]*|<\/?[bius][BIZ]*)/; // x|y is different from y|x, see https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions
//          var elems = 'ar|n[mrfio]|pg|rp|im|[lds]p|[cbp][tT]|pr|dn|bd|RP|IM|I[BbIi]|<\/?[bius][BI]*|<\/?span'; // don't use capture (), following code assumes it to use split function
          var arr = fmt.split(/(\([^)]*?\))/).filter(function(v){return v;}).map(function(v){return v[0]==='('? v.slice(1,-1) : v;});
//          var arr = fmt.split(new RegExp('(\\(.*?(?:'+elems+').*?\\))')).filter(function(v){return v;});
//          var rx = new RegExp('('+elems+')');
          var obj = {};
          var len = 0;
//          var proto = {enumerable:true, configurable:true};
          for (var i=0;i<arr.length;i++) {
            var tmp = arr[i].split(rx);
            if (tmp.length>=3) {
              if (!pref.test_mode['138'] && arr[i].indexOf('<')!==-1) {
                for (var j=1;j<tmp.length;j+=2) if (tmp[j][0]==='<' && tmp[j].indexOf('>')===-1) while (tmp[j+1].indexOf('>')===-1) tmp[j+1] += tmp.splice(j+2,2).join('');
                for (var j=tmp.length-2;j>=-1;j-=2) {
                  if (j>=1 && tmp[j][0]==='<')
                    if ((tmp[j]+tmp[j+1]).search(/^<\/?(span[BIZ]*|[bius][BIZ]*)(?: +style="[^&"<>\']*")?>/)===0) continue; // perfection check for security
                    else tmp[j] = pref_func.sanitize(tmp[j]);
                  tmp[j+1] = pref_func.sanitize(tmp[j+1]);
                }
//                var count = tmp.filter(function(v,i){return i%2==1 && funcs[v] && funcs[v](pf)}).length;
              }
              var count_src   = tmp.filter(function(v,i){return i%2==1 && funcs[v]}).length;
              var count_funcs = tmp.filter(function(v,i){return i%2==1 && funcs[v] && funcs[v](pf)}).length;
              if (count_src>0 && count_funcs==0) continue;
              tmp = compile_rec(pf, tmp, 1, tmp.length, [tmp[0]]);
//              for (var j=2;j<tmp.length;j+=2) { // patch for the pattern '( /<iI> I: IM</iI>)', but 'I:' shows even at im===0.
//                var idx = tmp[j].indexOf('>');
//                if (idx!==-1 && idx!==tmp[j].length) {
//                  tmp.splice(j+1, 0, tmp[j].slice(idx+1), '');
//                  tmp[j] = tmp[j].slice(0,idx+1);
//                  j += 2;
//                }
//              }
//              for (var j=1;j<tmp.length;j+=2) { // working code, but can't handle '( /<iI> I: IM</iI>)' properly.
//                if (funcs_and_tags[tmp[j]]) {
//                  tmp[j] = funcs_and_tags[tmp[j]](pf);
//                  if (!tmp[j]) {tmp.splice(j,2); j-=2;} // !tmp[0] means parameters because head of tags always return functions. // delete following separator
//                } else {
//                  var tag_prev = j-2>=0 && (tmp[j-2][0]==='<' || tmp[j-2][0]==='I') || tmp[j-1].indexOf('>')!==-1;
//                  if (j==1 || count<=1 && !tag_prev) {tmp[j-1] += tmp.splice(j,2).join(''); j-=2;}
//                  else {tmp[j] = funcs.const(tmp[j]+tmp[j+1]); tmp[j+1] = '';} // can't consolidate straight 2 tags, like <iI><b>im</b></iI>
//                }
//              }
////              for (var j=1;j<tmp.length;j+=2) tmp[j] = funcs[tmp[j]](pf); // working code. without test_mode['138]', just simple 2 lines.
////              for (var j=tmp.length-3;j>=0;j-=2) if (!tmp[j+1]) tmp.splice(j,2); else if (j>0 && !tmp[j-1]) tmp[j]=''; // delete previous separator
              if (tmp[1]) Object.defineProperty(obj, len++, {get: paste_wrap_concat(tmp), enumerable:true, configurable:true}); // __proto__:proto});
//              if (tmp.length>=5) { // working code, but has a bug.
//                for (var j=1;j<tmp.length;j+=2) tmp[j] = funcs[tmp[j]](pf);
//                for (var j=tmp.length-3;j>=0;j-=2) if (!tmp[j-1] && !tmp[j+1]) tmp.splice(j,2); // access tmp[-1], but ok.
//                if (tmp.length>=3) Object.defineProperty(obj, len++, {get: funcs['concat'].bind(null, tmp), __proto__:proto});
//              } else if (tmp.length>=3) {
//                var func = funcs[tmp[1]](pf);
//                if (func && (tmp[0] || tmp[2])) func = funcs['wrap'].bind(null, tmp[0], func, tmp[2]);
//                if (func) Object.defineProperty(obj, len++, {get: func, __proto__:proto});
            } else if (arr[i]) obj[len++] = arr[i]; // Object.defineProperty(obj, len++, {value: arr[i], __proto__:proto});
          }
          obj.length = len;
          this.formatted_arr = obj;
          this.realtime = (pf.prate || pf.rptime || pf.rbtime || pf.rctime)? 2 : (pf.ptime || pf.btime || pf.ctime)? 1 : 0; // date part may be omitted.
//          this.realtime = pf.prate || pf.rptime || pf.rbtime || pf.rctime;
//          this.real_ts = 0;
          this.timestamp = (this.realtime? 32 : 0) + (this.mode==='thread' || pref.test_mode['131']? 16 : 0);
          this.pn_menu = (pf.use && pf.menu)? new Triage(pf.menu_str, {embed:true, child_class:'embeddedTriage'}).pn : null;
          this.pn_triage = (pf.use && pf.triage)? new Triage(pf.triage_str, {embed:true, child_class:'embeddedTriage'}).pn : null;
          return obj;

          function paste_wrap_concat(arr){
            return arr.length>=5? paste_concat(arr)
              : arr[0] || arr[2]? paste_wrap(arr[0], arr[1], arr[2])
              : arr[1];
          }
          function paste_concat(arr){ // for tags, delimiter belongs to previous func
            return function(){
              var str = '';
              for (var i=1;i<arr.length-3;i+=2) {
                var tmp = arr[i]();
                if (tmp!=='') str += tmp + arr[i+1];
              }
              str += arr[i++]();
              return str!==''? arr[0] + str + arr[i] : ''; // fixed. // can't return end tags when str==='', like (<u>ni/nf</u>)
            };
          }
//          concat: function(arr){
//            var str = arr[1]();
//            for (var i=2;i<arr.length-1;i+=2) {
//              var tmp = arr[i+1]();
//              if (tmp!=='') str += arr[i] + tmp;
//            }
//            return str!==''? arr[0] + str + arr[arr.length-1] : '';
//          },
          function paste_const(str){return function(){return str;};}
//          function paste_tag(tag_o0, tag_o1, tag_cl, arr){
//            return function(){
//              var str = (arr.length>2)? paste_concat(arr) : arr[0];
//              var tag_open;
//              return (!str)? '' : (tag_open = tag_o0())? tag_open + tag_o1 + str + tag_cl : str;
//            };
//          }
//          function compile_tag(arr, tag_close){
//            tag_close = ((tag_close.search(/[BI]$/)!==-1)? tag_close.slice(0,-1) : tag_close) + '>';
//            var idx;
//            return (typeof(arr[0])!=='string')? (idx=arr[1].indexOf('>'), paste_tag(arr[0], arr[1].slice(0,idx+1), tag_close, [arr[1].slice(idx+1)].concat(arr.slice(2))))
//              : (arr.length>=5)? (arr[arr.length-1] = arr[arr.length-1]+tag_close, paste_concat.bind(null, [arr[0]+arr[1]].concat(arr.slice(2))))
//              : paste_wrap(arr[0]+arr[1], arr[2], arr[3] + tag_close);
//          }
          function compile_rec(pf, tmp, j, end, result){
            while (j<end) {
              var res = funcs_and_tags[tmp[j]] && funcs_and_tags[tmp[j]](pf);
              var idx = tmp[j][0]==='<' && tmp.indexOf('</'+tmp[j].slice(1),j+1); // least start index is 2, so idx will never be 0.
              if (idx>0) {
                if (idx-4>=j) {
                  var stag = tmp[j] + tmp[j+1];
                  var etag = tmp[idx] + tmp[idx+1];
                  var k = 0;
                  while (tmp[j+2+k][0]==='<' && !funcs_and_tags[tmp[j+2+k]] && tmp[idx-k-2]==='</'+tmp[j+2+k].slice(1)) { // consolidate straight 2 tags, like <b><i>***</i><b>
                    k+=2;
                    stag += tmp[j+k] + tmp[j+1+k];
                    etag = tmp[idx-k] + tmp[idx+1-k] + etag;
                  }
                  var tag_arr = compile_rec(pf, tmp, j+2+k, idx-k, [res? '' : stag]);
                  if (!res) tag_arr[tag_arr.length-1] += etag; // close tag as a text
                  if (tag_arr[1]) {
                    var tag_func = paste_wrap_concat(tag_arr);
                    result.push(res? tagFuncs.condTag(stag, tag_func, etag) : tag_func, '');
                  }
                }
                j = idx;
              } else if (res) result.push(res, tmp[j+1]);
              else if (result.length==1 || tmp[j][0]!=='<') result[result.length-1] += tmp[j]+tmp[j+1];
              else result.push(paste_const(tmp[j]+tmp[j+1]), '');
//              if (funcs_and_tags[tmp[j]]) { // working code
//                var res = funcs_and_tags[tmp[j]](pf);
//                if (res) {
//                  if (result.length==1 && typeof(result[result.length-1])==='string') { // patch for (<span style="opacity:0.6">+dp</span>)
//                    var idx_l = result[result.length-1].lastIndexOf('>')+1;
//                    var str = (idx_l>0)? result[result.length-1].slice(idx_l) : '';
//                    if (str) {
//                      result[result.length-1] = result[result.length-1].slice(0,idx_l);
//                      res = paste_wrap(str, res, '');
//                    }
//                  }
//                  result.push(res, tmp[j+1]);
//                  var tag_close = tmp[j][0]==='<' && '</'+tmp[j].slice(1);
//                  if (tag_close) {
//                    var idx = tmp.indexOf(tag_close,j+1);
//                    if (idx>=0) {
//                      var arr_tag = (idx-4>=j)? result.splice(-2).concat(compile_rec(pf, tmp, j+2, idx, [])) : result.splice(-2);
//                      if (arr_tag.length>=4 || !(arr_tag.length===2 && idx-4>=j)) result.push(compile_tag(arr_tag, tag_close), '');
//                      j = idx;
//                    }
//                  }
//                }
//              } else {
//                if (typeof(result[result.length-1])==='string' && (result.length==1 || tmp[j][0]!=='<')) result[result.length-1] += tmp[j]+tmp[j+1];
//  //              if (typeof(result[result.length-1])==='string') result[result.length-1] += tmp[j]+tmp[j+1];
//  //              if (result.length===1) result[result.length-1] += tmp[j]+tmp[j+1];
//                else result.push(paste_const(tmp[j]+tmp[j+1]), '');
//  //              var tag_prev = j-2>=0 && (tmp[j-2][0]==='<' || tmp[j-2][0]==='I') || tmp[j-1].indexOf('>')!==-1;
//  //              if (result.length>0 && (j==1 || count<=1 && !tag_prev)) result[result.length-1] += tmp[j] + tmp[j+1];
//  //              else result.push(funcs['const'](tmp[j]+tmp[j+1]), ''); // can't consolidate straight 2 tags, like <iI><b>im</b></iI>
//              }
              j += 2;
            }
            return result;
          }
        }
        function refresh_start(){
          ts_refresh = Date.now();
//          ts_refresh_rx = null;
//          this.timestamp_set();
        }
        parent.refresh_start = refresh_start;
        Object.defineProperty(parent,'ts_refresh', {get:function(){return ts_refresh;}, enumerable:true, configurable:true});
//        parent.format_relative_time = format_relative_time;
//        parent.format_time = format_time;
        return function(tgt_th_in, lth_in){
//          if (!this.formatted_arr) this.formatted_arr = compile(this);
          tgt_th = tgt_th_in;
          lth = lth_in;
          nums2 = tgt_th[8];
          return Array.prototype.join.call(this.formatted_arr,'');
        };
      })(Footer);
//      Footer.compile();
      cataLog.Footer = Footer;


      var AutoUpdate = function(clg, indicator){
        this.timer = null;
        this.time_remains = 0;
        this.clg = clg;
        this.indicator = indicator;
        this.timer_tgt = this.timer_tgt.bind(this);
        this.countdown = this.countdown.bind(this);
      };
      AutoUpdate.prototype = {
        timer_tgt: function(from_manual, from_switch){
          this.timer=null;
          this.clg.refresh(true,null,!from_manual, from_switch);
          this.restart();
        },
        stop_if_running: function(){
          if (this.timer) {clearTimeout(this.timer); this.timer=null;}
          this.indicator.countdown('');
        },
        countdown: function(){
          var dec = this.time_remains>=(pref.proto.auto_update_shrink*60+60)? 60 : 1;
          this.indicator.countdown(dec>1? Math.floor(this.time_remains/60)+'m' : this.time_remains); // shows 0
          this.time_remains -= dec;
          if (this.time_remains<0) this.timer_tgt();
          else this.timer = setTimeout(this.countdown, dec*1000);
        },
        set: function(){
          this.stop_if_running();
          this.restart();
        },
        restart: function(){
          var pf = this.clg.pref[this.clg.mode];
          if (pf.auto_update) {
            var period = pf.auto_update_period>=1? pf.auto_update_period : [0.5, 0.25, 1/6][-pf.auto_update_period]||60;
            if (!pref.proto.auto_update_countdown) this.timer = setTimeout(this.timer_tgt,period*60000);
            else {
              this.time_remains = period*60;
              this.countdown();
            }
          }
        },
        manual_refresh: function(from_switch){
          if (this.timer) clearTimeout(this.timer);
          this.timer_tgt(true, from_switch);
        }
      };

      var oninput_funcs = {
        'filter.kwd.str': function(e){
          var clg = this['.'];
          var pf = clg.pref.filter.kwd;
          if (pf.use == !pf.str) {
            pf.use = !pf.use;
            clg.components.kwd_use.checked = pf.use; // pref_func.apply_prep(this['.'].components.kwd_use,false); // can't use apply_prep for false with pref.
          }
          var idx = clg.components.kwd_mirror.indexOf(e.target); // -1, 0, or 1
          if (idx!=-1) clg.components.kwd_mirror[idx?0:1].value = pf.str;
          clg.filter_kwd_prep(e);
        },
//        'filter.kwd.str': (function(){ // working code
//          var pn_use = pn_filter.getElementsByTagName('*')['filter.kwd.use'];
//          return function(e){
//            if (pref.filter.kwd.use == !pref.filter.kwd.str) {
//              pref.filter.kwd.use = !pref.filter.kwd.use;
//              pref_func.apply_prep(pn_use,false);
//            }
//            if (e && e.target.name.substr(-4,4)==='.str') pref3.filter.kwd.str(e);
//            filter_kwd_prep(e, pref.filter.kwd);
//          }
//        })(),
        'filter.list_str': function(){
          var clg = this['.'];
          clg.filter_changed();
          if (clg.pref[clg.mode].mark_new_posts) clg.view_time_filter_changed();},
        'filter.attr_list_str': function(){if (this['.'].pref.filter.attr_list) this['.'].view_attr_changed();},
        'filter.tag_search.str': liveTag.popup_filter.oninput_funcs['filter.tag_search.str'],
        __proto__: pref_func.settings.oninput_funcs.__proto__
      };
      var onchange_funcs = {
        'refresh': function(){this['.'].auto_update.manual_refresh();},
        '*.board_list_sel' : function(e, init, adaptive_refresh){
          var clg = this['.'];
          if (!init) {
            e.target.blur();
            if (pref.catalog.auto_save_filter) this.save(e, clg.INST.board_list_sel_old);
            if (pref.catalog.refresh.at_switch) clg.clear_threads(0);
//            clg.prep_reserved_tags(false,clg.INST.board_list_sel_old);
          }
//if (this===pClg) threads_delayed_pruning = Object.create(null); // TEMPORAL
          if (pref.catalog.auto_load_filter || init) this['load'](e,init);
          clg.liveTag.tags_reserved_init(clg, clg.pref[clg.mode].board_list_sel, init? null : clg.INST.board_list_sel_old);
          
//          if (!pref.test_mode['133'] && gClg.Clgs.length>=2) { // not required
//            for (var i=0;i<gClg.Clgs.length;i++) {
//              var tclg = gClg.Clgs[i];
//              if (clg.pref[clg.mode].board_list_sel===tclg.pref[tclg.mode].board_list_sel) {
//                for (var name in tclg.threads) clone_thread(clg, name);
//                adaptive_refresh = false;
//                break;
//              }
//            }
//          }
          if (!init || adaptive_refresh) {
            if (pref.catalog.refresh.at_switch) clg.auto_update.manual_refresh(true);
            else clg.refresh(false,null,false, true);
//            clg.refresh(pref.catalog.refresh.at_switch,null,false, true);
            if (pref.archive.IDB.auto_restore) {
              clg.format_refresh_tgts();
              for (var i in this.refresh_tgts) gClg.IDB_req[i] = null;
            }
          }
//          if (pref[embed_mode].auto_config_posts_search) {
//            pref.filter.kwd.post = false;
//            pref_func.apply_prep(pn12_0_4.getElementsByTagName('input')['filter.kwd.post'],true);
//          }
          clg.INST.board_list_sel_old = clg.pref[clg.mode].board_list_sel;
        },
//        'float.format.fc.*w/': function(e,pn_name){
//          var clg = this['.'];
//          if (!clg || clg.view!=='catalog') return;
//          var prop = pn_name.split('.').pop();
//          var pf = clg.pref.float.format.fc;
//          var w = (!pf.resize || pf.width==0)? null : pf.width + 'px';
//          var h = (!pf.resize || pf.height==0 ||  pf.max)? null : pf.height + 'px';
//          var mh= (!pf.resize || pf.height==0 || !pf.max)? null : pf.height + 'px';
//          for (var name in clg.threads) if (clg.threads[name][0]) {
//            clg.threads[name][0].style.width = w;
//            clg.threads[name][0].style.height = h;
//            clg.threads[name][0].style.maxHeight = mh;
//          }
//        },
//        'catalog_size_width'  : catalog_resized,
//        'catalog_size_height' : catalog_resized,
//        'catalog_size_text_width'  : catalog_resized,
//        'catalog_size_text_height' : catalog_resized,
        'catalog.order.*': 'float.order.*', // function(){pClg.idx_re_sort();},
        'float.order.*': function(){this['.'].idx_re_sort();},
        '*.hide_unwatched': 'float.order.*',
        '*.auto_update' : function(){this['.'].auto_update.set()},
        '*.auto_update_period' : '*.auto_update',
        'Drop now': function(){this['.'].show_catalog(-1);},
        'clear_threads'       : function(){this['.'].clear_threads(0); this['.'].show_catalog();},
        'clear_filters': function(e){this['load'](e, null, true);},
        'load': function(e, init, clearOnly){
          var initOnly = init && !pref.catalog.auto_load_filter;
          var clg = this['.'];
          var pf = clg.pref.filter;
          if ((localStorage || clearOnly) && !initOnly) {
            var blank = pref_default().filter;
            delete blank['tag']; // keep filter.tag because it may be changed to getter/setter for support multiple liveTag.
            if (pref.common.consolidated_watch_list) delete blank['watch_list_str'];
            pref_func.pref_overwrite(pf, blank);
            if (clearOnly) {
              pf.tag = false;
              if (!clg.liveTag.NC) for (var i=0;i<gClg.Clgs.length;i++) if (!gClg.Clgs[i].liveTag.NC) gClg.Clgs[i].components.tag_use.checked = false;
            }
//            pref_func.pref_overwrite(pf,pref_default().filter);
          }
          if (localStorage && !clearOnly && !initOnly) {
            try { 
              var loaded = JSON.parse(localStorage.getItem(this.load_save_key(clg.pref[clg.mode].board_list_sel)) ||
                                      localStorage.getItem(this.load_save_key(clg.pref[clg.mode].board_list_sel).replace(/\./,'.catalog.'))) || {}; // patch from 2020.10
              delete loaded['tag'];
              if (pref.common.consolidated_watch_list) {
                if (init) pf.watch_list_str = localStorage.getItem(pref.script_prefix + '.WatchList') || '';
                if (loaded['watch_list_str']) pf.watch_list_str = loaded['watch_list_str'] + '\n' + pf.watch_list_str; // the latter has the priority.
                delete loaded['watch_list_str'];
              }
              pref_func.pref_overwrite(pf, loaded, true);
//              pref_func.pref_overwrite(pf,JSON.parse(localStorage.getItem(this.load_save_key(clg.pref[clg.mode].board_list_sel))),true);
            } catch (e){
              console.log(e);
            }
          }
          if (init && pref[clg.mode].disable_kwd_filter_at_initial) clg.pref.filter.kwd.use = false;
          pref.filter.time.quick_sel = 0;
          pref_func.apply_prep(clg.components.pn_filter,false, null, null, true, clg.pref, init); // also make obj.
//          pref_func.apply_prep(pn_filter,true); // make obj2.
          clg.filter_kwd_prep_init(null, init); // call filter_changed in this.
          clg.liveTag.filter_onchange(pf.tag_search, true, true);
          if (!init) clg.view_attr_changed(); // 'if (!init)' is a patch
        },
//        'save_onleave'        : function(){if (pref.catalog.auto_save_filter) onchange_funcs.save();window.removeEventListener('beforeunload', onchange_funcs.save_onleave, false);},
//        'save'                : function(){if (localStorage) localStorage.setItem(onchange_funcs.load_save_key(pref.catalog_board_list_sel),JSON.stringify(pref.filter));},
        'save': function(e, sel){
          if (localStorage) {
            var clg = this['.'];
            if (typeof(sel)!=='number') sel = clg.pref[clg.mode].board_list_sel;
            var pf = clg.pref.filter;
            var dst = pref_default(true).filter;
            if (!pf.time.use) delete dst.time.time_val;
            if (pref.common.consolidated_watch_list) delete dst.watch_list_str;
            delete dst.tag;
            var save_obj = pref_func.obj_elim_the_same(dst, pf);
//            var save_str = JSON.stringify( // working code
//              pref_func.obj_elim_the_same(pref_default(true).filter, pf), function(key,value){
//                return key==='time_val' && !pf.time.use || // (!pf.time && !pf.time_watch && !pf.time_watch_creation && !pf.time_creation) ||
//                       key==='watch_list_str' && pref.common.consolidated_watch_list || key==='tag'? undefined : value;});
            var key = this.load_save_key(sel);
            if (key) {
              if (!save_obj) delete localStorage[key];
              else localStorage.setItem(key, JSON.stringify(save_obj));
              if (key.indexOf('CatChan.filter.')===0) delete localStorage[key.replace(/\./,'.catalog.')]; // patch from 2020.10
            }
            if (pref.common.consolidated_watch_list) this['save_watch_list']();
          }
        },
        'save_watch_list': function(){
          if (localStorage) {
            var clg = this['.'];
            var pf = clg.pref.filter;
            var key_wl = pref.script_prefix + '.WatchList';
            if (pf.watch_list_str==='') delete localStorage[key_wl];
            else localStorage.setItem(key_wl, pf.watch_list_str);
          }
        },
//        'load_default': function(){
//          var clg = this['.'];
////          var pref_def = pref_default();
//          pref_func.pref_overwrite(clg.pref.filter,pref_default().filter);
////          pref.filter.time_str = new Date().toLocaleString();
//          pref_func.apply_prep(clg.components.pn_filter,false, null, null, true);
////          pref_func.apply_prep(pn12_0_4,true); // obj init.
//          clg.filter_kwd_prep(null);
//          clg.view_attr_changed();
//        },
        'load_save_key': function(num){
          var row_obj = pref.catalog_board_list_obj[num];
          return (row_obj)? pref.script_prefix + (pref.common.consolidated_filter? '.Filter':'.filter.' + row_obj[0].key) : null;},
        'filter.kwd.*'   : function(e){this['.'].filter_kwd_active(e);}, // use, op, sub, name, file, com, trip, meta, id, id2, post, inPost
        'filter.kwd.re'    : function(){this['.'].filter_kwd_prep();},
        'filter.kwd.match' : 'filter.kwd.re',
        'filter.kwd.ci'    : 'filter.kwd.re',
        'filter.kwd.sentence': 'filter.kwd.re',
        'filter.kwd.ew'    : 'filter.kwd.re',
//        'filter.kwd.post'  : function(e, init){ // working code
//          var clg = this['.'];
//          if (e.target.checked && this.mode!=='thread') {
//            if (pref[cataLog.embed_mode].searchAs) this.scanBoard(e, true, true);
//            if (pref[cataLog.embed_mode].searchAsA) this.scanSite(e, true, true);
//          }
////          if (pref[embed_mode].auto_config_posts_search) {
////            if (e.target.checked) {
////              pref[embed_mode].t2h_sel = 'ALL_agg';
////              if (embed_mode==='catalog') pref[embed_mode].popup2 = 'sr';
////              onchange_funcs.scanBoard();
////            } else {
////              var pref_def = pref_default();
////              pref[embed_mode].t2h_sel = pref_def[embed_mode].t2h_sel;
////              if (embed_mode==='catalog') pref[embed_mode].popup2 = pref_def[embed_mode].popup2;
////            }
////            pref_func.apply_prep(pref_func.mirror_targets.pn13_1,false);
////          }
//        },
        'filter.tag': function(){
          var clg = this['.'];
          if (!clg.liveTag.NC) {
            var clgs = gClg.Clgs;
            var val = clg.components.tag_use.checked;
            for (var i=0;i<clgs.length;i++) if (clg!==clgs[i] && !clgs[i].liveTag.NC) clgs[i].components.tag_use.checked = val;
          }
          clg.liveTag.filter_changed_broadcast();
        },
//        'filter.tag'       : catalog_filter_changed,
//        'filter.tag_list'  : catalog_filter_changed,
        'filter.time.*W/': function(e,[,,src]){
          var pf = this['.'].pref.filter.time;
          if (src==='quick_sel') {
            pf.time_val = Date.now() - e.target.value*3600000;
            this['.'].components.time_str.value = pf.time_str;
            e.target.selectedIndex = 0;
          }
          var pf_h_old = pf.h;
          pf.h = !pf.use || pf.func[0]==='n' || !pf.time_val? 0 : pf.func[0]==='p'? 1 : 2;
          pf.w = !pf.use || pf.func[1]==='n' || !pf.time_val? 0 : pf.func[1]==='p'? 1 : 2;
          if (pf.h!==pf_h_old || pf.h && (src==='time_str'||src==='quick_sel')) this['filter.list_str']();
        },
//        'filter.time'      : oninput_funcs['filter.list_str'],
//        'filter.time_creation' : 'filter.time',
//        'filter.time_str'  : 'filter.list',
        'filter.list'      : oninput_funcs['filter.list_str'],
        'filter.watch_list_str': function(e, from_storage){
          if (pref.common.consolidated_watch_list && !from_storage) {
            gClg.Sync_watch_load({newValue:this['.'].pref.filter.watch_list_str}, true); // broadcast
            this['save_watch_list']();
          } else this['filter.list_str']();
        },
//        'filter.watch_list_str' : 'filter.time',
        'filter.attr_list' : function(){this['.'].view_attr_changed();},
//        'scan'                     : scan_tags,
        'scanBoardIf': function(e){this.scanBoard(e,false,true);},
        'scanSiteIf': function(e){this.scanSite(e,false,true);},
        'scanBoard' : function(e, from_auto, required_only){if (!from_auto && pref.common.clear_at_manual_scan) this.clear_threads();scan.keyword_load_1(site.nickname,required_only? 2:true,[site.board], health_indicator);},

        'scanSite' : function(e, from_auto, required_only){if (!from_auto && pref.common.clear_at_manual_scan) this.clear_threads();scan.keyword_load(required_only? 2:true, health_indicator);},
//        'filter.tag_ci'     : function(){scan_tags_init(scan_tags_common_c(site3[site.nickname].tags,'')[0], null);},
//        'filter.time.quick_sel': function(e){ // working code
//          var pn = this['.'].components.time_str;
//          pn.value = new Date(Date.now() - e.target.value*3600000).toLocaleString();
//          pref_func.apply_prep(pn,this['.'].pref,true);
//          e.target.selectedIndex = 0;
//        },
        'filter.tag_search.re': 'filter.tag_search.str',
        'filter.tag_search.showActives': liveTag.popup_filter.onchange_funcs['filter.tag_search.showActives'],
        'filter.tag_search.show_nof_boards': liveTag.popup_filter.onchange_funcs['filter.tag_search.show_nof_boards'],
        'tagB.*': liveTag.cbx_all_onchange,
        'tag2bList': liveTag.add_tags_to_boardlist,
        'add_to_auto': function(e){
          var clg = this['.'];
          var pf = clg.pref.filter.auto_list;
          if (!clg.pref.filter.kwd.str && !pf.dtrm_str) return;
          var cmds = [pf.kill? 'KILL':null, pf.watch? 'WATCH':null, pf.show? 'SHOW':null, pf.sticky? 'STICKY':null, pf.style? 'ATTR:'+pf.style_str:null].filter(function(v){return v;});
          var cmds_str = cmds.join();
          var obj = clg.pref.filter.kwd.str? {cmds:cmds_str, __proto__:clg.pref.filter.kwd} : {cmds:cmds_str, use:true, str:'*', __proto__:pref_default().filter.kwd};
          var str = (pf.dtrm_str? pf.dtrm_str+':':'') + pref_func.str2obj7.o2s(obj);
          clg.pref.filter.auto_list.str += str+'\n';
          var obj7_old = pf.obj7;
          pref_func.apply_prep(clg.components.auto_list,false, null, null, true, clg.pref);
          for (var name in clg.threads) {
            if (!clg.filter_test(name, clg.threads[name])) continue;
            if (pf.dtrm_str && (pref_func.merge_obj5a({key:name},pf.obj7,null)||[]).length === (pref_func.merge_obj5a({key:name},obj7_old,null)||[]).length) continue;
            for (var j=0;j<cmds.length;j++) {
              var idx = cmds[j].indexOf(':');
              clg.triage_exe_broadcast(name, idx===-1? cmds[j] : cmds[j].slice(0,idx), idx===-1? '' : cmds[j].slice(idx+1));
            }
          }
        },
        'filter.*.style': function(e){
          this['filter.*.style_str']({target:e.target.parentNode.nextSibling});
        },
        'filter.*.style_str': function(e){
          var str = e.target.value;
          if (str[0]!=='.') e.target.nextSibling.setAttribute('style', str||'');
        },
        'filter.postFilter.add': function(e){
          var clg = this['.'];
          var pf = clg.pref.filter.postFilter;
          if (clg.pref.filter.kwd.str==='' || (!pf.hide && !pf.style)) return;
          var boards = pf.boards_str.replace(/\s/g,'').split(',');
          var str = pref_func.str2obj7.o2s({hide:pf.hide, boards:pf.boards && boards.length>0? boards : null, style:pf.style && pf.style_str, __proto__:clg.pref.filter.kwd}, true);
          clg.pref.filter.postFilter.str += str+'\n';
          pref_func.apply_prep(clg.components.postFilter,false, null, null, true); // , clg.pref);
          if (pf.use) this['filter.postFilter.REDRAW'](null, pref_func.str2obj7.s2o({}, str, 'filter.postFilter.str'));
        },
        'filter.postFilter.str w/': function(e, name){
          var clg = this['.'];
          if ((pref.postFilter.use ^ name==='postFilter.use') || (clg.pref.filter.postFilter.use ^ name==='filter.postFilter.use'))
            this['filter.postFilter.REDRAW'](clg.pref.filter.postFilter.obj7_old||clg.pref.filter.postFilter.obj7||{}, null, pref.postFilter.obj7_old||pref.postFilter.obj7); // remove
          clg.pref.filter.postFilter.obj7_old = null;
          if (pref.postFilter.use || clg.pref.filter.postFilter.use) this['filter.postFilter.REDRAW']();
        },
        'filter.postFilter.use w/': 'filter.postFilter.str w/',
        'filter.postFilter.REDRAW': function(obj_remove, obj_added, obj_remove_global){
          var clg = this['.'];
          for (var name in clg.threads) format_html.postFilter(clg, clg.threads[name][16], clg.threads[name][16].posts, obj_remove, obj_added, obj_remove_global);
        },
//        'postFilter.use': function(e, obj_added){
//          var clg = this['.'];
//          for (var name in clg.threads) format_html.postFilter(clg.threads[name][16], clg.threads[name][16].posts, !pref.postFilter.use && pref.postFilter.obj7, obj_added);
//        },
        'filter.disLWKA': function(){if (this['.'].pref.filter.kwd.active) this['.'].filter_changed();},
        'filter.disTWKA': 'filter.disLWKA',
        'INST.view': function(e){
          var clg = this['.'];
          clg.func_hide_all(clg.threads, true);
//          for (var i in clg.threads) clg.remake_html_prep(clg.threads[i]);
////          for (var i in clg.threads) {
////            var tgt_th = clg.threads[i];
////            tgt_th[0] = false;
////            tgt_th[16].th = {pn:null, __proto__:tgt_th[16].lth.th};
////            tgt_th[16].posts = null;
////            if (tgt_th[16].icon_sticky) delete tgt_th[16].icon_sticky;
////            if (tgt_th[16].icon_showAlways) delete tgt_th[16].icon_showAlways;
////          }
////          var idx = e.target.selectedIndex;
          clg.view = clg.set_view_class(-1, clg.pref.INST.view);
//          clg.view = clg.pref[clg.mode].view;
//          clg.view = clg.merge_bases.view = clg.pref[clg.mode].view;
          if (clg.native.viewChanged) clg.native.viewChanged();
//          clg.drawn_idx = 0;
          clg.footer.view_changed();
          var footer_sel = ['headline','catalog','page','float'].indexOf(clg.view);
          clg.pref.settings.footer_sel = footer_sel===-1? 0 : footer_sel;
          clg.show_catalog(0);
        },
        'INST.page.subStyle2/O': 'INST.*.subStyle/O',
        'INST.page.thumb.size/O': 'INST.*.subStyle/O',
        'INST.catalog.op/O': 'INST.*.subStyle/O',
        'INST.catalog.thumb.size/O': 'INST.*.subStyle/O',
        'INST.*.subStyle2/O': 'INST.*.subStyle/O',
        'INST.*.subStyle/O': function(e,src,old_val){
          var clg = this['.'];
          if (src.split('.')[1]!==clg.view) return;
          var src2 = src.split('.')[2];
          clg.set_view_class(src2==='op' || src2==='subStyle2'? 2:1, old_val);
          if (src.split('.').slice(-1)[0]==='size') clg.image_resize_all(e.target.value);
          clg.show_catalog(0);
        },
        'liveTag.NC': function(){ // test
          var clg = this['.'];
          var lt = clg.liveTag;
          if (lt===liveTag) return;
          if (lt.subscribed) lt.unsubscribe(true);
          clg.Setup_liveTag(clg.pref.liveTag.NC, true);
          clg.footer.update_all_force(null, 'float', null, true);
          clg.filter_changed();
        },
        'clear_pruneds': function(e){this['.'].rm_items_404_clearPRUNEDs();},
        sticky_filter_settings: function(e){
          var sticky = e.target.textContent==='[\u25e5]';
          e.target.textContent = (sticky)? '[\u25e3]' : '[\u25e5]';
          var pn = e.target.parentNode.parentNode;
          var ppn = pn.parentNode;
          if (sticky) ppn.previousSibling.appendChild(ppn.replaceChild(document.createElement('div'),pn)); // place dummy to keep DOM structure
          else ppn.nextSibling.replaceChild(pn,ppn.nextSibling.firstChild);
//          pn.parentNode.style.overflow = sticky? 'hidden' : 'auto';
//          pn.nextSibling.style.overflow = sticky? 'auto' : '';
//          pn.nextSibling.style.height = sticky? 'inherit' : '';
        },
        'SHOW3.*W/': function(e, [func,tgt]){ // and also 'HIDE3.*W/'
          var clg = this['.'];
          clg.pref[clg.mode].appearance.expand[tgt] = (func==='SHOW3');
//          if (clg.pref[clg.mode]===pref[clg.mode]) pref_func.apply_prep_save(null,true); // moved to set_val_beforeunload
          this[func](e);
        },
        __proto__: oninput_funcs,
      };
      onchange_funcs['HIDE3.*W/'] = onchange_funcs['SHOW3.*W/'];
////      onchange_funcs.entry_func = (function(myself){ // working code.
////        return function(e){
////          pref_func.apply_prep(this,true);
////          var this_name = this.getAttribute('name'); // for <span>
////          if (myself[this_name]) myself[this_name].call(this,e);
////          else if (this_name.indexOf('.')!=-1 && this_name.substr(0,this_name.indexOf('.'))===embed_mode) {
////            var name = this_name.substr(this_name.indexOf('.')+1);
////            if (myself[name]) myself[name].call(this,e);
////          }
////        }
////      })(onchange_funcs);
      pref_func.add_onchange_format(oninput_funcs);
      pref_func.add_onchange_format(onchange_funcs);
      comf.Object_defProps(onchange_funcs, site2['DEFAULT'].update_posts_merge_bases.onchange_funcs);


      cataLog.embed_mode = embed_mode;
      if (brwsr.sw_cache && pref.info_client) brwsr.sw_cache.subscribe(true);
      if (!pref.test_mode['114']) {
        styleSheet.register_cc('headline','white-space:nowrap;text-overflow:ellipsis;overflow:hidden;width:100%');
        styleSheet.register_ccAll('.headlineView1 .headline','white-space:normal;border-bottom:1px solid lightgray');
        var headline_css = site2[site.nickname].parse_funcs['headline_html'].css;
        if (headline_css) styleSheet.register_cc('headline '+headline_css);
      }

var Clg = function(embed_embed, embed_mode, watcher){
  this.serialNo = Clg.prototype.serialNo++;
  this.mode = embed_mode;
  this.view = embed_mode==='float'? pref.float.view : /*embed_mode==='thread'? 'page' :*/ site.view || embed_mode;
  this.show_catalog_cont = new DelayBuffer(this.show_catalog_delayed.bind(this), 200).get_bound_func();
  this.show_catalog_resize = this.show_catalog_resize.bind(this);
  this.show_catalog_scroll = this.show_catalog_scroll.bind(this);
  this.show_catalog_buf = this.Show_catalog_buf(this);

  var clgs = Clg.prototype.Clgs
  var pf_original = clgs.length==0 || clgs.length==1 && clgs[0].mode!=='float';
  this.pref = clgs.length==0? pref : {
    filter: pref_func.pref_overwrite(pref_default().filter, pref.filter), // copy objects also.
//    filter:comf.deep_copy(pref.filter), // copy objects also. // SHOULD CHANGE TO USE pref_overwrite by gathering functions which make obj here. <-- is this true???
    liveTag:{NC:pref.liveTag.NC},
    catalog: {__proto__:pref.catalog}, // for access via this.view
    page: {__proto__:pref.page},
    thread: {__proto__:pref.thread},
    float: pf_original? pref.float : {__proto__:pref.float},
    headline: {__proto__:pref.headline}
  };
  this.order = pf_original? (this.mode==='float'? pref.float.order : pref.catalog.order) : comf.deep_copy(pref.float.order);
////  if (clgs.length==0) Object.defineProperty(this,'pref',{get:function(){return pref;}, enumerable:true, configurable:true}); // working code
////  else this.pref = {filter:comf.deep_copy2({}, pref.filter), liveTag:{NC:pref.liveTag.NC}}; // copy objects also. // SHOULD CHANGE TO USE pref_overwrite by gathering functions which make obj here.
////  else this.pref = {catalog:{filter:pref_default(true).filter}, liveTag:{NC:pref.liveTag.NC}};
//  if (pf_original) { // working code
//    Object.defineProperty(this,'order',{get:this.mode==='float'? function(){return pref.float.order;} : function(){return pref.catalog.order;}, enumerable:true, configurable:true});
//    if (clgs.length!=0) Object.defineProperty(this.pref, this.mode, {get: this.mode==='float'? function(){return pref.float;} : function(){return pref.catalog;}, configurable:true, enumerable:true});
////    if (clgs.length!=0) this.pref.float = this.mode==='float'? {get order(){return pref.float.order;}} : {get order(){return pref.catalog.order;}};
//  } else {
//    this.pref.float = pref_func.pref_overwrite(pref_default().float, pref.float, true);
//    this.order = this.pref.float.order;
////    this.pref.float = comf.deep_copy({}, pref.float);
////    this.pref.float.order = this.order;
////    this.pref.float = {order:this.order, view:pref.float.view, format:pref.float.format)}; // temporary
//  }
  this.pref[this.mode].board_list_sel = pref[this.mode].save_board_list_sel? pref[this.mode].board_list_sel : pref.catalog_board_list_sel;
  this.name = 'FC'+this.serialNo;
//  function prep_subStyle(proto){ // working code
//    return {ss:  proto.subStyle,  get subStyle(){return this.ss;},   set subStyle(v){this.ss = v; this.__proto__.subStyle = v;},
//            ss2: proto.subStyle2, get subStyle2(){return this.ss2;}, set subStyle2(v){this.ss2 = v; this.__proto__.subStyle2 = v;}, __proto__:proto}; // temporal
//  }
  var pref_headline_format = this.mode==='float'? pref.float.headline : pref.headline.format;
  this.pref.INST = pf_original? {view:this.view,
                                 headline: pref_headline_format,
                                 get safety(){return pref[embed_mode].safety;},
                                 get catalog(){return pref.catalog.format;},
                                 get page(){return pref.page.format;}}
                              : {view:this.view,
                                 headline: {subStyle: pref_headline_format.sybStyle, thumb:{__proto__:pref.headline.format.thumb}}, // access to thumb is needed in 'image_resize'
                                 safety:comf.deep_copy(pref[this.mode].safety),
                                 catalog: {op: pref.catalog.format.op, thumb:{size: pref.catalog.format.thumb.size, __proto__:pref.catalog.format.thumb}},
                                 page: {subStyle2:0, thumb:{size: pref.page.format.thumb.size, __proto__:pref.page.format.thumb}}};
//  this.pref.INST = {view:this.view, headline:prep_subStyle(this.mode==='float'? pref.float.headline : pref.headline),
//                    get catalog(){return pref.catalog.format;},
//                    page:{}};
//  Object.defineProperty(this.pref.INST, 'safety', pf_original? {get:function(){return pref[embed_mode].safety;}}
//                                                             : {value:comf.deep_copy2(pref[this.mode].safety), writable:true});
////  var subStyle = {ss: pref['headline'].subStyle, get subStyle(){return this.ss;}, set subStyle(v){this.ss = v; pref['headline'].subStyle = v;}}; // temporal
////  this.pref.INST = pf_original? {view:this.view, get safety(){return pref[embed_mode].safety;}, headline:prep_subStyle(pref.headline), catalog:prep_subStyle(pref.catalog), page:{}}
////                              : {view:this.view, safety: comf.deep_copy2(pref[this.mode].safety), headline:prep_subStyle(pref.headline), catalog:prep_subStyle(pref.catalog), page:{}};

      var pfai = pref.catalog.appearance.initial;
      var pn12 = cnst.init3({
        func_str:embed_embed?'etb:embed':'left:'+pfai.left+'px:top:'+pfai.top+'px:tb'+(pfai.width?':width:'+pfai.width+'px':'')+(pfai.height?':height:'+pfai.height+'px':'')+':overflow:auto:Show:bottom_top:tb_press:'+pfai.state,
//        func_str:'left:'+pfai.left+'px:top:'+pfai.top+'px:resize:both:'+((embed_embed)? 'display:none:tb:embed':'tb')+':width:'+pfai.width+'px:height:'+pfai.height+'px:resize:both:overflow:auto'+
//          ':Show'+(embed_embed?'':':bottom_top')+((embed_mode==='float')? ':tb_press:'+pfai.state:''),
        onrolldown: this.onrolldown,
        onrollup: this.onrollup,
        exit: (pref.test_mode['114'])? show_hide : this.destroy.bind(this),
//        exit: (!watcher)? show_hide : this.destroy.bind(this),
        maximize: this.show_catalog_resize,
        get auto_roll(){return pref.catalog_auto_rollup_when_moving;},
        autoHeight: function(e){
//          var height = e.target.dataset.height || 'auto';
          var style = e.target.parentNode.parentNode.nextSibling.style;
          var height = (!style.height || style.height==='auto') && e.target.dataset.height || 'auto';
          e.target.dataset.height = style.height;
          style.height = height;
          if (height==='auto') this.this_obj.permit_draw_on_demand = false;
          else delete this.this_obj.permit_draw_on_demand;
          this.this_obj.show_catalog(0); // before binding this['.'], see initialization.
        },
        settings_after: function(){
          if (this.this_obj.mode!=='float') return;
          var comp = this.this_obj.components;
          comp.sticky_filter_settings.style.display = (comp.filter.style.display==='none' && comp.settings.style.display==='none')? 'none' : null;
        },
        this_obj:this, __proto__:cnst.tb_funcs}).pn;
//      var pn12_whole = cnst.init('left:0px:tile:get:bottom:resize:both:Show:tb:width:'+pref.catalog.appearance.initial.width+'px:height:'+pref.catalog.appearance.initial.height+'px:resize:both:overflow:auto',
//        function(){pn12_0_4.style.display='';},function(){pn12_0_4.style.display='none';},show_hide,show_catalog_cont);
//      var pn12 = pn12_whole[0];
//      if (embed_embed) pn12.style.display = 'none';
//      var pn12_rollup_func = pn12_whole[1];

//// test for prototype base coding.
////      var pn12_whole = new Cnst2('left:0px:tile:get:bottom:resize:both:Show:tb:width:400px:height:400px:resize:both:overflow:auto',
////        {rolldowm:function(){pn12_0_4.style.display='';}, rollup:function(){pn12_0_4.style.display='none';}, exit:show_hide, maximize:show_catalog_cont});
////      var pn12 = pn12_whole.pn;
////      var pn12_rollup_func = pn12_whole.funcs.rollup;

//      cnst.bottom_top(pn12);
      pn12.classList.add(pref.cpfx+'FC');
      pn12.id = pref.cpfx+this.name;
      var pn12_0 = pn12.childNodes[0];
      var pn12_1 = pn12.childNodes[1].appendChild(document.createElement('div'));
//      var pn12_1 = pn12.childNodes[1];
//      pn12_1.id = 'catalog_debug'; // working code.
//      var autorollup_state = false;
//      function auto_hide_catalog() {
//        if (pref.catalog_auto_rollup_when_moving) {
//          if (pn12_1.style.display!='none') {
//            pn12_rollup_func();
//            autorollup_state = true;
//          } else if (autorollup_state) {
//            pn12_rollup_func();
//            autorollup_state = false;
//          }
//        }
//      }
//      pn12.addEventListener('dragstart', auto_hide_catalog, false);
//      pn12.addEventListener('dragend'  , auto_hide_catalog, false);
      if (this.mode==='float') pn12_0.childNodes[1].insertBefore(cnst.dom('<button type="button" name="autoHeight">\u25ca</button>'),pn12_0.childNodes[1].firstChild);
  
      var pf_at = this.pref[this.mode].appearance.titleBar;
      var pn12_0_2 = cnst.add_to_tb(pn12,
//        '<label name="filter"><input type="checkbox" name="filter.show"> Filter </label>' +
//        '<label name="settings"><input type="checkbox" name="catalog_show_setting"> Settings</label>'+
        '<button type="button" name="filter"'+(pf_at.filter?'':' style="display:none"')+'>'+ ((site2[site.nickname].CONTENT_SECURITY_POLICY_DATAURI)? 'F' : cnst.icons.img(cnst.icons.filter)) +'</button>' +
        '<button type="button" name="settings"'+(pf_at.settings?'':' style="display:none"')+'>'+ ((site2[site.nickname].CONTENT_SECURITY_POLICY_DATAURI)? 'S' : cnst.icons.img(cnst.icons.settings)) +'</button>'+
//        '<button name="refresh">Refresh</button>'+
//        '<button name="refresh">\u27f3</button>'+
        '<button type="button" name="refresh"'+(pf_at.refresh?'':' style="display:none"')+'>'+ ((site2[site.nickname].CONTENT_SECURITY_POLICY_DATAURI)? 'R' : cnst.icons.img(cnst.icons.refresh)) +'</button>'+
//        '<span name="num_of_pages">'+
//          '<span name="hide_at_embed"> up to <input type="text" name="catalog_max_page" size="2" style="text-align: right;">pages in </span></span>'+
        '<span name="boards_selector"'+(pf_at.boards_selector?'':' style="display:none"')+'><select name="'+this.mode+'.board_list_sel"></select></span>');
      var board_sel = pn12_0_2.getElementsByTagName('select')[this.mode+'.board_list_sel'];
      this.pref[this.mode].board_list_sel = cnst.auto_shrink_board_selector.setup(board_sel, this.pref[this.mode].board_list_sel, function(idx){this.pref[this.mode].board_list_sel=idx;}.bind(this));
////      pref_func.apply_prep(pn12_0_2,false);
//      var pn12_0_2_children = ['filter','settings','refresh','num_of_pages','boards_selector'];
//      for (var i=0;i<pn12_0_2_children.length;i++)
//        if (!pref.catalog.appearance.titleBar[pn12_0_2_children[i]]) pn12_0_2.getElementsByTagName('*')[pn12_0_2_children[i]].style.display = 'none';
////      pn12_1.style.width = pn12_0.offsetWidth + 'px';

  this.health_indicator = (!watcher)? health_indicator : new healthIndicator.HealthIndicator();
      pn12_0.childNodes[3].appendChild(this.health_indicator.pn);
  if (!watcher && IDB) {
      pn12_0.childNodes[3].appendChild(cnst.dom('<span> </span>')); // spacer
      var IDB_indicator = new healthIndicator.HealthIndicator();
      IDB.set_indicator(IDB_indicator);
      pn12_0.childNodes[3].appendChild(IDB_indicator.pn);
  }
      var pn12_0_4 = cnst.dom('<div style="margin:0px 3px;clear:both'+(embed_mode==='float'?';position:relative" class="'+pref.cpfx+'window':'')+'"></div>');
//      pn12_0.appendChild(pn12_0_4);
      var pf_order = (embed_mode==='float'? 'float':'catalog')+'.order.';
      var pf_ex = this.pref[this.mode].appearance.expand;
      var html_funcs = pref_func.settings.html_funcs;
      pn12_0_4.innerHTML = pref_func.format_html_str(
        '<div style="float:right;display:none">'+
          '<div style="float:right">'+
            html_funcs.updates(this.mode, html_funcs.rollup(
//            '<input type="checkbox" name="catalog_snoop_refresh" checked> Snoop update<br>'+
//            '&emsp;<IC"catalog_promiscuous">Promiscuous<br>'+
//            '<select name="'+embed_mode+'.view"><option>Headline</option><option>Catalog</option><option>Page</option></select> :View<br>'+
              '1,<IC"INST.safety.hide">Auto drop threads temporarily for light feeling <BTN"Drop now"><br>'+
              '1,<IC"INST.safety.remove">Auto remove threads latter than <ITBN5"INST.safety.max">th<br>'+
              '<BTN"clear_threads,Clear all threads">','',true))+
              '<span class="'+pref.cpfx+'viewSel"'+(site.hasViewSel?'':' style="display:none"')+'><SEC"INST.view,headline,catalog,page">'+
              '<span class="'+pref.cpfx+'headlineOnly"><SE"INST.headline.subStyle,SingleLine,MultiLines"></span>'+
              '<span class="'+pref.cpfx+'catalogOnly"><SEC"INST.catalog.thumb.size,small,large,custom,custom2"><SE"INST.catalog.op,off:w/o OP,on:w/ OP"></span>'+
              '<span class="'+pref.cpfx+'pageOnly"><SEC"INST.page.thumb.size,small,large,custom,custom2"><SE"INST.page.subStyle2,normal,narrow,inline,inline2"></span><br></span>'+
//            html_funcs.design_of_fc('INST', 1)+'<br name="PLACEHOLDER_NATIVE_SIZE">'+
            (this.mode==='float'? '<IC"liveTag.NC">Separated virtual board<br>' : '')+
            '<IC"'+this.mode+'.merge">Merge all threads<br>'+
            '<IC"'+this.mode+'.merge_list">Merge threads by list<br>'+
          '</div>'+
          '<div style="float:right">'+
//            '<div name="hide_at_embed">'+
            '<SE"'+pf_order+'ordering,Bump order,Creation date,Reply count,Image count,Last reply,Fast,/Bump order,/Creation date,/Reply count,/Image count,/Last reply,Slow"><br>'+
//              'size '+
//              '<span name="catalog.text_mode.mode.graphic">'+
//                '<ITB4"catalog_size_width"> x '+
//                '<ITB4"catalog_size_height"></span>'+
//              '<span name="catalog.text_mode.mode.text">'+
//                '<ITB4"catalog_size_text_width"> x '+
//                '<ITB4"catalog_size_text_height"></span>'+
//            '</div>'+
            '<div>'+
//              '&emsp;Order<br>'+
              html_funcs.rollup_radio(''+pf_order+'sticky', ' :Sticky', 1, '',
                ['first:at first','last:at last','dont_care:don\'t care'])+ '<br>'+
              '<IC"'+pf_order+'reply_to_me">New reply to me at first<br>'+
              '<IC"'+pf_order+'reply">New reply at first<br>'+
//              '<IC"'+pf_order+'watch">Watch listed at first<br>'+
              html_funcs.rollup_radio(''+pf_order+'watch', ' :Watch listed', 1, '',
                ['first:at first','last:at last','dont_care:don\'t care'])+ '<br>'+
//              '&emsp;<input type="radio" name="'+pf_order+'sticky" value="hide"> hide<br>'+
              '<IC"'+this.mode+'.hide_unwatched">Watched only<br>'+
            '</div>'+
          '</div>'+
//          '<div style="float:right">'+
//            '<div name="hide_at_embed">'+
//              '<input type="radio" name="catalog.text_mode.mode" value="graphic"> Graphical mode<br>'+
//              '<input type="radio" name="catalog.text_mode.mode" value="text"> Text mode<br>'+
//            '</div>'+
//          '</div>'+
        '</div>'+
        '<div style="float:left;display:none">'+
          '<div data-show3="kwd"'+(pf_ex.kwd?'':' style="display:none"')+'>'+
            '<a name="HIDE3.kwd" style="cursor:pointer">[-]</a>'+
            '<span class="PLACEHOLDER" style="float:right"></span>'+ // tack
            '<ICN"filter.kwd.use">Keyword :'+
//            '<textarea style="height:1em" cols="25" name="filter.kwd.str"></textarea>'+
            '<ITBL25"filter.kwd.str">'+
            ' <ICN"filter.kwd.ci">CI '+
            '<SE"filter.kwd.match,match all,match any,unmatch all,unmatch any">'+
//            html_funcs.make_sel('filter.kwd.match', ['match all','match any','unmatch all','unmatch any'])+
            html_funcs.show_hide4(
              ' <ICN"filter.kwd.ew">exact'+
              ' <ICN"filter.kwd.sentence">Sentence'+
              ' <ICN"filter.kwd.inPost">inAPost')+
            ' <ICN"filter.kwd.re">RE<br>'+
            '1,(<ICN"filter.kwd.sub">Subject '+
            '<ICN"filter.kwd.name">Name '+
            '<ICN"filter.kwd.com">Comment '+
            html_funcs.show_hide4(
              '<ICN"filter.kwd.trip">Tripcode '+
              '<ICN"filter.kwd.id">ID '+
              '<ICN"filter.kwd.id2">ID2 '+
              '<ICN"filter.kwd.flag">Flag'+
              '<ICN"filter.kwd.file">Filename '+
              '<ICN"filter.kwd.meta">No. ')+
            ') (<ICN"filter.kwd.op">OP '+
            '<ICN"filter.kwd.post">Posts)'+
//            '<BTN"scanBoard,>">'+
//            '<BTN"scanSite,>>">'+
            html_funcs.rollup(
              '2,<IC"filter.disLWKA">Disable list filter when keyword filter is active<br>'+
              '2,<IC"filter.disTWKA">Disable tag filter when keyword filter is active<br>'+
              '1,<IC"filter.auto_list.use">Auto thread filter<br>'+
              '2,'+html_funcs.show_hide4('Determiner:<ITBL10"filter.auto_list.dtrm_str"> ')+
              '<ICN"filter.auto_list.kill">Hide '+
              '<ICN"filter.auto_list.watch">Watch '+
              '<ICN"filter.auto_list.show">Show '+
              '<ICN"filter.auto_list.sticky">Sticky '+
              '<ICN"filter.auto_list.style">Style:<ITBL25"filter.auto_list.style_str"><span> &emsp;</span>'+
              '<BTN"add_to_auto,+"><br>'+
              '2,<TA"filter.auto_list.str"><br>'+
              (!pref.test_mode['158']? '' : 
              '1,<IC"filter.postFilter.use">Post filter<br>'+
              '2,<ICN"filter.postFilter.hide">Hide '+
              '<ICN"filter.postFilter.boards">Boards:<ITBL10"filter.postFilter.boards_str">'+
              '<ICN"filter.postFilter.style">Style:<ITBL25"filter.postFilter.style_str" placeholder="border-left:10px solid orange"><span> &emsp;</span>'+
              '<BTN"filter.postFilter.add,+"><br>'+
              '2,<TA"filter.postFilter.str">')) +
          '</div>'+
//          '<div>'+
////            '<button name="scanSite">scanSite</button> '+
//////////            '<span name="scan_progress"></span><br>'+
////            '<button name="clear_threads">ClearAllThreads</button>'+
////            '<input type="checkbox" name="common.clear_at_manual_scan">Auto <br>'+
////            '<div style="float:left;overflow:auto;resize:both;" name="filter.tag_site_list"></div>'+
////            '<input type="checkbox" name="filter.tag_scansite">scan tags<br>'+
//          '</div>'+
          '<div style="clear:both'+(pf_ex.time?'':';display:none')+'" data-show3="time">'+
            '<a name="HIDE3.time" style="cursor:pointer">[-]</a>'+
//'<ICBX"filter.time">'+ html_funcs.rollup_radio('filter.time_sel',
            '<ICN"filter.time.use">Time: '+
            '<SE"filter.time.func,pn:Hide threads w/o posts after,cn:Hide threads created before,np:Watch threads posted after,nc:Watch threads created after,pp:Hide or watch by posted time,cc:Hide or watch by created time,pc:Hide/posted & watch/created">'+
//            'Time: Posted/Created '+
//            '<ICN"filter.time">'+
//            '<ICN"filter.time_creation">Hide '+
////            '<IC"filter.time_mark">Mark: '+
//            '<ICN"filter.time_watch">'+
//            '<ICN"filter.time_watch_creation">Watch '+
////            '<textarea style="height:1em" cols="25" name="filter.time_str"></textarea>'+
            '<ITBL25"filter.time.time_str">'+
            '<SE"filter.time.quick_sel,dummy:quick select...,0:now,1:1 hour ago,2:2 hours ago,4:4 hours ago,12:12 hours ago,24:1 day ago,72:3 days ago,168:1 week ago">'+
//            '<span class="CatChan_hMenu">quick select...'+
//              '<div class="CatChan_hMCnt" style="z-index:1">'+
//                ['now','1 hour ago','2 hours ago','4 hours ago','12 hours ago','1 day ago','3 days ago','1 week ago'].map(function(v){return '<button type="button">'+v+'</button>';}).join('')+
//              '</div>'+
//            '</span>'+
            '<br>'+
          '</div>'+
          '<div style="clear:both'+(pf_ex.tag?'':';display:none')+'" data-show3="tag">'+
            '<a name="HIDE3.tag" style="cursor:pointer;float:left">[-]</a>'+
            '<span class="PLACEHOLDER" style="float:right"></span>'+ // tack
            liveTag.popup_filter.html+
////            '<div style="float:left">'+
////              '<IC"filter.tag">Tag :'+
////            '</div>'+
////            '<div style="float:left">'+
////              '<div>'+
////                ' Fetcn / In / Out / Threads / Tag '+
////                '<ITBL25"filter.tag_search.str" placeholder="Search tags...">'+
//////                ' <ICBX"filter.tag_search.ci">CI'+
////                ' <ICBX"filter.tag_search.re">RE'+
////              '</div>'+
////              '<div><span></span>'+html_funcs.rollup(
////                '<IC"filter.tag_search.show_nof_boards">Show number of boards<br>'+
////                '<BTN"tagB.shown_t_pk,T"><BTN"tagB.shown_t_in,T"><BTN"tagB.shown_t_ex,T"> Toggle checkboxes of shown tags<br>'+ // span for not being wrapped by label.
////                '<BTN"tagB.shown_c_pk,C"><BTN"tagB.shown_c_in,C"><BTN"tagB.shown_c_ex,C"> Clear checkboxes of shown tags<br>'+
////                '<BTN"tagB.all_c_pk,C"><BTN"tagB.all_c_in,C"><BTN"tagB.all_c_ex,C"> Clear checkboxes of all tags')+'<br></div>'+
////              '<div style="overflow:auto;resize:both;float:left" name="filter.tag_list"></div>'+
////              html_funcs.rollup('<div style="clear:both">'+
////                '<BTN"tag2bList,Add"> selected tags to the last of board group<br>'+
////                '1,Label: <ITBL20"filter.tag2bList.label" placeholder="default: topmost tag"><br>'+
////                '1,<IR"filter.tag2bList.by,tag">By tag<br>'+
////                '1,<IR"filter.tag2bList.by,board">By board<br>'+
////                '</div>')+
//////              '<a name="SHOW" style="cursor:pointer" data-str="[\u25b2]">[\u25bc]</a>'+
//////              '<div name="SUB" style="clear:both;display:none">TEST</div>'+
////            '</div>'+
////            '<div style="clear:both"></div>'+ // for floating
          '</div>'+
          '<div style="clear:both'+(pf_ex.list?'':';display:none')+'" data-show3="list">'+
            '<a name="HIDE3.list" style="cursor:pointer;float:left">[-]</a>'+
            '<span class="PLACEHOLDER" style="float:right"></span>'+ // tack
            '<div style="float:left">'+
              '<IC"filter.list">Exclusive list:<br>'+
  //            '<IC"filter.list_mark_time">Mark:<br>'+
  //            '<!-- &emsp;<IC"filter.list_time_scroll">Scroll to new post -->'+
  //            '<div style="clear:both">&emsp;<textarea style="height:1em" cols="20" name="filter.list_str"></textarea></div>'+
              '&emsp;<textarea style="height:1em" cols="20" name="filter.list_str"></textarea>'+
            '</div>'+
            '<div style="float:left">'+
  //            '<div style="float:left"><IC"filter.attr_list">Attribute list:<br></div>'+
              '<IC"filter.attr_list">Attribute list:<br>'+
              '&emsp;<textarea style="height:1em" cols="20" name="filter.attr_list_str"></textarea>'+
            '</div>'+
            '<div style="float:left">'+
              'Watch time list:<br>'+
  //            '<IC"filter.watch_list_mark_time">Mark:<br>'+
              '&emsp;<textarea style="height:1em" cols="20" name="filter.watch_list_str"></textarea>'+
            '</div>'+
            '<div style="float:left">'+
              '<IC"filter.bookmark_list">Bookmark list:<br>'+
              '&emsp;<textarea style="height:1em" cols="10" name="filter.bookmark_list_str"></textarea>'+
            '</div>'+
            '<div style="clear:both"></div>'+ // for floating
          '</div>'+
          '<div style="clear:both'+(pf_ex.ctrl?'':';display:none')+'" data-show3="ctrl">'+
            '<a name="HIDE3.ctrl" style="cursor:pointer;float:left">[-]</a>'+
            '<div style="float:left;width:16px"><br></div>'+ // spaces for not clicking clear button
            '<div style="float:left">'+
              '<BTN"clear_filters,clear filters"><br>'+
              '<BTN"load,load"><IC"catalog.auto_load_filter">Auto load filters at changing board group<br>'+
              '<div>'+
                '<div style="float:left"><BTN"save,save"></div>'+
                '<div style="float:left">'+
                   '<IC"catalog.auto_save_filter">Auto save filters at opening or changing board group<br>'+
                   '<IC"catalog.auto_save_filter_at_refresh">Auto save filters at refresh'+
                '</div>'+
              '</div>'+
              '<BTN"clear_pruneds,clear all pruned entries in all lists"><br>'+
            '</div>'+
          '</div>'+
          '<div style="cursor:pointer;clear:both">'+
            '<a name="SHOW3.kwd"  data-show3="kwd"' +(pf_ex.kwd? ' style="display:none"':'')+'>[+ Keyword filter]</a>'+
            '<a name="SHOW3.time" data-show3="time"'+(pf_ex.time?' style="display:none"':'')+'>[+ Time filter]</a>'+
            '<a name="SHOW3.tag"  data-show3="tag"' +(pf_ex.tag? ' style="display:none"':'')+'>[+ Tag search]</a>'+
            '<a name="SHOW3.list" data-show3="list"'+(pf_ex.list?' style="display:none"':'')+'>[+ List filters]</a>'+
            '<a name="SHOW3.ctrl" data-show3="ctrl"'+(pf_ex.ctrl?' style="display:none"':'')+'>[+ Control]</a>'+
          '</div>'+
        '</div>'+
        (this.mode==='float'? '<a name="sticky_filter_settings" style="position:absolute;right:0;top:0;display:none">[\u25e5]</a>':'')+
        '<div style="clear:both"></div>');
  
//  var fm = pn12_0_4;
//  if (!pf_original) {
  var fm = document.createElement('form'); // this will be stripped at embed_mode, because contents of pn12_1 will be replaced.
  fm.name = pref.cpfx+this.name;
  fm.setAttribute('style','margin:0');
  fm.appendChild(pn12_0_4);
//  }
      pn12.childNodes[1].insertBefore(fm, pn12_1); // appendChild(pn12_0_4);
      var tacks = pn12_0_4.getElementsByClassName('PLACEHOLDER');
      for (var i=0;i<tacks.length;i++) cnst.set_tack_float(tacks[i].appendChild(site2[site.nickname].make_tack()).parentNode, hide_prev_float, show_prev_dock);
//      var btns = pn12_0_2.getElementsByTagName('button');
//      cnst.set_target_of_button(btns['settings'],pn12_0_4.childNodes[0]);
//      cnst.set_target_of_button(btns['filter'],pn12_0_4.childNodes[1]);
  this.pn12 = pn12;
  this.pn12_0 = pn12_0;
  this.pn12_1 = pn12_1;
  this.pn12_0_2 = pn12_0_2;
  this.pn12_0_4 = pn12_0_4;
  this.board_sel = board_sel;
  this.threads = {};
//  this.ths_undo = {};
  this.idxs = [];
  this.idxs_req = new Map; // Set(); // [];
  this.drawn_idx = 0;
  this.threads_odl = {};
  this.ppn = pn12_1;
//  this.ppn.className = pref.cpfx+'catalog';
  this.pppn = this.ppn.parentNode; // pppn!==pn12, because patched.
  this.set_view_class();
  this.pppn_window = this.mode==='float'? this.pppn : window;
  gGEH.pns_resize.set(this.pn12, this.show_catalog_resize);
  var comps_0_4 = pn12_0_4.getElementsByTagName('*');
  this.components = {
    ordering: comps_0_4[(this.mode==='float'? 'float' : 'catalog')+'.order.ordering'],
    pn12_0: pn12_0,
    pn12_0_4: pn12_0_4,
    pn_hi: null,
    kwd_use: comps_0_4['filter.kwd.use'],
    kwd_mirror: [],
    time_str: comps_0_4['filter.time.time_str'],
    tag_use: comps_0_4['filter.tag'],
    attr_list: comps_0_4['filter.attr_list_str'],
    watch_list: comps_0_4['filter.watch_list_str'],
    ex_list: comps_0_4['filter.list_str'],
    pn_filter: pn12_0_4.childNodes[1],
    auto_list: comps_0_4['filter.auto_list.str'],
    postFilter: comps_0_4['filter.postFilter.str'],
    merge: comps_0_4[this.mode+'.merge'],
    merge_list: comps_0_4[this.mode+'.merge_list'],
    filter: pn12_0_4.childNodes[1], // these are opened by clicking the button. related by name.
    settings: pn12_0_4.childNodes[0],
    sticky_filter_settings: pn12_0_4.childNodes[2],
    titleBar: pn12_0,
  };
  this.native = {};
  this.triage_history = [];
  this.INST = {};
  this.auto_update = new AutoUpdate(this, this.health_indicator);
  this.refresh_tgts = {};
  this.image_resize1_onload_bound = this.image_resize1_onload.bind(this);
  this.image_resize2_onload_bound = this.image_resize2_onload.bind(this);
//  this.image_resize_th_onload_bound = this.image_resize_th_onload.bind(this);

  this.footer = Footer.factory(this, clgs.length==0);
//  this.footer = (clgs.length!=0)? Footer.factory(this.mode, this.threads, this) : (Footer.clg = this, Footer);

  Tooltips.add_root(this.mode==='float'? pn12 : pn12_0_4);
  pref_func.apply_prep(pn12_0_4.childNodes[0], false, null, null, true, this.pref);
//  pref_func.apply_prep(pn12_0_4,false, null, null, true, this.pref); // redundant, moved to 'load'
  cnst.auto_shrink_selector(this.components.ordering);
  this.onchange_funcs = {'.':this, __proto__:onchange_funcs};
//  if (clgs.length!=0) { // temporal, this can be consolidated.
    pref_func.add_onchange(pn12_0_2, this.onchange_funcs, oninput_funcs);
    var event_func = pref_func.add_onchange(pn12_0_4, this.onchange_funcs, oninput_funcs);
//  }
  if (this.mode==='float' && pref.catalog.appearance.orderOnTB) {
    this.components.ordering.parentNode.removeChild(this.components.ordering.nextSibling);
    this.pn12_0.childNodes[2].appendChild(this.components.ordering);
  }
  if (clgs.length==0) cataLog.event_func = event_func;
  this.scrollTop = 0;
  this.innerHeight = null;
  this.pppn_window.addEventListener('scroll', this.show_catalog_scroll, false);
  /*if (!pref.test_mode['96'] || this.mode!=='catalog')*/ this.pppn_window.addEventListener('resize', this.show_catalog_resize, false); // embed catalog needs to be added eventLister, but // BUG, DOM ELEMENT CAN'T TAKE resize EVENT, this doesn't work // https://stackoverflow.com/questions/6492683/how-to-detect-divs-dimension-changed
  if (this.mode==='float') this.ppn.addEventListener('scroll', this.show_catalog_scroll, false); // Element: scroll event is not bubbles. // https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll_event

//  this.filter_kwd_prep_init(null,true); // prep kwd.rexps // redundant, called in this.onchange_funcs['*.board_list_sel']
  this.Setup_liveTag(pref.liveTag.NC);
//  this.liveTag = (clgs.length===0)? (liveTag.clg = this, liveTag.footer = this.footer, liveTag) : liveTag.factory(this, this.footer, pref.liveTag.NC);
//  this.liveTag.setup_pn(pn12_0_4, this.pref.filter.tag_search); // this, clgs.length!==0 && pref.liveTag.NC && this.footer);
//  this.footer.tag_node_onclick = this.liveTag.tag_node_onclick.bind(this.liveTag);
//  this.footer.liveTag = this.liveTag;
//  if (this.pref!==pref && !pref.liveTag.NC) Object.defineProperty(this.pref.filter, 'tag', {get:function(){return pref.filter.tag;}, set:function(v){pref.filter.tag = v;}, configurable:true, enumerable:true});
  this.merge_bases = (clgs.length===0)? site2['DEFAULT'].update_posts_merge_bases : site2['DEFAULT'].update_posts_merge_bases.factory();
  this.merge_bases.view = this.view; // for site2['DEFAULT'].update_posts_merge_bases itself // REDUNDANT???
  this.merge_op_hops = {};
  this.delayed_filter_changed = new Watchdog(this.filter_changed.bind(this),1000).get_bound_func();

  this.Clgs.push(this);
//  this.Clgs_pn.set(pn12,this);
  if (site2[site.nickname].catalog_float_prep) site2[site.nickname].catalog_float_prep(this);

  if (clgs.length==2) this.hook_set({
//    set drawn_idx(v){this.Clgs.forEach(function(c){c.drawn_idx = v;});}, // temporal
    footer: {
//      update: function(name,flags,tags, force){ // will be discarded by test_mode['133']
//        var clgs = Clg.prototype.Clgs;
//        for (var i=0;i<clgs.length;i++) if (clgs[i].threads[name]) clgs[i].footer.update(name,flags,tags, force);
//      },
      update_force: function(name,flags,tags){
        var clgs = Clg.prototype.Clgs;
        for (var i=0;i<clgs.length;i++) if (clgs[i].threads[name]) clgs[i].footer.update_force(name,flags,tags); // clgs[i].pref[clgs[i].footer.mode_fmt].footer.flags,tags);
      },
      color_tag_node: function(name,tag, all){
        var clgs = Clg.prototype.Clgs;
        for (var i=0;i<clgs.length;i++) if (all || !clgs[i].NC) if (clgs[i].threads[name]) clgs[i].footer.color_tag_node(name,tag);
      },
      update_all_force: function(e,src, flags){ // Footer.update_all_force has 'tags' in arguments, and that is used in 'liveTag.NC' event function.
        var clgs = Clg.prototype.Clgs;
        for (var i=0;i<clgs.length;i++) clgs[i].footer.update_all_force(e,src, flags);
      },
//      refresh_start: function(){
//        var clgs = Clg.prototype.Clgs;
//        clgs[0].footer.refresh_start();
//        for (var i=1;i<clgs.length;i++) clgs[i].footer.timestamp_set();
//      },
      __proto__: Footer},
    show_catalog: function(tgts_in){
      var clgs = Clg.prototype.Clgs;
      for (var i=0;i<clgs.length;i++) {
        var clg = clgs[i];
        if (!tgts_in) clg.show_catalog();
        else if (typeof(tgts_in)==='string') {if (clg.threads[tgts_in]) clg.show_catalog(tgts_in);}
        else {
          var tgts = {};
          var flag = false;
          for (var t in tgts_in) if (clg.threads[t]) {tgts[t] = null; flag = true;}
          if (flag) clg.show_catalog(tgts);
        }
      }
    },
//    remove_thread: function(name, pn_only, keep){
//      var clgs = Clg.prototype.Clgs;
//      var removed = false;
//      for (var i=0;i<clgs.length;i++) removed |= clgs[i].remove_thread(name, pn_only, keep, true) && !keep;
//      if (removed) gClg.Maintain_AllThreads(name);
//    },
    filter_changed: Clg.prototype.Clgs.FilterChanged, // this may be redundant already...
    keepMergedThread: function(name){
      var clgs = Clg.prototype.Clgs;
      for (var i=0;i<clgs.length;i++) if (clgs[i].keepMergedThread(name)) return true;
    },
    __proto__:Clg.prototype});

  if (clgs.length===1) cataLog.auto_update = this.auto_update;
  if (site.whereami!=='archive') this.auto_update.set();

  if (pref.test_mode['160'] && clgs.length===1) site2[site.nickname].catalog_native_prep(this.pn12_0_4,this.pn12_0, this.health_indicator.pn2, this);
  if (clgs.length>=2) this.onchange_funcs['*.board_list_sel'](null,true, pref[this.mode].env.refresh_initial); // invoke all, call from clgs[0] is left later for safty.
  if (this.serialNo!==0 && clgs.length===1) reentry_0_patch(this);
  this.show_catalog_scroll_lock = this.show_catalog_scroll_lock_factory(this);

};
      
      Clg.prototype = {
        onrolldown: function(){var val_old = this.rolledup; this.rolledup = 0; if (val_old>1) this.show_catalog(0);},
        onrollup: function(){this.rolledup++;},
        Clgs: [],
        AllThreads: null, // contains tgt_th(s) which have the most numbers of posts to show.
        UnsyncedTriages: {}, // unsynced triages; catalog data is delayed from thread sometimes, triage through pipe may hit this in 4chan.
        destroy: function(e){
          var pn12 = e.currentTarget.parentNode;
          cnst.div_destroy(e.currentTarget.parentNode, true);
          var idx = Clg.prototype.Clgs.map(function(v){return v.pn12;}).indexOf(pn12);
          var clg = Clg.prototype.Clgs.splice(idx,1)[0];
          if (pref.catalog.auto_save_filter) clg.onchange_funcs.save();
          for (var name in clg.threads) clg.remove_thread(name);
          clg.auto_update.stop_if_running();
          clg.footer.liveTag = null; // cut reference loop.
          clg.footer = null; // cut reference loop.
          clg.liveTag = null; // cut reference loop.
          clg.auto_update = null; // cut reference loop.
          clg.pppn_window.removeEventListener('scroll', clg.show_catalog_scroll, false);
          clg.pppn_window.removeEventListener('resize', clg.show_catalog_resize, false);
          if (clg.mode==='float') clg.ppn.removeEventListener('scroll', clg.show_catalog_scroll, false);
          clg.GEH.destroy();
          if (Clg.prototype.Clgs.length==1) Clg.prototype.hook_set(Clg.prototype.Clgs[0]);
        },
        hook_set: function(clg){
          gClg = clg;
          cataLog.Footer = clg.footer;
          show_catalog = clg.show_catalog.bind(clg);
          cataLog.show_catalog = show_catalog;
          cataLog.gClg = gClg;
          if (Clg.prototype.Clgs.length==1) pClg = clg;
          Clg.prototype.AllThreads = (Clg.prototype.Clgs.length==1)? pClg.threads : comf.shallow_copy_1(pClg.threads);
        },
        set_view_class: function(sel, old_val){
          if (sel!==undefined) this.set_view_class_1('remove', sel<=0, sel>=0? sel : undefined, old_val);
          this.set_view_class_1('add', sel<=0, sel<0? 0 : undefined, old_val);
          return old_val;
        },
        set_view_class_1: function(func, change_view, sel, old_val){
          var v  = sel===0? old_val : this.view;
          var pf = this.pref.INST[v] || {};
          var s2 = sel===2? old_val : v==='catalog'? pf.op : pf.subStyle2;
          var s  = sel===1? old_val : v==='catalog'? pf.thumb.size : pf.subStyle;
//          if (v==='catalog') {
//            s2 = ({off:0, on:1})[s2];
//            s = ({small:0, large:1, custom:2})[s];
//          }
          if (change_view) this.pppn.classList[func](pref.cpfx+v+'View');
          var cl = v==='catalog'? (s2==='on'? 'extended-':'')+s : (s||s2? pref.cpfx+v+'View'+(s||(s2?'0':''))+(s2||'') : '');
//          var cl = this.dic_view_class[v+(s||(s2?'0':''))+(s2||'')] || (s||s2? pref.cpfx+v+'View'+(s||(s2?'0':''))+(s2||'') : '');                    
          if (cl) this.ppn.classList[func](cl);
//          return pref.cpfx+v+'View'+(s||(s2?'0':''))+(s2||'');
        },
//        dic_view_class: {catalog:'small', catalog1:'large', catalog01:'extended-small', catalog11:'extended-large'},
//        Clgs_pn: new WeakMap(),
//        get_clg_recursive: function(pn){
//          var clg;
//          while (pn && !(clg=this.Clgs_pn.get(pn))) pn = pn.parentNode;
//          return clg;
//        },
        serialNo: 0,
//        ppn2clg: function(ppn){
//          var i=0;
//          while (i<this.Clgs.length && this.Clgs[i].ppn!==ppn) i++;
//          return this.Clgs[i];
//        },
        BroadcastEventByMode: function(mode, func, argsObj){
          var clgs = mode==='proto'? this.Clgs : this.Clgs.filter(function(v){return v.mode===mode;});
          for (var i=0;i<clgs.length;i++) func.apply({'.':clgs[i], __proto__:this.onchange_funcs},argsObj); // don't use arguemnts.callee for strict mode
        },
        onchange_funcs: onchange_funcs,
//        SearchFirstClg: function(mode){
//          for (var i=0;i<this.Clgs.length;i++) if (this.Clgs[i].mode===mode) return this.Clgs[i];
//        },
//        Mark_missing_info: function(name, lvl){ // working code
//          var clgs = Clg.prototype.Clgs;
//          for (var i=0;i<clgs.length;i++) if (clgs[i].threads[name])
//            clgs[i].threads[name][16].missing_info = lvl;
//        },
        Setup_liveTag: function(NC, reentry){
          this.liveTag = (this.Clgs.length===0)? (liveTag.clg = this, liveTag.footer = this.footer, liveTag) : liveTag.factory(this, this.footer, NC);
          this.liveTag.setup_pn(this.components.pn_filter, this.pref.filter.tag_search); // this, clgs.length!==0 && pref.liveTag.NC && this.footer);
//          this.footer.tag_node_onclick = this.liveTag.tag_node_onclick.bind(this.liveTag);
          this.footer.liveTag = this.liveTag;
          if (this.pref!==pref) {
            if (!NC) Object.defineProperty(this.pref.filter, 'tag', {get:function(){return pref.filter.tag;}, set:function(v){pref.filter.tag = v;}, configurable:true, enumerable:true});
            else if (reentry) {
              delete this.pref.filter['tag'];
              this.pref.filter.tag = false;
            }
            this.components.tag_use.checked = this.pref.filter.tag;
          }
        },
        Maintain_AllThreads: function(name){
          if (this.Clgs.length==1) return;
          var clgs = this.Clgs.filter(function(v){return v.threads[name];});
          if (clgs.length===0) delete gClg.AllThreads[name];
          else {
            var tgt_th = clgs[0].threads[name];
            if (clgs.length>=2) {
              var sels = clgs.map(function(v){return v.threads[name][16].t2h_sel || pref[embed_mode].t2h_sel;});
              var i=1;
              while (i<clgs.length) if (sels[0]==sels[i]) i++; else break;
              var variety = i!=clgs.length;
            }
            gClg.AllThreads[name] = (!variety)? tgt_th : {16:{t2h_sel:'ALL', __proto__:tgt_th[16]},__proto__:tgt_th};
          }
        },
        Maintain_AllThreads_incremental: function(name, tgt_th, tgt_th_new){
//          if (this.Clgs.length==1) return; // this is equivalent to tgt_th!==tgt_th_new in caller.
//          var tgt_th = gClg.AllThreads[name]; // this is equivalent to given tgt_th
          var t2h_sel = pref[embed_mode].t2h_sel;
          if ((tgt_th_new[16].t2h_sel || t2h_sel) !== (tgt_th[16].t2h_sel || t2h_sel))
            gClg.AllThreads[name] = (tgt_th_new[16].t2h_sel==='ALL')? tgt_th_new : {16:{t2h_sel:'ALL', __proto__:tgt_th_new[16]},__proto__:tgt_th_new};
        },
//        Maintain_AllThreads: function(name){ // working code
//          if (this.Clgs.length==1) return;
//          var pf_store = (pref[embed_mode].storePosts==='ALL_agg' || pref[embed_mode].storePosts==='auto' && pref4.search_posts_active_once)? 'ALL' : pref[embed_mode].deleted_posts.detect; // the same code is in callback2
//          var ary = this.Clgs.map(function(v){return !v.threads[name]? null : scan_boards.get_nof_store_posts(pf_store, v.threads[name]);});
//          var ary2 = ary.filter(function(v){return v!==null;});
//          if (ary2.length===0) delete gClg.AllThreads[name];
//          else {
//            var tgt_th = this.Clgs[ary.indexOf(ary2[0])].threads[name];
//            var variety = ary2.length>=2 && ary2.filter(function(v){return v!=ary2[0];}).length>=2;
//            gClg.AllThreads[name] = (!variety)? tgt_th : {16:{t2h_sel:'ALL', __proto__:tgt_th[16]},__proto__:tgt_th};
//          }
//        },
        Sync_watch_load: function(e, from_modify, from_pruned){
          if (pref.common.consolidated_watch_list && pref.common.sync_watch_list) {
            if (pref.debug_mode['1']) console.log('LocalStorage: '+e.key+': '+(e.newValue && e.newValue.length>120? e.newValue.slice(0,100)+'...':e.newValue));
            if (e.key===pref.script_prefix+'.WatchList' || from_modify || from_pruned) {
              for (var i=0;i<gClg.Clgs.length;i++) {
                var clg = gClg.Clgs[i];
                if (clg.pref.filter.watch_list_str === e.newValue) continue;
                if (pref.debug_mode['37']) {
                  var olds = clg.pref.filter.watch_list_str.split('\n').reduce(function(a,c){a[c]=null;return a;},{});
                  var news = e.newValue.split('\n').reduce(function(a,c){if (c in olds) delete olds[c]; else a[c]=null; return a;},{});
                  console.log('changed: '+Object.keys(olds)+' -> '+Object.keys(news));
                }
                clg.pref.filter.watch_list_str = e.newValue || '';
                pref_func.apply_prep(clg.components.watch_list,false, null, null, true, clg.pref);
                if (!from_pruned) clg.onchange_funcs['filter.watch_list_str'](null, true);
              }
            } else if (e.key===pref.script_prefix+'.WatchListCmd') {
              var args = JSON.parse(e.newValue);
              if (!args) return;
              if (args[1]==='PRUNED') {gClg.Sync_watch_load({newValue:args[2]}, null, true); return;}
              if (typeof(args[1])==='string') gClg.Sync_watch_load_cmd_1(args);
              else for (var i=0;i<args.length;i++) gClg.Sync_watch_load_cmd_1(args[i]);
//              if (args[1]==='UNWATCH' && !gClg.AllThreads[args[0]]) return;
//              var dbt = comf.fullname2dbt(args[0]);
//              if (!liveTag.mems[dbt[0]] || !liveTag.mems[dbt[0]][dbt[1]] || !liveTag.mems[dbt[0]][dbt[1]][dbt[2]] || !gClg.AllThreads[args[0]]) {
//                args[5] = true;
//                gClg.triage_exe_broadcast.apply(gClg,args);
//              } else triage_exe_pipe(args, true);
            }
          }
        },
        Sync_watch_load_cmd_1: function(args){
          if (args[1]==='UNWATCH' && !gClg.AllThreads[args[0]]) return;
          var dbt = comf.fullname2dbt(args[0]);
          if (!liveTag.mems[dbt[0]] || !liveTag.mems[dbt[0]][dbt[1]] || !liveTag.mems[dbt[0]][dbt[1]][dbt[2]] || !gClg.AllThreads[args[0]]) {
            args[5] = true;
            gClg.triage_exe_broadcast.apply(gClg,args);
          } else triage_exe_pipe(args, true);
        },
        Sync_watch_save: function(args, merged_broadcast){
          if (merged_broadcast) {
            this.Sync_watch_save_merged.push(args);
            return;
          }
//          pClg.onchange_funcs['save_watch_list']();
          var data = this.Sync_watch_save_merged.length!=0 && this.Sync_watch_save_merged.concat([args]) || args.concat(Date.now());
          if (localStorage) localStorage.setItem(pref.script_prefix + '.WatchListCmd', JSON.stringify(data)); // timestamp for force update
          this.Sync_watch_save_merged = [];
        },
        Sync_watch_save_merged: [],
//        Sync_watch_save: (function(){
//          var args;
//          var save_delayed = DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(function(){
//            pClg.onchange_funcs['save_watch_list']();
//            if (args) {
//              if (localStorage) localStorage.setItem(pref.script_prefix + '.WatchListCmd', JSON.stringify(args.concat(Date.now()))); // timestamp for force update
//              args = null;
//            }
//          },100));
//          return function(args_in){
//            args = args_in;
//            save_delayed(pref.common.sync_watch_list_delay);
//          };
//        })(),
        jump_to_post: function(dbtp){
          var name = dbtp[0]+dbtp[1]+dbtp[2];
          var posts = this.threads[name] && this.threads[name][1] && this.threads[name][16].posts;
          if (posts) {
            for (var i=posts.length-1;i>=0;i--) if (posts[i].no==dbtp[3]) break; // dbtp[3] is string
            if (i>=0) window.scrollTo(0,posts[i].pn.offsetTop);
            return posts[i].pn;
          }
        },
        image_resize: function(pn,fst, th){
          var pfp = this.pref.INST[this.view].thumb; // this.pref[this.view].format.thumb;
          var pf = pfp[pfp.size];
          var w = fst? pf.w1 : pf.w2;
          var h = fst? pf.h1 : pf.h2;
          var m = pf.en && pf.min;
//          this.image_resize_1(pn, fst? pf.w1 : pf.w2, fst? pf.h1 : pf.h2, pf.en && pf.min);
//        },
//        image_resize_1: function(pn,w,h, m){
          var video = pn.tagName==='VIDEO';
          var nw = (video? pn.videoWidth : pn.naturalWidth) || 32; // pn.naturalWidth is 0 at error, so 'pn.naturalWidth || pn.videoWidth' doesn't work.
          var nh = (video? pn.videoHeight : pn.naturalHeight) || 32; // 32 is enough to show "broken icon"(by system)
          var fw = nw/w; // (nw>w)? nw/w : 1;
          var fh = nh/h; // (nh>h)? nh/h : 1;
          var f = fw>fh? fw : fh;
          if (f<1) f = (!m || nw>m && nh>m || f==0)? 1 : (f===fw? nw : nh)/m;
  //      if (fh>1 || fw>1) {
          var tn_w = nw/f;
          var tn_h = nh/f;
          if (pn.style.width)  pn.style.width  = tn_w+'px'; else pn.setAttribute('width', tn_w);
          if (pn.style.height) pn.style.height = tn_h+'px'; else pn.setAttribute('height',tn_h);
          if (th && pn.src) th.size_from_img(pn, this, nw, nh);
  //      }
        },
        image_resize_onload_add: function(imgs, th){
          for (var i=0;i<imgs.length;i++) this.image_resize_img(imgs[i], i==0, th && th.size_from_img && th);
        },
        image_resize_img: function(img, first, th){
          var video = img.tagName==='VIDEO';
          if (video? img.duration : img.complete) {
            this.image_resize(img, first, th);
            if (video) {if (img.onloadedmetadata) img.onloadedmetadata = null;}
            else if (img.onload) img.onload = null;
          } else {
            var func = first? this.image_resize1_onload_bound : this.image_resize2_onload_bound;
            if (th) func = func.bind(null,th);
            if (video) img.onloadedmetadata = func; else img.onload = func; // overwrite for racing condition of merging at start up
          }
//          } else img.addEventListener('load',(i==0)? this.image_resize1_onload_bound : this.image_resize2_onload_bound, false);
        },
        image_resize_all: function(size){
          if (size) this.pref[this.view].format.thumb.size = size;
          for (var name in this.threads) if (this.threads[name][0]) this.image_resize_th(this.threads[name][0]);
//          for (var name in this.threads) if (this.threads[name][0]) {
//            var imgs = site2[site.nickname].parse_funcs[this.mode+'_html'].tn_imgs({pn:this.threads[name][0]});
//            for (var i=0;i<imgs.length;i++) this.image_resize(imgs[i], i===0);
//          }
        },
        image_resize_th: function(pn){
          var imgs = site2[site.nickname].parse_funcs[this.view+'_html'].tn_imgs({pn:pn}); // must be this.view instead of this.mode, because floating catalog can change view, and catalog on catalog may call parse_funcs['headline_html].tn_imgs, but undefined.
          this.image_resize_onload_add(imgs);
//          for (var i=0;i<imgs.length;i++) this.image_resize(imgs[i], i===0);
        },
//        image_resize_th_onload: function(e){
//          e.currentTarget.removeEventListener('load',this.image_resize_th_onload_bound,false);
//          this.image_resize_th(e.currentTarget);
//        },
        image_resize1_onload: function(e1, e2){
          var e = e2||e1;
          e.currentTarget.onload = null;
//          e.currentTarget.removeEventListener('load',this.image_resize1_onload_bound,false);
          this.image_resize(e.target, true, e2?e1:null);
        },
        image_resize2_onload: function(e1, e2){
          var e = e2||e1;
          e.currentTarget.onload = null;
//          e.currentTarget.removeEventListener('load',this.image_resize2_onload_bound,false);
          this.image_resize(e.target, false, e2?e1:null);
        },
        kwd_clear: function(){
          var pn = pClg.components.kwd_mirror[1]; // pClg is hard-coded for event input
          pn.value = '';
          pn.oninput({target:pn,currentTarget:pn});
        },
        kwd_init: function(search, pn_filter, force_clear){
          var search2;
          if (this.components.kwd_mirror.length==0) { // for multientry in meguca.
            search2 = search.cloneNode(false);
            search2.name = 'filter.kwd.str';
            search2.oninput = cataLog.event_func;
            this.components.kwd_mirror = [pn_filter.getElementsByTagName('input')['filter.kwd.str'], search2];
          } else search2 = this.components.kwd_mirror[1];
          search.setAttribute('style','display:none');
          search.parentNode.insertBefore(search2,search.nextSibling);
if (force_clear || site.nickname==='meguca') this.kwd_clear(); // just NOT tested in meguca
          return search2;
        },
        filter_kwd_prep_init: function(e, init) {
          var kwd = this.pref.filter.kwd;
          kwd.str_old = '';
          kwd.fail_list = {};
          kwd.suc_list  = {};
          this.filter_kwd_prep(e, init);
        },
        filter_kwd_prep: function(e, init) {
          var kwd = this.pref.filter.kwd;
          kwd.rexps = comf.kwd_prep_regexp(kwd);
  //        kwd.timestamp = Date.now(); // not saved because pref.default() don't geenrate this.
          this.filter_kwd_active(e, init);
        },
        filter_kwd_count: 0,
        filter_kwd_active: function(e, init) {
          var kwd = this.pref.filter.kwd;
          var val_old = kwd.posts_active;
          var basic_active = kwd.use && kwd.rexps && (kwd.sub || kwd.name || kwd.com || kwd.file || kwd.meta || kwd.trip || kwd.id || kwd.id2 || kwd.flag);
          kwd.active       = basic_active && (kwd.op || kwd.post);
          kwd.posts_active = basic_active &&            kwd.post;
          if (kwd.posts_active && !val_old && (e && e.target.name==='filter.kwd.post' || !pref4.search_posts_active_once)) {
            if (pref[this.mode].searchAs) this.onchange_funcs.scanBoard(e, true, true);
            if (pref[this.mode].searchAsA) this.onchange_funcs.scanSite(e, true, true);
            pref4.search_posts_active_once |= kwd.posts_active;
          }
          if (!init && !kwd.posts_active && val_old) format_html.posts_search_inactivated();
          kwd.timestamp = this.filter_kwd_count++; // not saved because pref.default() don't geenrate this. // use single entity for multi catalog or switching pref.*.filter object.
  if (!pref.test_mode['59']) {
          if (e && e.target.name==='filter.kwd.str' && kwd.match===0 && !kwd.ew && (!kwd.re || /^[\w\s]*$/.test(kwd.str+kwd.str_old))) {
            for (var i in kwd.fail_list) {
              if (kwd.str.indexOf(kwd.fail_list[i])!==0) delete kwd.fail_list[i];
              else if (kwd.fail_list[i]===kwd.str) kwd.timestamp = i;
            }
            for (var i in kwd.suc_list) {
              if (kwd.suc_list[i].indexOf(kwd.str)!==0) delete kwd.suc_list[i];
              else if (kwd.suc_list[i]===kwd.str) kwd.timestamp = i;
            }
          } else {
            kwd.fail_list = {};
            kwd.suc_list  = {};
          }
          if (kwd.str) {
            kwd.fail_list[kwd.timestamp] = kwd.str;
            kwd.suc_list[kwd.timestamp]  = kwd.str;
          }
  }
          var delay = kwd.posts_active && (kwd.str.length < pref[this.view].env.kwd_filter_delay_len) && pref[this.view].env.kwd_filter_delay;
          if (!init) if (delay) this.delayed_filter_changed(delay); else this.filter_changed();
        },
        filter_changed: function(){
//if (pref.test_mode['60']) {        
//        if (pref.filter.kwd.use || pref.filter.tag || pref.filter.time || pref.filter.list) for (var th in threads) threads[th][9] = catalog_filter_query(th);
//        else {
////          for (var th in threads) threads[th][9] = [true];
//          for (var th in threads) threads[th][9][0] = true;
//          gClg.drawn_idx = 0;
//        }
//} else {
          var pf = this.pref.filter;
          var val = (pf.kwd.use || pf.tag || pf.time.h || pf.list)? null : true;
          for (var th in this.threads) this.threads[th][9][0] = val;
//        this.drawn_idx = 0;
//}  
          if (show_catalog) this.show_catalog(0); // 'if (show_catalog)' is a patch.
//console.log('filter_changed');
//        catalog_refresh_gather_info(); // cut at 2015.05.15.
        },
        FilterChanged: function(){
          for (var i=0;i<this.Clgs.length;i++) this.Clgs[i].filter_changed();
        },
        format_refresh_tgts: function(){
          if (Array.isArray(this.refresh_tgts)) {
            var refresh_tgts = {};
            for (var j=0;j<this.refresh_tgts.length;j++) refresh_tgts[this.refresh_tgts[j].replace(/\/[cjpqt][0-9]+$/,'/')] = null;
            this.refresh_tgts = refresh_tgts;
          }
        },
        IDB_req: {},
        remake_html_prep: function(tgt_th, keep_html_key){
          if (!keep_html_key) {
            var lth = tgt_th[16].lth;
            tgt_th[0] = false;
            tgt_th[16].th = (lth.ta)? {pn:null, __proto__:lth.ta.posts[0], posts:lth.ta.posts} : {pn:null, __proto__:tgt_th[16].lth.th}; // lth.ta is updated without synchronizing with lth.th, but 'recent_posts' uses lth.ta.posts in insert_thread_prepare_html_lazy
//            if (th.type_data==='html') cataLog.format_html.prepare_html_extract_params(lth.ta? lth.ta.posts[0] : tgt_th[16].lth.th); // for 4chan catalog_html // doesn't work because th.pn was already destroyed.
//            tgt_th[16].th = {pn:null, __proto__:tgt_th[16].lth.th};
            tgt_th[16].posts = null;
          } else gGEH.pns_all_keys.set(tgt_th[0],keep_html_key);
          if (tgt_th[16].icon_sticky) delete tgt_th[16].icon_sticky;
          if (tgt_th[16].icon_showAlways) delete tgt_th[16].icon_showAlways;
          if (tgt_th[16].pn_summary) delete tgt_th[16].pn_summary;
        },
        remove_thread: function(name, pn_only, from_gClg){ // , no_reorder){
          var tgt_th = this.threads[name];
//          if (keep) {
//            if (tgt_th && tgt_th[14]!=='IDB' && tgt_th[14]!=='FILE') {
//              if (tgt_th[14]!=='x') {
//                tgt_th[14]='x';
//                this.footer.update_force(name);
//              }
//            }
//            return;
//          }
          if (name.substr(0,4)!==':DL:') {
            if (!tgt_th) return;
            var lth = liveTag.mems.getFromName(name);
            if (!pn_only) if (lth.q) site2[lth.domain].popups_release_all_not_exist(lth); // remove cross thread/board links.
//            remove_threads_events(name); // must NOT use test_mode['104']
            if (tgt_th[1]) {
//              if (pop_up_status[name]) pop_down_op(name); // must NOT use test_mode['110']
              if (tgt_th[0] && tgt_th[0].parentNode===this.ppn) if (this.func_hide(name)) tgt_th[1] = false; // parent check is for on_demand_draw // calls idx_reorder and changes order of idxs here
//              if (!no_reorder) {
//                var ref = this.idxs.indexOf(name); // consumes 400ms in 2ch at initial if this is evaluated always.
//                if (ref<this.drawn_idx) this.drawn_idx = 0;
//              }
            }
//            this.merge_bases.remove_th(name, tgt_th, this); // redundant, called in func_hide above. // calls idx_reorder and changes order of idxs here <- ,but changed???
          }
          if (pn_only) {
            tgt_th[0] = false;
            tgt_th[1] = false;
            tgt_th[16].th = tgt_th[7];
            tgt_th[16].posts = null;
  //          if (!tgt_th[16].th) tgt_th[16].th = tgt_th[16];
            if (tgt_th[24]) tgt_th[24][0] = null;
            return;
          }
          delete this.threads[name]; // remove ':DL:' also
          if (pref.debug_mode['2']) console.log('removed: '+name);
          this.idx_remove(name);
          if (pref.test_mode['95'] && site.nickname==='dist') site2[site.nickname].testPoster(name);
          if (!from_gClg && tgt_th) gClg.Maintain_AllThreads(name);
          return tgt_th;
        },
        get_latest_time_of_a_thread: function(name){
          var tgt_th8 = this.threads[name][8];
          return (tgt_th8[0]>tgt_th8[4])? tgt_th8[0] : (tgt_th8[4] || tgt_th8[0]);
        },
        clear_threads: function(num, schedule){
          if (schedule && this.INST.threads_candidates_of_deletion) {
            for (var name in this.INST.threads_candidates_of_deletion)
              if (this.threads[name] && this.INST.threads_candidates_of_deletion[name]===this.get_latest_time_of_a_thread(name)) this.remove_thread(name);
            this.INST.threads_candidates_of_deletion = null;
          }
          var idx = 0;
          while (idx<this.idxs.length && idx<num) if (this.idxs[idx++].substr(0,4)===':DL:') num++;
          var tgts = {};
          this.idxs.slice(num).forEach(function(name){
            if (name.substr(0,4)===':DL:' || !schedule) this.remove_thread(name);
            else tgts[name] = this.get_latest_time_of_a_thread(name);
          },this);
          if (Object.keys(tgts).length>0) this.INST.threads_candidates_of_deletion = tgts;
//          if (schedule) {if (this.INST.threads_candidates_of_deletion===null) this.INST.threads_candidates_of_deletion = {};} // working code
////          else if (pref.test_mode['127']) triage.off_delay();
//          while (this.idxs.length>num) {
//            var name = this.idxs[num];
//            if (name.substr(0,4)===':DL:' || !schedule) this.remove_thread(name);
//            else {
//              this.INST.threads_candidates_of_deletion[name] = this.get_latest_time_of_a_thread(name);
//              num++;
//            }
//          }
        },
        show_catalog_scroll_lock_factory: function(clg){
          var marks = null;
  //        var set = Watchdog.prototype.restart.bind(new Watchdog(scroll_back, 5));
          var wdg  = new Watchdog(scroll_back, 5);
          var wdg2 = new Watchdog(tgt_lock_cancel, 100); // 500ms scroll lock and watchdog for oscillation.
          var set = Watchdog.prototype.restart.bind(wdg);
          function tgt_lock_cancel(){
            wdg.cancel();
            marks = null;
          }
          function scroll_back(){
//            if (typeof(marks)==='number') {
//              var marked_first_post = site2[site.nickname].mark_newer_posts2(clg.merge_bases.bases[site.key].posts, marks, pref.thread_reader.unmark_on_hover, true) || null;
//              if (marked_first_post) scrollTo(0,marked_first_post.offsetTop - site.header_height());
//              else scrollTo(0,document.body.clientHeight - window.innerHeight); // to the last
//              return;
//            }
            var mark_top = marks[0].offsetTop || 0;
            var abs_top = mark_top - marks[1] + marks[2];
            if (abs_top<0) abs_top = 0;
            if (!mark_top || clg.get_now_height()===abs_top) { // may cause oscillation
  //            marks = null;
  //             wdg2.stop(); // scroll lock
              return;
            }
            window.scrollTo(0, abs_top);
            set();
          }
          var last_viewed = null;
          function start(pn){
            if (marks===null || pn) {
  //            if (marks && pn) scroll_back();
              if (!pn) pn = (wdg2.id)? last_viewed : // scroll locked
                            clg.GEH.get_last_viewed() || last_viewed; // return null when initial or deleted
              if (pn) {
                marks = [pn, pn.offsetTop, clg.get_now_height()];
                last_viewed = pn;
//              } else {if (gGEH.time_jumped) marks = gGEH.time_jumped;}
              } else return;
  ////            if (last_viewed) { // working code.
  ////              marks = [last_viewed, last_viewed.offsetTop, get_now_height()];
  ////              set();
  ////            }
  //            var now_height = get_now_height();
  //            var last_top; // working code.
  //            if (last_viewed) last_top = last_viewed.offsetTop;
  //            if (last_viewed && last_top >= now_height && last_top <= now_height + window.innerHeight) marks = [last_viewed, last_top, now_height];
  //            else { // scrolled
  //              var posts = (embed_mode==='thread')? site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'thread_html', {thread:site.no})[0].posts :
  //                                                   site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'page_html', {page:0})[0].posts;
  //              var step = posts.length >> 1;
  //              var i = 0;
  //              while (step>0) {
  //                if (posts[i+step].pn.offsetTop<now_height) i += step;
  //                step >>= 1;
  //              }
  //              while (i<posts.length-1 && posts[i].pn.offsetTop<now_height) i++;
  //              marks = [posts[i].pn, posts[i].pn.offsetTop, now_height];
  //            }
            }
            wdg2.restart();
            set();
          }
          return {
            modified: function(tgt){
  //            start((tgt.pn && marks[0]===tgt.pn)? site2[site.nickname].general_event_handler[site.whereami].get_prev_mark(tgt.pn) : null); // fail if get_prev_mark returns undefined;
              if (tgt.pn && marks && marks[0]===tgt.pn) {
                scroll_back();
                marks = null; // get_prev_mark may return undefined
  //              start(site2[site.nickname].general_event_handler[site.whereami].get_prev_mark(tgt.pn));
                start(site2[site.nickname].general_event_handler[site.whereami].get_mark_from_height(tgt.pn.offsetTop));
                clg.GEH.clear_last_viewed();
              } else start();
            },
            set: start,
  //          viewed: function(tgt){if (!marks) last_viewed = tgt;},
  //          viewed: function(tgt){
  //            if (!wdg2.id)
  //              last_viewed = tgt;}, // scroll lock
          }
        },
        get_now_height: function(){
          return (this.mode!=='float')? brwsr.document_body.scrollTop : this.ppn.scrollTop;
        },
        get_ref_height: function(threshold){
//        if (!threshold) threshold = 1.5;
//        return (embed_embed)? brwsr.document_body.scrollTop + window.innerHeight * threshold // slow.
//                            : triage_parent.scrollTop + triage_parent.clientHeight * threshold;
          if (this.innerHeight===null) {
            this.scrollTop = this.mode==='float'? this.pppn.scrollTop + this.pppn.offsetTop : brwsr.document_body.scrollTop; // this causes inconsistency of this.drawn_y at this.mode==='float', but OK, because embed<->float change invokes 'show_catalog_delayed' and recalculate this.scrollTop always.
            this.innerHeight = this.mode==='float'? (parseInt(this.pppn.style.height,10) || null) : window.innerHeight; // for auto
//            this.innerHeight = this.mode==='float'? this.pppn.clientHeight : window.innerHeight;
          }
          return (this.innerHeight===null)? NaN : this.scrollTop + this.innerHeight * threshold;
        },
        show_catalog_delayed: function(){
          if (this.drawn_idx!==true) this.show_catalog();
          else if (this.func_lazy_draw) this.func_lazy_draw();
          if (this===pClg) { // patch
            cataLog.DIH.expand_thumbnail_on_demand_kick();
          }
        },
        show_catalog_scroll: function(e){
          var y_new = e.currentTarget.scrollTop || window.scrollY;
          var y_old = this.scrollTop;
          this.scrollTop = y_new;
          if (y_old<y_new) this.show_catalog_cont();
        },
        show_catalog_resize: function(e){
          this.innerHeight = null;
          this.show_catalog_cont();
        },
        Show_catalog_buf: (function(){
          var Buf = function(clg){
            this.clg = clg;
            this.db = new DelayBuffer(this.do.bind(this), 10);
            this.reset();
          };
          Buf.prototype = {
            do: function(){
              this.clg.show_catalog(this.tgts, this.arg1, this.arg2);
              this.reset();
            },
            reset: function(){
              this.tgts = {};
              this.arg1 = false;
              this.arg2 = false;
            },
            queue: function(name, arg1, arg2){
              if (typeof(name)==='string') this.tgts[name] = null;
              else if (Array.isArray(name)) for (var i=0;i<name.length;i++) this.tgts[name[i]] = null;
              else for (var i in name) this.tgts[i] = null;
              this.arg1 = this.arg1 || arg1; // ||= from chrome85
              this.arg2 = this.arg2 || arg2;
              this.db.delayed_do();
            },
          };
          return function(clg){
            return Buf.prototype.queue.bind(new Buf(clg));
          };
        })(),
      };
      pClg = new Clg(embed_embed, embed_mode); // primary catalog
      var pn12 = pClg.pn12;
      var pn12_0 = pClg.pn12_0;
      var pn12_1 = pClg.pn12_1;
      var pn12_0_2 = pClg.pn12_0_2;
      var pn12_0_4 = pClg.pn12_0_4;
      var board_sel = pClg.board_sel;
      var threads = pClg.threads;
      Footer.threads = pClg.threads;
      cataLog.threads = threads;
//      var threads_idx = pClg.idxs;
//      var pop_up_status = {};
//  Object.defineProperty(pClg,'idxs',{get:function(){return threads_idx;}, set:function(v){threads_idx = v;}}); // temporarily patch
//  Object.defineProperty(pClg,'order',{get:function(){return pref.catalog.order;}}); // temporarily patch
//  Object.defineProperty(pClg,'drawn_idx',{get:function(){return this.catalog_obj2.drawn_idx;}, set:function(v){this.catalog_obj2.drawn_idx = v;}}); // temporarily patch
  Object.defineProperty(pClg,'ppn',{get:function(){return triage_parent;}, set:function(v){triage_parent = v;}}); // temporarily patch
      gClg = pClg;
      cataLog.gClg = gClg;
      cataLog.get_ref_height = pClg.get_ref_height;
      Clg.prototype.AllThreads = pClg.threads;

      function reentry_0_patch(clg){
        cataLog.gClg = gClg = pClg = clg;
        clg.threads = threads;
//  Object.defineProperty(pClg,'idxs',{get:function(){return threads_idx;}, set:function(v){threads_idx = v;}}); // temporarily patch
        cataLog.get_ref_height = pClg.get_ref_height;
        Clg.prototype.AllThreads = pClg.threads;
        for (var i in threads) delete threads[i][1];
        while (clg.idxs.length!=0) clg.idxs.shift();
        clg.onchange_funcs['*.board_list_sel'](null,true, true);
      }

      function hide_prev_float(e){
        cnst.tack_float_nSblgs(e, true);
//        site.script_body.appendChild(e.currentTarget.nextSibling);
        e.currentTarget.previousSibling.style.display = 'none'; // must be AFTER float not to change offsetLeft.
      }
      function show_prev_dock(e){
        var tack = cnst.tack_dock_e2tack(e);
//        tack.parentNode.insertBefore(e.currentTarget.parentNode,tack);
        tack.previousSibling.style.display = null;
        cnst.tack_dock_nSblgs(e);
      }

      cataLog.components = pClg.components;
//      if (embed_mode==='catalog' || embed_mode==='page' || embed_mode==='thread') {
//        pn12_0_2.getElementsByTagName('span')['hide_at_embed'].style.display = 'none';
//        pn12_0_4.getElementsByTagName('div')['hide_at_embed'].style.display = 'none';
//      }
//      var hide_target = (pref.catalog.text_mode.mode==='text')? 'catalog.text_mode.mode.graphic' : 'catalog.text_mode.mode.text';
//      pn12_0_4.getElementsByTagName('span')[hide_target].style.display = 'none';
      if (embed_mode==='thread') pref_func.pref_overwrite(pref.filter,pref_default().filter);
//      filter_kwd_prep_init(null, pref.filter.kwd,true); // prep kwd.rexps

////      pref_func.tooltips.add_hier(pn12_0_4);
//      pref_func.tooltips.add_root(pn12_0_4);
////      if (pref[embed_mode].auto_config_posts_search) auto_config_posts_search_deactivate();
//      pref_func.apply_prep(pn12_0_4,false, null, null, true);
////      pref_func.apply_prep(pn12_0_4,true); // obj init.
//      var pn_filter = pn12_0_4.childNodes[1];
      window.addEventListener('beforeunload', window_beforeunload, false);
//  pClg.onchange_funcs = {'.':pClg, __proto__:onchange_funcs}; // temporal, this can be consolidated.
//      pref_func.add_onchange(pn12_0_2,pClg.onchange_funcs, oninput_funcs); // causes 1 leak.
//      cataLog.event_func = pref_func.add_onchange(pn12_0_4,pClg.onchange_funcs, oninput_funcs); // causes 1 leak.
//console.log('init_onchange');
      window.addEventListener('storage', site2[site.nickname].prep_own_posts_event, false);
//      site2[site.nickname].prep_own_posts();
      site2[site.nickname].check_reply.make_own_posts(); // prevent from calling twice.
      function window_beforeunload() {
        var done = {};
        if (pref.catalog.auto_save_filter) for (var i=0;i<gClg.Clgs.length;i++) {
          var clg = gClg.Clgs[i];
          if (clg.mode!=='thread') {
            var sel = clg.pref[clg.mode].board_list_sel;
            if (done[sel]===undefined) {
              clg.onchange_funcs.save();
              done[sel] = null;
            }
          }
        }
//        if (pref.catalog.auto_save_filter && embed_mode!=='thread') onchange_funcs.save();
        window.removeEventListener('beforeunload', window_beforeunload, false);
      }
      if (pref.virtualBoard.scan && embed_mode!=='thread')
        setTimeout(function(){scan.keyword_load(false, health_indicator);}, pref.virtualBoard.scanDelay*1000);
      
//      var pn_setting = pn12_0_4.childNodes[0];
      var triage_parent;
      triage_parent_set();
      function triage_parent_set(){
        triage_parent = (embed_embed)? site2[site.nickname].catalog_get_native_area() : pn12_1;
        cataLog.parent = triage_parent;
        pClg.ppn = triage_parent;
        pClg.pppn = pClg.ppn.parentNode;
        pClg.set_view_class();
      }
      cataLog.triage_parent_set = triage_parent_set;
//      if (pref.catalog.auto_load_filter) onchange_funcs.load();
      pClg.onchange_funcs['*.board_list_sel'](null,true);

//      var tags_scan_regex = new RegExp('#[^#, \.:;\n]+(?=#|,| |\.|:|;|\n|$)','g'); // ATTENTION. REFER function prep_tag_str(); // AND ALSO IN scan_tag_obj.
      var scan_boards = (function(){
//        var scan_button   = pn12_0_4.getElementsByTagName('button')['scanSite'];
////////        var scan_boards   = {args:{}, crawler_timer:null, pool:null};
        var scan_boards   = {args:{}, pool:null};
////////        var scan_progress = (function(){
////////          var elem = pn12_0_4.getElementsByTagName('span')['scan_progress'];
////////          var timer = null;
////////          var str = '';
////////          function show(){elem.innerHTML=str; timer=null;}
////////          return function(s){str=s; if (timer===null) timer = setTimeout(show,100);};
////////        })();
////////        function keyword_load(key){ // working code.
////////          if (scan_boards_check_pre(key,true)) {
//////////            if (pref.common.clear_at_manual_scan && !keep) onchange_funcs.clear_threads();
////////            scan_button.innerHTML = 'Cancel';
////////            if (!site3[site.nickname].boards) {
////////              http_req.get(key,site.nickname,site2[site.nickname].url_boards_json(),scan_boards_keyword_callback,pref.scan.lifetime*60,true,key);
////////              scan_progress('Loading boards\' information');
////////            } else {
////////              var tgts = site2[site.nickname].enumerate_boards_to_scan(); // must remake to evaluate change of preference.
////////              scan_boards_init(key, tgts, {lifetime:pref.scan.lifetime*60, cache_write:true,
////////                                           callback:(pref.liveTag.use)? catalog_liveTag_scan_threads : null, callback_args:'scan'});
////////            }
////////          }
////////        }
        function scan_boards_check_pre(key){
          if (scan_boards.args[key]) {
            if (scan_boards.args[key].max != scan_boards.args[key].idx) {
              scan_boards.args[key].max = scan_boards.args[key].idx;
              if (scan_boards.args[key].INDICATOR) scan_boards.args[key].INDICATOR.report({abort_str:'canceled'});
            } else {
////////              if (scan_boards.args[key].crawler_watchdog) scan_boards.args[key].crawler_watchdog.stop();
////              crawler.watchdog.abort(key);
              if (scan_boards.args[key].INDICATOR) scan_boards.args[key].INDICATOR.report({abort_str:'aborted', end:Date.now()});
              delete scan_boards.args[key];
            }
            return false;
          } else return true;
        }
//        function scan_boards_keyword_init(key){
//          scan_boards_init(key, site3[site.nickname].boards,pref.filter.tag_scansite,pref.filter.tag_scansite,pref.scan.lifetime*60);
//          var obj = [];
//          obj.max_threads = pref.scan.max_threads;
//          var count = pref.scan.max;
//          for (var i in site3[site.nickname].boards) if (site3[site.nickname].boards[i].max) {
//            obj.push('/'+site3[site.nickname].boards[i].uri+'/');
//            if (--count==0) break;
//          }
//          scan_boards_init(key, obj, {lifetime:pref.scan.lifetime*60, cache_write:true});
//        }
        function scan_init(key,mem,args, multi_entry){
if (pref.debug_mode['5']) console.log('scan_init: '+key);
          if (!multi_entry && !scan_boards_check_pre(key,false)) return;
          if (!Array.isArray(mem)) mem = Object.keys(mem);
          if (mem.length==0) {
            if (args.callback) args.callback(args.callback_args);
            return;
          }
          scan_boards_init(key, mem, args);
        }
        scan_boards.args_proto = { // for httpd
          callback_1: function(req,value,REQ){
            scan_boards_keyword_callback2(req.key2||'',value,[req.url,REQ]); // req.key2||'' for over limit error.
          },
          get_tgt_func: function(req){
            if (!(req.IDX<req.max && (req.refresh || req.found_threads<req.max_threads))) {req.ABORT = true; req.UPPERLIMIT = true; return;}
            do {
              var tgt = scan_boards_keyword_make_tgt(req,req.IDX++);
            } while (req.IDX<req.max && (!tgt || (pref.catalog.board.ex_list && pref_func.merge_obj5(tgt,pref.catalog.board.ex_list_obj2,null))));  // 'if (tgt)' is for tgts_iterator
            if (req.IDX<=req.max) {
              var archived = scan.list_nup.issued(tgt);
              var dbt = comf.name2dbt(tgt);
              var url = site2[dbt[0]].make_url4(dbt);
              var key2 = dbt.join(',');
              var retval = {REQ:req, url:url[0], responseType:(pref.test_mode['165'] || archived)? 'text' : (url[1]==='html' || url[1]==='xml')? 'document' : url[1], tgt:tgt, key2:key2, data_type:url[1], domain:dbt[0]};
              if (url[3]) for (var i in url[3]) retval[i] = url[3][i];
              return retval;
            }
          },
          callback: function(req){
            if (req.ABORT) req.INDICATOR.report({err:'Aborted.'+(req.UPPERLIMIT? '(Reached upper limit)':'')});
            delete scan_boards.args[req.key];
            if (Object.keys(scan_boards.args).length==0) scan_boards.pool = null;
            if (req.callback2) req.callback2(req.callback_args);
          },
          initialize: function(){
            Object.defineProperty(this,'idx',{get:function(){return this.IDX;}, configurable:true}); // for test.
//            Object.defineProperty(this,'indicator',{get:function(){return this.INDICATOR;}, configurable:true}); // for test.
            Object.defineProperty(this,'tgts',{get:function(){return this.obj;}, configurable:true}); // for test.
            Object.defineProperty(this,'initiator',{get:function(){return this.key;}, configurable:true}); // for test.
            if (this.hasOwnProperty('callback')) {
              this.callback2 = this.callback;
              delete this.callback;
            }
            return this;
          },
        };
        scan_boards.args_proto.callback_1_fail = scan_boards.args_proto.callback_1;
        function scan_boards_init(key, obj, args){
          if (!scan_boards.pool) scan_boards.pool = {
            div: document.createElement('div'), parser: new DOMParser(), doc: null, ths: null, dbt: null, tgts: null, tags:{cs:{}, ci:{}},
            name:null, sticky:null, type:null, wrapper_obj:null};
  //        var obj = site3[site.nickname].boards;
          scan_boards.args[key] = {
                key: key,
                idx: 0,
//                max: (obj.length>pref.scan.max)? pref.scan.max : obj.length,
                max: obj.length,
//                max_threads: (obj.length>pref.scan.max)? obj.length : pref.scan.max,
                max_threads: pref4.scan.max_threads,
                found_threads: 0,
                found_boards: 0,
                scanned: 0,
////////                error: '',
//                error_obj: {},
                crawler: 0,
////////                crawler_max : pref.scan.crawler,
                obj: obj,
//                scan_tag: pref.filter.tag_scansite,
//                store_tag: pref.filter.tag_scansite,
                lifetime: 0,
                cache_write: false,
                callback : null,
                callback_args : null,
                pool: scan_boards.pool,
                refresh: null,
                indicator: null,
                tgt_raw: false,
                watchdog: null,
////////                spawn_crawler: function(){scan_boards_spawn_crawler(key);},
////////                crawler_watchdog: null,
                from_auto: null,
                load_on_demand: null,
//                tag_only: null,
                senders_returned:[],
                priority: 0,
                tgts_iterator: null,
                done_list: null,
            __proto__: scan_boards.args_proto
          };
          for (var i in args) scan_boards.args[key][i] = args[i];
          var sb = scan_boards.args[key];

          if (sb.load_on_demand && !sb.tgts_iterator) {
////            sb.max = 1; // working code.
////            for (var i=obj.length-1;i>=1;i--) {
////              var name = ':DL:'+ sb.obj[i];
////              var idx = threads_idx.indexOf(name);
////              if (idx!=-1) threads_idx.splice(idx,1);
////              threads_idx.unshift(name);
////            }
            for (var i=0;i<sb.obj.length;i++) {
              var tgt = scan_boards_keyword_make_tgt(sb,i);
              var dbt = comf.name2domainboardthread(tgt,true);
              if (dbt[2].search(/[cjpq][1-9]/)===0) {
                pClg.idx_insert_odl(tgt);
                sb.obj.splice(i--,1);
              }
            }
            if (sb.obj.length===0) {
//              if (sb.indicator) sb.indicator.remove();
              if (sb.callback) {
                var func = sb.callback
                var args = sb.callback_args;
              }
              delete scan_boards.args[sb.key];
              return (func)? func(args) : undefined;
            } else {
              if (pClg.load_on_demand_try_get_mutex()) sb.callback = (sb.callback)? (function(func_old){return function(){pClg.load_on_demand_release();func_old();}})(sb.callback) : pClg.load_on_demand_release.bind(pClg); // First access may not have mutex.
//              if (load_on_demand.get()) sb.callback = (sb.callback)? (function(func_old){return function(){load_on_demand.release();func_old();}})(sb.callback) : load_on_demand.release; // First access may not have mutex.
              sb.max = sb.obj.length;
              pClg.drawn_idx = 0;
            }
          }

          httpd.req(sb.initialize(), (sb.priority<7)? sb.priority+1 : 1); // patch
          
////          if (!sb.indicator) sb.indicator = health_indicator.shift('limegreen','r',sb.key,sb.priority); // working code.
////          sb.indicator.report({start:Date.now(), prog:sb}); // make reference loop.
////          while (sb.crawler<pref.scan.crawler) {
////////////            scan_boards_spawn_crawler(key, true);
////            crawler.spawn(key, true);
////            if (pref.scan.crawler_adaptive) break;
////          }
        }
////        var crawler = { // working code.
////          key: null,
////          priority: 0,
////          num: 0, // dangerous, may hang up, all crawlers must be returned.
////          wdg_spawn: null,
////          spawn_req: function(sb){
////            this.clear_req(sb);
////            if (sb.priority>=this.priority) {
////              if (this.num<pref.scan.crawler || sb.priority>this.priority) {
////                this.wdg_spawn.start(pref.scan.crawler_idle_time_to_spawn);
////                this.key = sb.key;
////                this.priority = sb.priority;
////              }
////            }
////          },
////          spawn: function(key, init){
////            var sb = scan_boards.args[(key || this.key)]; // not to keep greping 'sb' object, 'key' and 'priority' are primitives.
////            if (sb && sb.idx<sb.max) {
////              var sender = (sb.senders_returned.length>0)? sb.senders_returned.shift() : sb.key+':'+sb.crawler;
////              sb.crawler++;
////              this.num++;
////              this.watchdog.add(sb, sender);
////              if (pref.debug_mode['5']) console.log('crawler_spawn: '+sender+'/'+sb.crawler);
////              scan_boards_keyword([sender,sb],200);
////              if (!init && !pref.scan.crawler_adaptive) this.spawn_req(sb);
////            }
////          },
////          clear_req: function(sb){
////            if (sb.priority>=this.priority) this.wdg_spawn.stop();
////          },
////          check_priority: function(sb, sender){
////            if (sb.priority>=this.priority || sb.crawler==1) return true;
////            else {
////              this.finish(sb, sender);
//////              sb.senders_returned.push(sender);  // THIS CAUSED A BUG,
////                                                   // crawler with status 1200(now changed to -200) call 'finish' immediately,
////                                                   // but this is respawned and causes multiple call of 'watchdog.add'.
////                                                   // For easy debug, this was moved to 'finish'.
////              return false;
////            }
////          },
////          finish: function(sb, sender){
////            this.clear_req(sb);
////            http_req.close(sender);
////            this.watchdog.end(sender);
////            sb.senders_returned.push(sender); // shold be here, but slightly redundant for end.
////            sb.crawler--;
////            this.num--;
////            if (pref.debug_mode['5']) console.log('crawler_finish: '+sender+'/'+sb.crawler);
////          },
////          finish_all: function(sb){
////            this.priority = 0;
////            if (Object.keys(scan_boards.args).length==0) this.reset(); // for safety.
////            else for (var key in scan_boards.args) if (scan_boards.args[key].senders_returned.length>0) this.spawn(key);
////          },
////          reset: function(){
////            this.num = 0;
////            this.watchdog.reset();
////          },
////          watchdog: {
////            list: [],
////            sbs: Object.create(null),
////            add: function(sb,sender){
////              this.list.push(sender);
////              this.sbs[sender] = sb;
////              if (this.list.length===1) this.wdg.start();
////            },
////            end: function(sender){
////              this.report_alive(sender);
////              delete this.sbs[sender];
////              this.list.splice(this.list.indexOf(sender),1);
////              if (this.list.length===0) this.wdg.stop();
//////              else this.wdg.restart(); // this is redundant because report_alive exists before
////            },
////            wdg: null,
////            report_alive: function(sender){
////              if (sender===this.list[0]) {
////                this.list.push(this.list.shift());
////                this.wdg.restart();
////              }
////            },
////            respawn: function(){
////              var sender = this.list[0];
////              var sb = this.sbs[sender];
////              if (pref.debug_mode['7']) console.log('crawler_respawn: '+sender+'/'+((sb)?sb.crawler:'undefined'));
////              scan_boards_keyword([sender,sb],200);
////              this.report_alive(sender);
////            },
////            reset: function(){
////              this.list = [];
////              this.sbs = Object.create(null);
////              this.wdg.stop();
////            },
////            abort: function(key){
////              for (var i=this.list.length-1;i>=0;i--) if (this.list[i].indexOf(key+':')===0) this.end(this.list[i]);
////            },
////          },
////          priority_up: null,
////          priority_release: null,
////          priority_wdg: null,
////          priority_count: 0,
////        };
////        crawler.wdg_spawn = new Watchdog(crawler.spawn.bind(crawler), 100);
////        crawler.watchdog.wdg = new Watchdog(crawler.watchdog.respawn.bind(crawler.watchdog), 30000);
////        crawler.priority_up = function(p){
////                                this.priority = p;
////                                this.priority_count++;
////                                this.priority_wdg.start();
////                              }.bind(crawler);
////        crawler.priority_release = function(){
////                                     if (--this.priority_count<=0) {
////                                       this.priority=0;
////                                       this.priority_wdg.stop();
////                                     } else this.priority_wdg.restart();
////                                   }.bind(crawler);
////        crawler.priority_wdg = new Watchdog(crawler.priority_release, 10000);

////////        function scan_boards_spawn_crawler_timer(sb){ // working code.
////////          scan_boards_crawler_timer_clear();
////////          if (sb.crawler<sb.crawler_max) scan_boards.crawler_timer = setTimeout(sb.spawn_crawler, pref.scan.crawler_idle_time_to_spawn);
////////        }
////////        function scan_boards_spawn_crawler(key, init){
////////          var sb = scan_boards.args[key];
////////          if (pref.debug_mode['5']) console.log('crawler_spawn: '+sb.key+', '+(sb.crawler+1));
////////          scan_boards_keyword([sb.key+sb.crawler++,sb],200);
////////          if (!init && sb.idx<sb.max) scan_boards_spawn_crawler_timer(sb);
////////        }
////////        function scan_boards_crawler_timer_clear(){
////////          if (scan_boards.crawler_timer!==null) {
////////            clearTimeout(scan_boards.crawler_timer);
////////            scan_boards.crawler_timer=null;
////////          }
////////        }
////////        var CrawlerWatchdog = function(sb){ // patch for 8chan's unstability.
////////          this.sb = sb;
////////          this.tgt = 0;
////////          this.db = new DelayBuffer(this.respawn.bind(this), 30000);
////////          this.db.delayed_do();
////////        }
////////        CrawlerWatchdog.prototype = {
////////          report_alive: function(kwd){
////////            if (!this.sb) return; // stopped already
////////            var me = kwd.substr(this.sb.key.length);
////////            if (this.tgt==me) {
////////              this.tgt = (this.tgt<this.sb.crawler-1)? this.tgt+1 : 0;
////////              this.db.cancel();
////////              this.db.delayed_do();
////////            }
////////          },
////////          respawn: function(){
////////            if (this.sb.idx<this.sb.max) {
////////              if (pref.debug_mode['7']) console.log('crawler_respawn: '+this.sb.key+this.tgt+'/'+this.sb.crawler);
////////              scan_boards_keyword([this.sb.key+this.tgt,this.sb],200);
////////              this.report_alive(this.sb.key+this.tgt);
////////            } else {
////////              if (pref.debug_mode['7']) console.log('crawler_respawn: '+this.sb.key+this.tgt+'/'+this.sb.crawler+', stopped.');
////////              this.stop();
////////            }
////////          },
////////          stop: function(){
////////            this.db.cancel();
////////            this.sb = null; // release pointer
////////          }
////////        }
        function scan_boards_keyword_make_tgt(sb,idx){
          var tgt = (sb.tgts_iterator)? sb.tgts_iterator.next().value : sb.obj[idx];
          if (tgt && typeof(tgt)!=='string') {
            var force_json = tgt.u&0x0080;
            tgt = tgt.key;
          }
          var dbt;
          if (tgt) dbt = cnst.name2domainboardthread(tgt,true);
          return (!tgt)? tgt:
                 (sb.tgt_raw)? tgt :
                 (dbt[2]==='')? tgt + ((pref.catalog.catalog_json | force_json)? 'j0' : 'c0') :
                 (pref.catalog.catalog_json && dbt[2][0].search(/[0-9]/)!=-1)? dbt[0]+dbt[1]+'t'+dbt[2] : tgt;
        }
////        function scan_boards_keyword(args,status){ // working code.
////          var sender = args[0];
////          var sb = args[1];
////          crawler.watchdog.report_alive(sender);
////          if (sb.watchdog) sb.watchdog();
////          if (pref.scan.crawler_adaptive) if (!crawler.check_priority(sb,sender)) return;
////////////          if (sb.crawler_watchdog) sb.crawler_watchdog.report_alive(sender);
//////if (pref.debug_mode['5']) console.log('request_entry: '+sb.idx+'/'+sb.max+', '+sb.found_threads+'/'+sb.max_threads+', '+status);
////if (pref.debug_mode['5'] && sb.idx!=0 && sb.idx%1000==0) console.log('request_progress: '+sb.key+', '+sb.idx+'/'+sb.max+', '+sb.found_threads+'/'+sb.max_threads+', '+status);
////          if (sb.idx<sb.max && (sb.refresh || sb.found_threads<sb.max_threads) && status<500) {
////            var tgt = scan_boards_keyword_make_tgt(sb,sb.idx);
////            sb.idx++;
////////////            scan_progress(sb.found_threads+'/'+sb.scanned+', '+sb.found_boards+'/'+sb.idx+'/'+sb.max+', ' + tgt);
////            if (tgt && (!pref.catalog.board.ex_list || !pref_func.merge_obj5(tgt,pref.catalog.board.ex_list_obj2,{hit:false}).hit)) { // 'if (tgt)' is for tgts_iterator
////              if (sb.indicator) sb.indicator.report({tgt:tgt});
////              http_req.get(sender,tgt,'',scan_boards_keyword_callback2,sb.lifetime,sb.cache_write,args, scan.list_nup.issued(tgt));
////////////              if (sb.idx<sb.max && pref.scan.crawler_adaptive) scan_boards_spawn_crawler_timer(sb);
////              if (pref.scan.crawler_adaptive) crawler.spawn_req(sb);
////            } else scan_boards_keyword(args,200);
////          } else {
////            crawler.finish(sb, sender);
////////////            http_req.close(sender);
////            if (sb.crawler===0) {
////////////              if (pref.scan.crawler_adaptive) scan_boards_crawler_timer_clear();
////////////              if (sb.crawler_watchdog) sb.crawler_watchdog.stop();
////////////              var prog_str = sb.found_threads+'/'+sb.scanned+', '+sb.found_boards+'/'+sb.max
////////////                            + '<span style="color:red">'
////////////                            +( (sb.error!=='')? ', Error at loading '+sb.error :
////////////                              ((!sb.refresh && sb.found_threads>=sb.max_threads)? ', Aborted.(Reached upper limit)' : ''))
////////////                            + '</span>';
////////////              scan_progress(prog_str);
//////              sb.max = sb.idx;
//////              if (sb.key==='scan') scan_button.innerHTML = 'scanSite';
////if (pref.test_mode['22']) {
////              if (sb.store_tag) {
////  //console.log(new Date());
////  //              scan_tags_init(sb.pool.tags);
////                var ret_obj = scan_tags_common_b(sb.pool.tags,'',site3[site.nickname].tags);
////                site3[site.nickname].tags = ret_obj[1];
////                scan_tags_init(ret_obj[0],true);
////  //              sb.pool.div.innerHTML = '';
////  //console.log(new Date());
////              }
////}
//////              sb = null;
////              if (sb.indicator) {
////////////                var keys = Object.keys(sb.error_obj);
////////////                if (keys.length==0) sb.indicator.set(null,'\u25cf');
////////////                else if (keys.length>=sb.max) sb.indicator.set('red','X');
////////////                else sb.indicator.set(null,'\u25b2');
////                if (sb.found_threads>=sb.max_threads) sb.indicator.report({err:'Aborted.(Reached upper limit)'});
////                sb.indicator.report({end:Date.now()});
////              }
////              delete scan_boards.args[sb.key];
////if (pref.debug_mode['5']) console.log('scan_boards_end: '+sb.key+', Running:'+Object.keys(scan_boards.args));
////              crawler.finish_all(sb);
////              if (Object.keys(scan_boards.args).length==0) scan_boards.pool = null;
////              if (sb.callback) sb.callback(sb.callback_args);
////            }
////          }
////        }

        cataLog.scan_boards_keyword_callback2_default_args = {refresh:pClg, found_threads:0, get max_threads(){return pref4.scan.max_threads;}, found_board:0, scanned:0};
        var threads_meguca = {};
        function scan_boards_keyword_callback2(key,value,args,    ths_in){ // requires snoop and on demand rendering for merge.
////////  if (pref.scan.crawler_adaptive) scan_boards_crawler_timer_clear();
          var sb = args[1];
          var dbt = key.split(',');
////          if (pref.scan.crawler_adaptive) crawler.clear_req(sb);
          if ((value.status==200 || value.status==304) && value.response && sb.found_threads<sb.max_threads) { // Checking 'value.response' is a patch for 8chan's inconsistency, 8chan sometimes return 200 with null. // checking 304 is a patch.
            var sb_source = dbt[3].slice(0,dbt[3].indexOf('_')); // dbt[3]='' when it is aborted. this line must be internal of value.status==200.            
if (dbt[0]==='meguca1' && dbt[3]==='catalog_json') { // PATCH FOR MEGUCA
            for (var i=0;i<value.response.length;i++) {
//              threads_meguca[dbt[1]+value.response[i]] = {page:parseInt(i/10) +'.'+ i%10, refresh:sb.refresh};
              liveTag.mems.init({domain:dbt[0], board:dbt[1], no:value.response[i]});
              threads_meguca[dbt[1]+value.response[i]] = {page:parseInt(i/10) +'.'+ i%10, refresh:sb.refresh || threads_meguca[dbt[1]+value.response[i]] && threads_meguca[dbt[1]+value.response[i]].refresh}; // patch.
              scan.list_nup.add(dbt[0]+dbt[1]+value.response[i], sb.priority);
//              if (pref.liveTag.use) scan.list_nup.add_board(dbt[0]+dbt[1], value.date);
            }
            scan.list_nup.got_200_board(dbt[0],dbt[1]);
} else {
////            var tgts = {};
////            if (!pref.test_mode['133']) { // new Array(gClg.Clgs.length).fill({});
////              tgts = [];
//              for (var i=0;i<gClg.Clgs.length;i++) {
////                tgts[i] = {};
//                gClg.Clgs[i].format_refresh_tgts();
//              }
////            }
//            var ths_info = []; // for test_mode['133']
            var parse_options = (pref.test_mode['182'] && sb_source==='catalog')? null
                              : (sb_source==='thread')? {thread:dbt[2]} : {page:dbt[2]};
            if (sb.localArchive) { // to be moved to archiver
              if (!parse_options) parse_options = {};
              parse_options.localArchive = sb.localArchive;
              parse_options.archiveFile = sb.archiveFile;
              parse_options.page = sb.page;
            }
            var ths = ths_in || site2[dbt[0]].wrap_to_parse.get(value.response, dbt[0], dbt[1], dbt[3], parse_options, sb);
            if (sb.localArchive) {
              for (var i=0;i<ths.length;i++) ths[i].key = ths[i].key.substr(0,ths[i].key.lastIndexOf('/')+1) + ths[i].posts[0].no;
              if (ths.length===1) dbt[2] = ths[0].posts[0].no;
            }
            if (sb.force_annotate) { // for reentry in meguca.
              for (var i=0;i<ths.length;i++) if (threads[ths[i].key] && ths[i].pn && threads[ths[i].key][0]!==ths[i].pn) {
                threads[ths[i].key][0] = ths[i].pn;
                threads[ths[i].key][1] = true;
                pClg.insert_thread_format_th_pn(threads[ths[i].key], ths[i]);
//                threads[ths[i].key][24] = prep_footer3(ths[i]);
                threads[ths[i].key][16].posts = ths[i].posts;
              }
            }
            sb.scanned += ths.length;
//////if (pref.test_mode['22']) {
//////            var tgt_pn = sb.pool.div;
//////            var from_catalog = dbt[3]==='catalog_json' || dbt[3]==='catalog_html';
//////            var filter_active = pref.filter.kwd.active || (pref.filter.tag && filter_tags.length!=0);
//////} else {
////            var filter_active = pref.filter.kwd.active || (pref.filter.tag && pref.liveTag.use && liveTag.active.in);
////            var filter_active = pref.filter.kwd.active || (pref.filter.tag && liveTag.active.in); // for passive but don't use.
//            var filter_active_kwd = pref.filter.kwd.active;
//            var filter_active_tag = pref.filter.tag && liveTag.active.in; // for passive
            var filter_needs_wrapping = site2[dbt[0]].differentAPI && gClg.Clgs.filter(function(v){v.pref.filter.kwd.posts_active;}).length>0;
////}
////            var from_json = dbt[3].indexOf('_json')!=-1;
            var refresh = sb.refresh;
            if (dbt[0]==='meguca1' && dbt[3]==='thread_json') { // PATCH FOR MEGUCA
              var tgt_th_meguca = threads_meguca[dbt[1]+dbt[2]];
              if (tgt_th_meguca!== undefined) {
                refresh = tgt_th_meguca.refresh;
                ths[0].page = tgt_th_meguca.page;
                delete threads_meguca[dbt[1]+dbt[2]];
              }
            }
            var post_updated_static = !site2[dbt[0]].passiveWatch && (pref.liveTag.from==='post' || pref.stats.use && !pref.test_mode['167'] || pref[embed_mode].deleted_posts.detect.indexOf('full')===0 ||
                pref[embed_mode].storePosts==='ALL_agg' || site2[dbt[0]].parse_funcs[dbt[3]].has_editing && (embed_mode==='page' || embed_mode==='thread') ||
                pref[embed_mode].backlink_all);
//            var post_updated_static = pref.liveTag.from==='post' || pref.stats.use || pref[embed_mode].deleted_posts.detect==='full';
            var pf_store = (pref[embed_mode].storePosts==='ALL_agg' || pref[embed_mode].storePosts==='auto' && pref4.search_posts_active_once)? 'ALL' : pref[embed_mode].deleted_posts.detect; // the same code is in 'gClg.Maintain_AllThreads'
            if (pf_store==='no' && embed_mode==='page') pf_store = pref[embed_mode].t2h_num_of_posts;
            if (pref[embed_mode].detect_sage && dbt[0]==='4chan' && dbt[3]==='catalog_json') site2['4chan'].sage_detect_all(ths);
//            if (dbt[3]==='catalog_json' || dbt[3]==='catalog_html') { // merge_op.auto requires nos always
            var nos = Object.create(null);
            if (ths[0] && ths[0].parse_funcs.db2pnos) ths[0].parse_funcs.add_nos(nos, dbt[0]+dbt[1], dbt[2]);
            for (var i=0;i<ths.length;i++) nos[ths[i].no] = null;
//            }
            var sb_merge = sb.key==='mergeUI' || sb.key==='mergeAuto';
            var sb_merge_op_not_done = {};
            var pf_notify_new_thread = sb.from_auto && (pref.notify.desktop.notify && pref.notify.desktop.new_thread || pref.notify.sound.notify && pref.notify.sound.new_thread);
            var has_page = sb_source!=='thread';
            var clgs_args = [];
            for (var j=0;j<gClg.Clgs.length;j++) {
              var clg = gClg.Clgs[j];
              clg.format_refresh_tgts();
              clgs_args[j] = {
                refresh: refresh===clg ||
                         refresh && refresh.pref[refresh.mode].board_list_sel===clg.pref[clg.mode].board_list_sel && clg.pref[clg.mode].accept_others_refresh ||
                         clg.pref[clg.mode].promiscuous,
                ALL_agg: pref[embed_mode].storePosts==='ALL_agg', // CAUTION: embed_mode is used intentionally. // this.pref[this.mode];
                pf_fKwd: clg.pref.filter.kwd.active && clg.pref.filter.kwd,
                pf_fTag: clg.pref.filter.tag && clg.liveTag.active.in,
                pf_foPg: has_page && clg.footer.pref_fmt.page}; // clg.pref[clg.footer.mode_fmt].footer.page};
            }
            for (var i=0;i<ths.length;i++) {
              var th = ths[i];
              if (!th.no) continue; // PATCH for a bug in 4chan, 4chan returns invalid data.
//              th.time_loaded = value.date;
//              if (sb.native_prep) th.native_prep = true;
//if (pref.test_mode['33'] && dbt[0]==='meguca1' && dbt[3]==='thread_json') {scan.list_nup.got_200(th, liveTag.mems.init[th.domain][th.board]);continue;}
//////if (pref.test_mode['22']) {
//////              if (sb.scan_tag && from_catalog) {
//////                if (th.type_data==='json') tgt_pn.innerHTML = th.name + '\n' + th.sub + '\n' + th.com;
//////                else tgt_pn = th.pn;
//////                th.tags = tgt_pn[brwsr.innerText].match(tags_scan_regex);
//////              }
//////} else {
////var tmp_len = Object.keys(scan.list_nup).length;
////if (tmp_len!=0 && tmp_len%100===0) console.log('len: '+tmp_len);
//////////              if (pref.liveTag.use) {
              var lth = liveTag.prep_tags(th); // extract tags in op. // editing tags are updated in this. // lth.pd is prepared here.
//              var insert_thread_from_native_1 = insert_thread_from_native && insert_thread_from_native_prep(th, lth, insert_thread_from_native);
//              th.tags = lth;
              var watch = lth.wt;
              var tgt_th = gClg.AllThreads[th.key]; // equivalent to threads[th.key] when gClg.Clgs.length===1
//              var tgt_th = threads[th.key];
              var tag_updated; //  = (tgt_th)? undefined : th.tags; // force new thread to add tags
              var post_updated = (post_updated_static || (watch[0]&0x002c0000))? false : null; // archiving or watching
//              var post_updated = (post_updated_static || (watch[0]&0x000c0000))? false : null;

              if (lth.ed_t && th.parse_funcs.has_posts) { // lth.ed_t is created only when th.parse_funcs.has_editing is true. lth.ed_t is shared even if pref.liveTag.from!=='post'
                var ed_f = [];
                tag_updated = liveTag.update_tags_in_editing_posts(th, lth, ed_f) || tag_updated; // lth.ed_t is updated here. 
                if (ed_f.length===0) ed_f = null;
              }
//              if (th.parse_funcs.has_editing && th.parse_funcs.has_posts) {
//                var ed_p = lth.ed_p || lth.ed_t; // share lth.ed_t when pref.liveTag.from==='post'
//                if (ed_p) {
//                  var ed_f;
//                  var posts_obj = th.parse_funcs.posts_obj(th,ed_p[ed_p.length-1].no);
//                  for (var j=ed_p.length-1;j>=0;j--) {
//                    var post = posts_obj[ed_p[j]]; // post may be a tag.
//                    if (post && !post.editing) {
//                      if (ed_f) ed_f[ed_f.length] = post;
//                      else ed_f = [post];
//                      //// CODE FOR UPDATING ARCHIVES SHOULD BE HERE.
//                      //// CODE FOR UPDATING POSTS CAN BE HERE.
//                      if (lth.ed_p) {
//                        if (lth.ed_p.length!==1) lth.ed_p.splice(j,1);
//                        else lth.ed_p = null;
//                      }
//                    }
//                  }
//                  if (lth.ed_t) tag_updated = liveTag.update_tags_in_editing_posts(th, lth, posts_obj) || tag_updated; // lth.ed_t is updated here.
//                }
//              }

//              if (liveTag.mems[dbt[0]][dbt[1]] && liveTag.mems[dbt[0]][dbt[1]].f) scan.list_nup.add(th.key, sb.priority); // use dbt[0] instead of th.domain for /popular/
              if (lth.q && lth.q.waiting && th.parse_funcs.has_posts) site2[th.domain].popups_fetched(th, lth);
              var updated = null;
              var archive_store = (lth.archived || pf_store!=='no'); //  && (!pref.test_mode['67'] && !sb.localArchive);
              var pf_store_1 = get_nof_store_posts(pf_store, tgt_th);
//              var pf_store_th = tgt_th && tgt_th[16].get_t2h_num_of_posts() || 'no';
//              var pf_store_1 = (pf_store==='ALL' || pref[embed_mode].storePosts==='ALL' && tgt_th)? -1
//                  : (pf_store==='no' || pf_store==='passive' && pf_store_th!=='no')? pf_store_th : pf_store;
              var ta_missing_info = lth.ta && lth.ta.posts[0].missing_info;
              var lastNo = th.parse_funcs.has_posts? th.posts[th.posts.length-1].no : lth.lastNo || th.no;
              var th_modified = lth.nof_posts!=th.nof_posts || lth.lastNo!==lastNo;
              var force_store_ta = th_modified || pf_store_1===-1 || ta_missing_info && ta_missing_info>(th.missing_info|0); // pf_store_1===-1 for expand_posts
              if (post_updated!==null && (th_modified || (watch[0]&0x00060000))) { // watch req || retag req
//              if (post_updated!==null && (lth.nof_posts<th.nof_posts || (watch[0]&0x00060000))) { // watch req || retag req
                if (th.type_source==='thread' || 
//                    (th.parse_funcs.has_posts && th.last_replies && th.last_replies.length>=th.nof_posts-lth.nof_posts) ||
                    (th.parse_funcs.has_posts && th.posts.length>th.nof_posts-lth.nof_posts && (lth.ta || pref[embed_mode].deleted_posts.detect.indexOf('full')!==0)) ||
                    (th.nof_posts===1 && th.posts[0].time>=0 && !th.parse_funcs.dont_have_com)) {
                  updated = site2[th.domain].check_reply.check(th, lth, watch, tgt_th /*|| (refresh && 1)*/ , sb.ext_posts || []); // dive in at the first time if page_html contains all posts. sb.ext_posts for embed_mode==='thread'. // update lth.nof_posts here
                  lth.lastNo = lastNo; // for deletion
                  if (updated.posts.length!==0) post_updated = true;
//                  if (gClg.UnsyncedTriages[lth.key]) triage_exe_pipe(gClg.UnsyncedTriages[lth.key]); // should be here, but tgt_th[8] is not updated here.
                  if (pref[embed_mode].backlink_all) site2[th.domain].popups_add_1_cross(th, updated.posts, pref[embed_mode].backlink_all_cross);
                  tag_updated = updated.tags || tag_updated;
                  scan.list_nup.got_200(th,lth); // patch for 8chan. 8chan has an inconsistency between catalog and threads,
                                             // some threads which are there in catalog sometimes returns 404 if it gets as a thread.
                                             // However, 8chan doesn't return 404, but fails at send(null), so system like watchdog is required.
//                  if (pref.debug_mode['3']) console.log('Check tags: '+th.key+', '+lth.nof_posts);
                  if (pref.threadStats.use && site2[th.domain].stats_ID && (refresh || tgt_th)) if (site2[th.domain].stats_ID(th, updated.posts, lth)) scan.list_nup.add(th.key, 0);
                  if (archive_store) archiver.store(value, th, lth, updated.posts, updated.time_check_old, pf_store_1, ed_f, sb.localArchive);
                } else { // if (!(th.parse_funcs.has_posts && !th.last_replies))
                  scan.list_nup.add(th.key, sb.priority);
//                  if (pref.debug_mode['3']) console.log('Schedule to check tags: '+th.key+', '+lth.nof_posts);
                  if ((ed_f || force_store_ta) && archive_store) archiver.store(value, th, lth, undefined, undefined, pf_store_1, ed_f, true); // no archive, store posts only, because archiver MUST need full thread, or misclear flags in lth.archived and cause full archiving repeatedly.
//                  if ((ed_f || (pref.test_mode['129'] && embed_mode==='page')) && archive_store) archiver.store(value, th, lth, undefined, undefined, pf_store_1, ed_f);
                }
              } else {
                if (!sb.localArchive) scan.list_nup.got_200(th,lth);
                if (pref.threadStats.use && site2[th.domain].stats_ID && (refresh || tgt_th)) {
                  site2[th.domain].stats_ID(th, null, lth);
                  if (pref.threadStats.retry && pref.threadStats.full && th.nof_posts<lth.nof_posts && th.type_source==='thread') scan.list_nup.add(th.key, 0); // for obsolete cache data, may cause infinite loop.
                }
//                  if (lth.nof_posts==th.nof_posts || !pref.network.fetch_actively || // only active when count is changed,
//                      // 'th.parse_funcs.missing_info' doesn't have state if the thread is not stored(tgt_th===undefined),
//                      //and all members will be fetched forever.
//                    ((tgt_th)? !tgt_th[23] || th.type_source==='thread' : !th.parse_funcs.missing_info)) scan.list_nup.got_200(th,lth); // for threads which doesn't have posts.
//                  else th.parse_funcs.missing_info_fetch(th);
////                if (th.parse_funcs.has_editing) updated = {posts:th.posts.slice(lth.nof_posts || 0)}; // BUG, REDUNDANT.
                if ((lth.rescan_dp || lth.force_ar || ed_f || force_store_ta && th.parse_funcs.has_posts) && archive_store)
//                if (((pref.test_mode['129'] && embed_mode==='page') || lth.rescan_dp || lth.force_ar || ed_f || lth.nof_posts!=th.nof_posts && th.parse_funcs.has_posts) && archive_store)
                  archiver.store(value, th, lth, undefined, undefined, pf_store_1, ed_f, sb.localArchive); // for initial if watched already // BUG!!! THIS DOESN'T WORK IF 304 IS RETURNED. // changed from 'lth.nof_posts<th.nof_posts'.
////                if (lth.nof_posts!=th.nof_posts)
////                  if(!th.parse_funcs.has_editing || th.parse_funcs.has_posts || embed_mode==='catalog') lth.nof_posts = th.nof_posts; // to get updated in next loop. // BUG, REDUNDANT.
                if (th_modified) {
                  if (pref.test_mode['167'] && pref.stats.use || (watch[0]&0x0c000000)) {
                    var nof_new_posts = th.nof_posts - lth.nof_posts;
//                  if (pref.test_mode['167'] && nof_new_posts) {
                    site2[th.domain].check_reply.check(th, lth, watch, tgt_th, sb.ext_posts || [], nof_new_posts); // updates th.nof_posts(===watch[0][15:0]) and ivoke notifier.
                    if (pref.test_mode['167'] && pref3.stats.use && nof_new_posts>0) stats.aggregate_passive(th, nof_new_posts, value.date);
//                    if (lth.watched_p) lth.nr += nof_new_posts - (lth.nof_posts===0? watch[2] : 0);
                  } else lth.nof_posts = th.nof_posts;
                  post_updated = true;
                }
//                lth.nof_posts = th.nof_posts;
                lth.lastNo = lastNo; // for deletion
              }
////////              }
              if (th.parse_funcs.has_editing) {
                if (updated && pref.liveTag.from!=='post') { // share lth.ed_t always
                  var ed_p = undefined;
                  for (var j=0;j<updated.posts.length;j++) // update is reverse ordered.
                    if (updated.posts[j].editing && updated.posts[j].no!=th.no)
                      if (ed_p) ed_p[ed_p.length] = updated.posts[j];
                      else ed_p = [updated.posts[j]];
                  if (ed_p) {
                    lth.ed_t = (lth.ed_t)? ed_p.concat(lth.ed_t) : ed_p;
                    if (pref.debug_mode['24']) console.log(th.key+ ': lth.ed_t: '+ lth.ed_t.map(function(v){return (typeof(v)==='string')? v : v.no+'('+v.time+')';}));
                  }
                }
//                if (updated && pref.liveTag.from!=='post') { // share lth.ed_t when pref.liveTag.from==='post'
//                  var ed_p = undefined; // ed_p is used previously, so it must be cleared.
//                  for (var j=0;j<updated.posts.length;j++) // update is reverse ordered.
//                    if (updated.posts[j].editing)
//                      if (ed_p) ed_p[ed_p.length] = updated.posts[j];
//                      else ed_p = [updated.posts[j]];
//                  if (ed_p) lth.ed_p = (lth.ed_p)? ed_p.concat(lth.ed_p) : ed_p;
//                }
//                if (updated) for (var j=updated.posts.length-1;j>=0;j--) // update is reverse ordered.
//                  if (updated.posts[j].editing)
//                    if (lth.ed_p) lth.ed_p[lth.ed_p.length] = updated.posts[j].no;
//                    else lth.ed_p = [updated.posts[j].no];
                if (th.type_source==='catalog' && (lth.ed_p || lth.ed_t) && (lth.archived || pref[embed_mode].t2h_sel!=='no' && tgt_th) &&
                  lth.ed_u<th.replyTime) scan.list_nup.add(th.key, sb.priority);
//                if (th.type_source==='catalog' && embed_mode!=='catalog' && tgt_th &&
//                  (tgt_th[16].needs_update<th.replyTime || tgt_th[16].needs_update===null))) scan.list_nup.add(th.key, sb.priority);
//                if (th.type_source==='catalog' && embed_mode!=='catalog' && tgt_th && lth.ed_p) scan.list_nup.add(th.key, sb.priority);
              }
//              if (th.domain==='meguca' && th.type_source==='catalog' && embed_mode!=='catalog' && tgt_th && tgt_th[16].needs_update<th.logCtr) scan.list_nup.add(th.key, sb.priority);
////}
//if (pref.test_mode['32'] && dbt[0]==='meguca1' && dbt[3]==='thread_json') continue;
              if (lth.th) {
                if (!tgt_th) delete lth.th;
                else if (lth.th.missing_info && lth.th.missing_info>(th.missing_info|0)) {
//                  if (lth.th.pn) th.pn = lth.th.pn; // Is this required???
                  lth.th = th;
                  // lth.th.posts SHOULD BE REDUCED HERE.
                }
              }
////              if (tgt_th) { // working code.
////                if (tgt_th[23]) {
////                  for (var p in tgt_th[23]) {
////                    if (p==='time_posted') {
////                      if (th.time_posted) {tgt_th[8][4] = th.time_posted; delete tgt_th[23][p];}
////                    } else if (p==='op_img_src_url') {
////                      if (th.parse_funcs.get_op_src) {tgt_th[16].op_img_src_url = th.parse_funcs.get_op_src(th); delete tgt_th[23][p];}
////                    }
////                  }
////                  if (Object.keys(tgt_th[23]).length===0) {
////                    tgt_th[23] = null;
//////                    if (missing_info[th.key]!==undefined) delete missing_info[th.key]
////                  }
////                }
////              }
              if (filter_needs_wrapping) site2[th.domain].wrap_to_parse.posts(th);
              for (var j=0;j<gClg.Clgs.length;j++) {
                var clg = gClg.Clgs[j];
//                var clg_refresh = refresh===clg ||
//                                  refresh && refresh.pref[refresh.mode].board_list_sel===clg.pref[clg.mode].board_list_sel && clg.pref[clg.mode].accept_others_refresh ||
//                                  clg.refresh_tgts[th.domain+th.board]===null;
                var tgt_th_new = clg.insert_thread_1(th, lth, post_updated, tag_updated, clgs_args[j], value, nos, sb_merge, sb_merge_op_not_done);
                if (!tgt_th && tgt_th_new) {
//                if (tgt_th_status===undefined) { // BUG, this can't track proper number of posts when watcher's view is page and it is expanded.
                  var pf_store_2 = get_nof_store_posts(pf_store, tgt_th_new); // depends on embed_mode, not this.mode, so all clg have the same number of stored poste.
                  if (pf_store_1!==pf_store_2) archiver.store_th_to_mem(value, th,lth, pf_store_2, null);
                  if (pf_notify_new_thread && clg.filter_test(th.key, tgt_th_new)) notifier.appeared(th, true); // 'appeared' was dropped.
                  gClg.AllThreads[th.key] = tgt_th_new;
                  tgt_th = tgt_th_new;
                }
                if (tgt_th_new && tgt_th!==tgt_th_new) gClg.Maintain_AllThreads_incremental(th.key, tgt_th, tgt_th_new); // always tgt_th===tgt_th_new when this.Clgs.length===1
              }
              if (post_updated && gClg.UnsyncedTriages[th.key]) triage_exe_pipe(gClg.UnsyncedTriages[th.key]); // temporarily patch
////if (!pref.test_mode['133']) {
////  ths_info[ths_info.length] = [th, lth, post_updated, tag_updated, insert_thread_from_native_1];
////}
            }
            sb.found_threads += ths.length;
////if (pref.test_mode['22']) {
////            if (sb.scan_tag && from_catalog) tag_scan_board(ths, sb);
////}
//            for (var j=0;j<gClg.Clgs.length;j++) gClg.Clgs[j].idx_reorder_exe();
            if (dbt[3]==='catalog_json' || dbt[3]==='catalog_html') {
////              if (pref.liveTag.use) scan.list_nup.add_board(dbt[0]+dbt[1], value.date);
              scan.list_nup.got_200_board(dbt[0],dbt[1]);
              rm_items_404_check(dbt[0],dbt[1],ths, nos);
              if (pref.test_mode['167'] && pref3.stats.use) Object.defineProperty(liveTag.mems[dbt[0]][dbt[1]], 'st', {value:value.date, writable:true, configurable:true});
//              if (pref[embed_mode].detect_sage && th.domain==='4chan' && dbt[3]==='catalog_json') site2['4chan'].sage_detect_all(ths);
////              if (liveTag.mems[dbt[0]][dbt[1]] && liveTag.mems[dbt[0]][dbt[1]].f) { // 'liveTag.mems[dbt[0]][dbt[1]]' is for /popular/
////                for (var i=0;i<ths.length;i++) scan.list_nup.add(ths[i].key);
////                liveTag.mems[dbt[0]][dbt[1]].f = false;
////              }
//              if (liveTag.mems[dbt[0]][dbt[1]] && liveTag.mems[dbt[0]][dbt[1]].f) liveTag.mems[dbt[0]][dbt[1]].f = false; // 'liveTag.mems[dbt[0]][dbt[1]]' is for /popular/
            }
            if (pref.catalog_max_page_auto && (dbt[3]==='page_html' || (dbt[3]==='catalog_json' && site2[dbt[0]].parse_funcs[dbt[3]].get_max_page))) {
              var max_page = site2[dbt[0]].parse_funcs[dbt[3]].get_max_page(value.response);
              if (max_page) liveTag.mems[dbt[0]][dbt[1]].pgs = max_page;
            }
            if (pref[embed_mode].load_on_demand) {
              var tgt = ths[ths.length-1];
              if (tgt && tgt.type_source==='page' && (!site2[tgt.domain].all_boards || site2[tgt.domain].all_boards.indexOf(site.board)!=-1)) pClg.idx_raise_odl(tgt);
            }
            sb.found_boards++;
            /*if (!sb.native_prep)*/ for (var i=0;i<gClg.Clgs.length;i++) {// show_catalog is requied to update footer even if sb.native_prep
              var clg = gClg.Clgs[i];
              if (clg.merge_op_tgts) {scan.scan_ui('mergeAuto', {tgts:clg.merge_op_tgts, options:{refresh:clg}}); clg.merge_op_tgts = null;}
              if (clg.dirty) clg.show_catalog();
            }
//              if (gClg.Clgs[i].dirty) gClg.Clgs[i].show_catalog(undefined, null, null, gClg.Clgs[i].footer.query_update_without_data(dbt[3]!=='thread_json' && dbt[3]!=='thread_html')); // call even if tgts[i] is vacant
//              if (gClg.Clgs[i].dirty) if (Object.keys(tgts[i]).length===0 || gClg.Clgs[i].show_catalog(tgts[i])) gClg.Clgs[i].footer.update_without_updated_data(dbt[3]!=='thread_json' && dbt[3]!=='thread_html');
//              if (Object.keys(tgts[i]).length===0 || gClg.Clgs[i].show_catalog(tgts[i])) if (refresh===gClg.Clgs[i]) gClg.Clgs[i].footer.update_without_updated_data();
            if (sb.key==='mergeAuto' && gGEH.time_jumped) for (var i=0;i<ths.length;i++) jump_to_time(ths[i], gGEH.time_jumped); // this has racing condition, see receive_message
  
////////            if ((dbt[3]==='thread_html' || dbt[3]==='thread_json') && threads[ths[0].key]) update_thread(dbt[0]+dbt[1]+dbt[2], ths[0], threads[ths[0].key][19]); // patch  // working code.
//            if ((dbt[3]==='thread_html' || dbt[3]==='thread_json') && threads[ths[0].key] && threads[ths[0].key][23]) update_thread(dbt[0]+dbt[1]+dbt[2], ths[0], threads[ths[0].key][19]); // patch to delete threads[name][23]
////            if (dbt[3]==='thread_html') {
////              var name = dbt[0] + dbt[1] + dbt[2];
////              if (threads[name]) { // patch for parallel entry.
////                site2[dbt[0]].check_reply.do(value.response, dbt, threads[name][19], threads[name][8], dbt[3]); // SHOULD CONSOLIDATE TO CHECK_REPLY.CHECK().
////
//////                if (threads[name][20]!==sb.pool.sticky) { // working code.
//////                  site2[dbt[0]].add_sticky_info(threads[name][0],threads[name][18],sb.pool.sticky);
//////                  threads[name][20] = sb.pool.sticky;
//////                }
////                if (threads[name][23]) {
////                  threads[name][23] = false;
////                  threads[name][9] = catalog_filter_query(name);
////                }
//////                if (pref.catalog_footer_show_nof_rep) site2[dbt[0]].insert_footer2(threads[name][0],threads[name][18],threads[name][19],threads[name][8]);
////                if (pref.catalog_footer_show_nof_rep) insert_footer3(threads[name][24],threads[name][19],threads[name][8],name);
////                threads[name][21] = false;
////                if (threads[name][19][0]>=0) notifier.changed(name,threads);
////                threads[name][19][5] = false;
////                threads[name][19][4] = null; // for GC.
////                reorder_thread_idx(name);
//////var debug = '';
//////for (var d=0;d<10;d++) debug += threads_idx[d] + ', ';
//////console.log('ddd :'+debug);
//////              if (reorder_thread_idx(name)) {
//////                tgts = {};
//////                tgts[name] = true;
//////                show_catalog(tgts);
//////              }
////              }
////            }
//            if (sb.native_prep) return ths;
}
          } else { // DON'T USE TH.XXX BECAUSE MEGUCA DOESN'T HAVE.
            if (value.status==404 && (dbt[3]==='thread_json' || dbt[3]==='thread_html')) {
              var lth = liveTag.mems.getFromName(dbt[0]+dbt[1]+dbt[2]);
              if (!pref.test_mode['146']) if (lth.q && lth.q.waiting) {
                var lth_w = {domain_html:site.nickname, time:0, com:'PRUNED', type_source:'thread', __proto__:lth}; // __proto__ kills optimizer, but this case isn't improved by removing these lines, just change to 'Not optimized: Optimized too many times', see https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
                site2[dbt[0]].popups_fetched({posts:[lth_w], __proto__:lth_w}, lth);
              }
              rm_items_404_th(dbt[0],dbt[1],dbt[2]);
//              liveTag.rm_404_1(dbt[0],dbt[1],dbt[2]);
              scan.list_nup.got_404(dbt[0] + dbt[1] + dbt[2]);
            }
            if (sb.found_threads<sb.max_threads) {
////////              sb.error += ((sb.error==='')? '' : ', ') + key;
////////              sb.error_obj[key] = value.status;
              if (value.status==404) comment_out_bookmark(key);
////              if (sb.indicator) {
////                sb.indicator.report({err:key+'('+value.status+')'});
////                sb.indicator.set('orange');
////              }
            }
            if (value.response===null && value.status==200) { // patch for 8chan's inconsistency.
              if (pref.debug_mode['16']) console.log('ERROR!!! Inconsistency in server. Server returned null with status 200 for '+site2[dbt[0]].make_url4(dbt)[0]+' , '+key);
              value.status=-200;
            }
            if (value.status==404 && pref.catalog_max_page_auto && (dbt[3]==='page_json' || dbt[3]==='page_html')) {
              var max_page = parseInt(dbt[2].replace(/^[pq]/,''),10);
              if (!liveTag.mems[dbt[0]][dbt[1]].pgs || max_page<liveTag.mems[dbt[0]][dbt[1]].pgs) liveTag.mems[dbt[0]][dbt[1]].pgs = max_page;
            }
          }
          if (site2[dbt[0]].rss2dbpEtag) {
            var url = args[0].indexOf(site.proxy)===0? args[0].slice(site.proxy.length) : args[0];
            if (site2[dbt[0]].rss2dbpEtag[url]) {
              site2[dbt[0]].rss2dbpEtag[url][2] = [value.status, value.date];
              pref_func.settings.onchange_funcs['dashboard.rss_update_status'](url);
            }
          }
////          scan_boards_keyword(args,value.status);
          if (sb.refresh && pref.features.IDB && pref.archive.IDB.auto_restore && ((dbt[0]+dbt[1]) in gClg.IDB_req) &&
              (dbt[3]=='catalog_json' || dbt[3]==='catalog_html' || dbt[3]=='page_json' || dbt[3]==='page_html') && !window.opener) {
            restore_bd_from_IDB(dbt[0], dbt[1]);
            delete gClg.IDB_req[dbt[0]+dbt[1]];
          }
//          if (sb.refresh && pref.features.IDB && pref.archive.IDB.auto_restore && (!threads_delayed_pruning[dbt[0]] || !threads_delayed_pruning[dbt[0]][dbt[1]]) &&
//              (dbt[3]=='catalog_json' || dbt[3]==='catalog_html' || dbt[3]=='page_json' || dbt[3]==='page_html') && !window.opener) restore_bd_from_IDB(dbt[0], dbt[1]);
          return ths;
        }
        cataLog.scan_boards_keyword_callback2 = scan_boards_keyword_callback2;
        function get_nof_store_posts(pf_store, tgt_th){
          var pf_store_th = tgt_th && tgt_th[16].get_t2h_num_of_posts() || 'no';
          return (pf_store==='ALL' || (pf_store==='acc' || pref[embed_mode].storePosts==='ALL') && tgt_th)? -1
            : (pf_store==='no' || pf_store==='passive' && pf_store_th!=='no')? pf_store_th : pf_store;
        }

////        Clg.prototype.insert_thread_1 = function(ths, ths_info, refresh, sb, value_date, pf_store, value){
////          var pfm = this.pref[this.mode];
////          var filter_active_kwd = this.pref.filter.kwd.active;
////          var filter_active_tag = this.pref.filter.tag && this.liveTag.active.in; // for passive
////          var tgts = {};
////          for (var i=0;i<ths.length;i++) {
////            var th = ths_info[i][0];
////            var lth = ths_info[i][1];
////            var post_updated = ths_info[i][2];
////            var tag_updated = ths_info[i][3];
////            var insert_thread_from_native_1 = ths_info[i][4];
////            var tgt_th = this.threads[th.key];
        Clg.prototype.insert_thread_1 = function(th, lth, post_updated, tag_updated, clg_args, value, nos, sb_merge, sb_merge_op_not_done){
//          var pfm = pref[embed_mode]; // CAUTION: embed_mode is used intentionally. // this.pref[this.mode];
//          var pfcm = this.pref[this.mode];
          var th_key = th.key;
          var tgt_th = this.threads[th_key];
          if (!tgt_th) {
            var new_th = true; // tgt_th===undefined;
//          var undo = (th_key in this.ths_undo);
//          if (undo) delete this.ths_undo[th_key];
            var refresh_or_search = clg_args.refresh || this.refresh_tgts[th.domain+th.board]===null ||
              this.view!=='thread' && (
                                    clg_args.ALL_agg && th.type_source==='thread' || // pick_up_for_search
                                    clg_args.pf_fKwd && catalog_filter_query_keyword.kwd(clg_args.pf_fKwd, th.posts, th.domain) || // picked_up_by_kwd_filter
                                    clg_args.pf_fTag && liveTag.search_by_tags(lth)); // picked_up_by_tag_filter
//              var pick_up_for_search = th.type_source==='thread' && pfm.storePosts==='ALL_agg';
//              var picked_up_by_kwd_filter = this.pref.filter.kwd.active && catalog_filter_query_keyword.kwd(this.pref.filter.kwd, th.posts, th.domain);
//              var picked_up_by_tag_filter = this.pref.filter.tag && this.liveTag.active.in && liveTag.search_by_tags(lth);
//              var picked_up_by_filter = picked_up_by_kwd_filter || picked_up_by_tag_filter;
//          tag_updated |= (tgt_th===undefined); // force update tags at initial
//              var tgt_th_status = (tgt_th===undefined)? undefined : tgt_th[9][0];
//              var force_update = (tgt_th && (tgt_th[16].expand_posts || (th.type_source==='thread' && (tgt_th[16].needs_update===true || tgt_th[16].needs_update===1)))) || pick_up_for_search;
//          var force_update = (tgt_th && (tgt_th[16].expand_posts || (tgt_th[16].missing_info && tgt_th[16].missing_info>(th.missing_info|0)))) ||
//                                 lth.ed_u<th.replyTime || pick_up_for_search;
////              var force_update = (tgt_th && (tgt_th[16].expand_posts || (th.type_source==='thread' &&
////                (tgt_th[16].needs_update===true || (tgt_th[16].needs_update<th.replyTime || tgt_th[16].needs_update===null))))) || pick_up_for_search;
////              if (refresh || (post_updated && tgt_th) || picked_up_by_filter || force_update) {
////              if (!sb.tag_only && (sb.refresh || (post_updated && tgt_th) || picked_up_by_filter)) {
////              if (!sb.tag_only && (sb.refresh || (filter_active && catalog_filter_query_scan(th.posts, th.tags)))) {
//////////                if (insert_thread_with_test(th, dbt[3], value.date)) {// RUNS A REDUNDANT POST CHECK AT FIRST TIME....
          } else {
//          if (tgt_th) {
            if (post_updated===null) {
//              if (post_updated===null && tgt_th) { // && refresh_or_search) {
//              if (post_updated===null && tgt_th && (refresh || picked_up_by_filter)) {
////                if (post_updated===null && tgt_th) {
              var tgt_th8 = tgt_th[8]; // must be sonsolidated to lth
              post_updated = !((((tgt_th8[0]>tgt_th8[4])? tgt_th8[0] : (tgt_th8[4] || tgt_th8[0])) >= ((th.time_bumped > th.time_posted)? th.time_bumped : (th.time_posted || th.time_bumped))) && // vichan has inconsistency in time between catalog.json and thread.json.
                               tgt_th8[2]==th.nof_posts && tgt_th8[3]==th.nof_files); // || th.type_source==='thread'; // pass thread to revise all of tgt_th[8] // <- should use missing_info
            }
            if (!post_updated) {
              var tgt_th16 = tgt_th[16];
              post_updated |= tgt_th16.expand_posts || tgt_th16.missing_info && tgt_th16.missing_info>(th.missing_info|0) || lth.ed_u<th.replyTime; // ed_u msut be consolidated to missing_info or tgt_th8[4]
            }
          }
          if (tgt_th && post_updated || !tgt_th && refresh_or_search) {
//              if (post_updated && (tgt_th || pfcm.promiscuous) || (refresh || picked_up_by_filter) && !tgt_th || force_update) { // stop merging at preparing cross-thread popup
////              if (post_updated || (refresh || picked_up_by_filter) && !tgt_th || force_update) {
////              if (post_updated || picked_up_by_filter || tgt_th_status===undefined || force_update) {
////                if (!tgt_th) { // PATCH, WILL BE REMOVED BY CONSOLIDATING.
////                  var pf_store_th_2 = tgt_th16_proto.get_t2h_num_of_posts.call({lth:lth}) || 'no';
////                  if (pf_store!=='ALL' && (pf_store==='no' || pf_store==='passive' && pf_store_th_2!=='no'))
////                    if (pf_store_1!==pf_store_th_2) archiver.store_th_to_mem(value, th,lth, pf_store_th_2, null);
////                }
                /*if (pfm.scroll_lock) this.show_catalog_scroll_lock.set();*/
//            this.idx_reorder(th_key, tgt_th? 0x03 : 0x01); // will be moved to here after removing pref_test.mode['169']
            tgt_th = this.insert_thread(th, value.date, sb_merge, nos, sb_merge_op_not_done); // insert_thread_from_native && ths[i].pn); // '&& ths[i].pn' for reentry.
            /*var order_new =*/ this.idx_reorder(th_key, new_th); // true);
                /*tgts[th.key] = true;*/
////                tgt_th = this.threads[th.key];
//                if (!tgt_th_g && gClg.AllThreads[th_key]===undefined) {
////                if (tgt_th_status===undefined) { // BUG, this can't track proper number of posts when watcher's view is page and it is expanded.
//                  var pf_store_2 = get_nof_store_posts(pf_store, tgt_th); // depends on embed_mode, not this.mode, so all clg have the same number of stored poste.
//                  if (pf_store_1!==pf_store_2) archiver.store_th_to_mem(value, th,lth, pf_store_2, null);
//                  if (pf_notify_new_thread && this.filter_test(th_key, tgt_th)) notifier.appeared(th, true); // 'appeared' was dropped.
////                    if (pref.notify.desktop.notify && pref.notify.desktop.new_thread || pref.notify.sound.notify && pref.notify.sound.new_thread) // lazy as much as possible
////                      if (this.filter_test(th_key, tgt_th)) notifier.appeared(th, true); // 'appeared' was dropped.
//                  gClg.AllThreads[th_key] = tgt_th;
//                }
          } // else if (undo && tgt_th) {/*order_new =*/ this.idx_reorder(th_key, true); /*tgts[th.key] = true;*/} // patch for rewinding watchtime
//              if (undo) delete this.ths_undo[th_key];
//              if (sb.from_auto && (!tgt_th_status && tgt_th[9][0])) notifier.appeared(th,tgt_th_status===undefined);
//              } // else if (pfm.load_on_demand && tgt_th) pClg.idx_reorder(th.key); // temporal, should be rewritten.  // place thread before ODL. // implementation is changed.
//              if (tgt_th_status!==undefined && tgt_th[16].expand_posts) insert_thread_format_html(th,th.key,false,false,th.type_data==='json'); // format_html.update_posts_in_page(th, th.key, true);
          if (tgt_th) {
            var page_updated = th.page && tgt_th[14]!=th.page;
            if (page_updated) tgt_th[14] = th.page;
            if (post_updated || new_th || clg_args.pf_foPg && page_updated) {
              this.footer.update(th_key, post_updated && th.country, tag_updated || new_th); // , order_new);
//                this.footer.update(th_key, post_updated && th.country, (tgt_th_status!==undefined)? tag_updated : th.tags); // , order_new);
              this.dirty |= 0x01;
            }
          }
          return tgt_th;
//              if (tgt_th) insert_footer3(th.key,(post_updated)? th.flags:null, th.page, (tgt_th_status!==undefined)? tag_updated : th.tags, th);
//              if (pref.liveTag.use && i===0) scan.list_nup.add_board(th.domain+th.board, (th.type_source==='catalog')? value.date : null);
//              if (th.tags.q && th.parse_funcs.has_posts) site2[th.domain].popups_fetched(th); // stored AFTER modifying pn. // BUT, posts are deleted sometimes.

////          }
////          if (Object.keys(tgts).length!==0 && (!sb.native_prep || this!==pClg)) {
////            sb.found_boards++;
////            this.show_catalog(tgts);
////  //        if (pref.filter.tag_scan_auto) scan_tags();
////          }
        };

        ////        function tag_scan_extract_1(pn) { // working code.
////          return pn[brwsr.innerText].match(tags_scan_regex);
////        }
////        function tag_scan_board(ths, sb) { // patch
////          var ths_tag = {};
////          for (var i=0;i<ths.length;i++) ths_tag[ths[i].key] = ths[i];
////          scan_tags_common(ths_tag,'',sb.pool.tags);
////        }

////        function tag_scan_extract_1(key,pn,sb) {
////          pn.tags = pn[brwsr.innerText].match(tags_scan_regex);
////          var ths = {};
////          ths[key] = pn;
////          scan_tags_common(ths,'',sb.pool.tags);
////  //        scan_tags_common({key:pn},'',sb.pool.tags,true);
////  //        return pn.tags;
////        }

////////        function scan_boards_keyword_callback2(key,value,args){
////////          var sb = args[1];
////////          if (pref.scan.crawler_adaptive) scan_boards_crawler_timer_clear();
////////          sb.pool.dbt = cnst.name2domainboardthread(key,true);
////////          if (value.status==200 && sb.found_threads<sb.max_threads) {
////////            if (sb.pool.dbt[2]=='j0' || sb.pool.dbt[2]=='c0') {
////////              if (sb.pool.dbt[2]=='j0') {
////////                sb.pool.type = 'catalog_json';
////////                sb.pool.doc = ('response' in value)? value.response : JSON.parse(value.responseText);
////////                var obj = sb.pool.doc;
//////////                var obj = ('response' in value)? value.response : JSON.parse(value.responseText);
////////    //            if (sb.scan_tag) {
////////    //              for (var i=0;i<obj.length;i++) {
////////    //                for (var j=0;j<obj[i].threads.length;j++) {
////////    //                  sb.pool.div.innerHTML = obj[i].threads[j].com;
////////    //                  obj[i].threads[j].tags = tag_scan_extract_1(sb.pool.dbt[0]+sb.pool.dbt[1]+obj[i].threads[j].no,sb.pool.div,sb);
////////    //                }
////////    //            }}
////////                for (var i=0;i<obj.length;i++) {
////////                  if (obj[i].threads) {
////////                    sb.scanned += obj[i].threads.length;
////////                    for (var j=0;j<obj[i].threads.length;j++) {
////////                      sb.name = sb.pool.dbt[0]+sb.pool.dbt[1]+obj[i].threads[j].no;
//////////                      if (threads[sb.name] && threads[sb.name][20]!==obj[i].threads[j].sticky) { // working code.
//////////                        site2[sb.pool.dbt[0]].add_sticky_info(threads[sb.name][0],threads[sb.name][18],obj[i].threads[j].sticky);
//////////                        threads[sb.name][20] = obj[i].threads[j].sticky;
////////////                        reorder_thread_idx(sb.pool.dbt[0] + sb.pool.dbt[1] +sb.pool.dbt[2].substr(1)); doesn't prepared threads[];
//////////                      }
////////                      if (sb.scan_tag) {
////////                        sb.pool.div.innerHTML = obj[i].threads[j].com + '\n' + obj[i].threads[j].sub + '\n' + obj[i].threads[j].name;
////////                        tag_scan_extract_1(sb.pool.dbt[0]+sb.pool.dbt[1]+obj[i].threads[j].no,sb.pool.div,sb);
////////                      }
//////////                      var search_obj = [obj[i].threads[j].com, obj[i].threads[j].sub, obj[i].threads[j].name,'','','','',''];
//////////                      if (!catalog_filter_query_scan(search_obj,sb.pool.div.tags)) {obj[i].threads.splice(j,1);j--;}
////////                      if (!catalog_filter_query_scan(obj[i].threads[j],sb.pool.div.tags)) {obj[i].threads.splice(j,1);j--;}
////////                    }
////////                  }
////////                }
////////    //            for (var i=0;i<obj.length;i++) sb.scanned += obj[i].threads.length; // NOT SO FAST.
////////    //            var kwd = pref.filter.kwd.str;
////////    //            if (pref.filter.kwd && kwd!=='') {
////////    //              if (!pref.filter.kwd.re) kwd = kwd.replace(/\*/g,'.*');
////////    //              if (pref.filter.kwd.ci) kwd = new RegExp(kwd,'i');
////////    //              for (var i=0;i<obj.length;i++) {
////////    //                for (var j=obj[i].threads.length-1;j>=0;j--) {
////////    //                  var str = obj[i].threads[j].name + '\n' + obj[i].threads[j].sub + '\n' + obj[i].threads[j].com;
////////    //                  var result = (str.search(kwd)!=-1);
////////    //                  if (pref.filter.kwd.match==='unmatch') result = !result;
////////    //                  if (!result) obj[i].threads.splice(j,1);
////////    //                }
////////    //              }
////////    //            }
////////                if ((pref.filter.kwd.use && pref.filter.kwd.str!=='') || (pref.filter.tag && filter_tags.length!=0))
////////                  sb.pool.ths = site2[sb.pool.dbt[0]].catalog_from_json3(obj,sb.pool.dbt[1]); // heavy, and cause loading in chrome.
////////                else sb.pool.ths = [];
////////              } else {
////////                sb.pool.type = 'catalog_html';
////////                sb.pool.doc = ('response' in value)? value.response : sb.pool.parser.parseFromString(value.responseText, 'text/html');
////////                sb.pool.ths = site2[sb.pool.dbt[0]].catalog_from_native(value.date,sb.pool.doc,sb.pool.dbt[1],sb.pool.type);
////////    //            if (sb.scan_tag) for (var i=0;i<sb.pool.ths.length;i++) sb.pool.ths[i].tags = tag_scan_extract_1(sb.pool.dbt[0]+sb.pool.dbt[1]+sb.pool.ths[i].no,sb.pool.ths[i].pn,sb);
////////                sb.scanned += sb.pool.ths.length;
////////                for (var i=0;i<sb.pool.ths.length;i++) {
////////                  if (sb.scan_tag) tag_scan_extract_1(sb.pool.dbt[0]+sb.pool.dbt[1]+sb.pool.ths[i].no,sb.pool.ths[i].pn,sb);
////////                  if (!catalog_filter_query_scan(sb.pool.ths[i],sb.pool.ths[i].pn.tags)) {sb.pool.ths.splice(i,1);i--;}
////////                }
////////              }         
////////              if (sb.pool.ths.length!=0 && ((pref.filter.kwd.use && pref.filter.kwd.str!=='') || (pref.filter.tag && filter_tags.length!=0))) {
////////                sb.found_boards++;
////////                sb.found_threads += sb.pool.ths.length;
////////                sb.pool.tgts = {};
////////                for (var i=0;i<sb.pool.ths.length;i++) {
////////                  insert_thread_with_test(sb.pool.ths[i], sb.pool.type, value.date); // patch
//////////                  insert_thread_from_native(sb.pool.ths[i], sb.pool.dbt[0], sb.pool.dbt[1], false, value.date);
////////                  sb.pool.tgts[sb.pool.dbt[0]+sb.pool.dbt[1]+sb.pool.ths[i].no] = true;
////////                }
////////                show_catalog(sb.pool.tgts);
////////    //            if (pref.filter.tag_scan_auto) scan_tags();
////////              }
////////            } else {
////////              var name = sb.pool.dbt[0] + sb.pool.dbt[1] + ((sb.pool.dbt[2][0]==='t')? sb.pool.dbt[2].substr(1) : sb.pool.dbt[2]);
////////              if (threads[name]) { // patch for parallel entry.
////////                sb.pool.type = (sb.pool.dbt[2][0]!=='t')? 'thread_html' : 'thread_json';
////////                if (sb.pool.dbt[2][0]!=='t') sb.pool.doc = ('response' in value)? value.response : sb.pool.parser.parseFromString(value.responseText, 'text/html');
//////////                site2[sb.pool.dbt[0]].check_reply_to_me(name,sb.pool.dbt,threads[name][19],(sb.pool.dbt[2][0]!=='t')? sb.pool.doc : value);
//////////var time_0 = performance.now();
//////////                site2[sb.pool.dbt[0]].check_reply_to_me(name,sb.pool.dbt,threads[name][19],(sb.pool.dbt[2][0]!=='t')? sb.pool.doc : value,threads[name][8], sb.pool, sb.pool.type); // also checks sage.
//////////                site2[sb.pool.dbt[0]].check_reply_to_me(name,sb.pool.dbt,threads[name][19],(sb.pool.dbt[2][0]!=='t')? sb.pool.doc : value.response, threads[name][8], sb.pool, sb.pool.type); // also checks sage.
////////                site2[sb.pool.dbt[0]].check_reply.do((sb.pool.dbt[2][0]!=='t')? sb.pool.doc : value.response, sb.pool.dbt, threads[name][19], threads[name][8], sb.pool.type);
////////
//////////                if (threads[name][19][0]>threads[name][8][4]) threads[name][19][0] = -1; // 2015.05.01 removed, but I don't remember why I wrote this.
////////
//////////console.log('check_reply :'+name+', '+(performance.now()-time_0));
//////////                if (threads[name][20]!==sb.pool.sticky) { // working code.
//////////                  site2[sb.pool.dbt[0]].add_sticky_info(threads[name][0],threads[name][18],sb.pool.sticky);
//////////                  threads[name][20] = sb.pool.sticky;
//////////                }
////////                if (threads[name][23]) {
////////                  threads[name][23] = false;
////////                  threads[name][9] = catalog_filter_query(name);
////////                }
//////////                if (pref.catalog_footer_show_nof_rep) site2[sb.pool.dbt[0]].insert_footer2(threads[name][0],threads[name][18],threads[name][19],threads[name][8]);
////////                if (pref.catalog_footer_show_nof_rep) insert_footer3(threads[name][24],threads[name][19],threads[name][8],name);
////////                threads[name][21] = false;
////////                if (threads[name][19][0]>=0) notifier.changed(name,threads);
////////                threads[name][19][5] = false;
////////                threads[name][19][4] = null; // for GC.
////////                reorder_thread_idx(name);
//////////var debug = '';
//////////for (var d=0;d<10;d++) debug += threads_idx[d] + ', ';
//////////console.log('ddd :'+debug);
//////////              if (reorder_thread_idx(name)) {
//////////                sb.pool.tgts = {};
//////////                sb.pool.tgts[name] = true;
//////////                show_catalog(sb.pool.tgts);
//////////              }
////////              }
////////            }
////////          } else {
////////            if (value.status==404 && (sb.pool.dbt[2][0]=='t' || sb.pool.dbt[2].search(/^[0-9]/)!=-1)) {
////////              var name = sb.pool.dbt[0] + sb.pool.dbt[1] + ((sb.pool.dbt[2][0]==='t')? sb.pool.dbt[2].substr(1) : sb.pool.dbt[2]);
////////              remove_thread(name);
////////            }
////////            if (sb.found_threads<sb.max_threads) sb.error += ((sb.error==='')? '' : ', ') + key;
////////          }
////////          sb.pool.doc = null;
////////          scan_boards_keyword(args,value.status);
////////        }
////////  
////////        function tag_scan_extract_1(key,pn,sb) {
////////  //        sb.pool.tags[key] = {};
////////  //        var tags = pn[brwsr.innerText].match(tags_scan_regex);
////////  //        sb.pool.tags[key].tags = tags;
////////  ////        var tags = div[brwsr.innerText].match(tags_scan_regex);
////////  ////        if (tags) {
////////  ////          var tags_uniq = {};
////////  ////          for (var k=0;k<tags.length;k++) tags_uniq[tags[k]] = 1;
////////  ////          for (var k in tags_uniq) {
////////  ////            if (sb.pool.tags[k]===undefined) sb.pool.tags[k] = [];
////////  ////            sb.pool.tags[k].push(key);
////////  ////          }
////////  ////        }
////////  //        return tags;
////////          pn.tags = pn[brwsr.innerText].match(tags_scan_regex);
////////          var ths = {};
////////          ths[key] = pn;
////////          scan_tags_common(ths,'',sb.pool.tags);
////////  //        scan_tags_common({key:pn},'',sb.pool.tags,true);
////////  //        return pn.tags;
////////        }
        cataLog.scan_init = scan_init;
        return {
          scan_init: scan_init,
          scan_abort: function(key){scan_init(key, {}, {});},
          scan_boards_keyword_callback2: scan_boards_keyword_callback2,
//          update_thread: update_thread,
////          priority_up: crawler.priority_up,
////          priority_release: crawler.priority_release,
//          get_nof_store_posts: get_nof_store_posts,
          args_proto: scan_boards.args_proto,
        }
      }());
      cataLog.scan_boards = scan_boards;

//      function catalog_resized(myself) {
//        var tgt;
//        if ((pref.catalog.text_mode.mode==='graphic' && (myself.name==='catalog_size_text_width' || myself.name==='catalog_size_text_height')) ||
//            (pref.catalog.text_mode.mode==='text'    && (myself.name==='catalog_size_width'      || myself.name==='catalog_size_height'))) return;
//        else tgt = myself.name.substr(myself.name.lastIndexOf('_')+1);
//        var val = (pref[myself.name]==0)? '' : pref[myself.name] + 'px';
//        for (var i in threads) threads[i][0].style[tgt] = val;
////        for (var i in threads) {
////          threads[i][0].style.width  = (pref.catalog_size_width==0 )? '' : pref.catalog_size_width  + 'px';
////          threads[i][0].style.height = (pref.catalog_size_height==0)? '' : pref.catalog_size_height + 'px';
////        }
//      }
//      var auto_update = (function(){ // working code
//        var timer = null;
//        var time_remains = 0;
//        var timer_tgt = function(from_refresh_button){
//          timer=null;
//          catalog_refresh(true,null,!from_refresh_button);
//          auto_update.set();
//        }
//        function stop_if_running(){
//          if (timer) {clearTimeout(timer); timer=null;}
//          if (pref.catalog_auto_update_countdown) health_indicator.pn_countdown.textContent = '';
//        }
//        function countdown(){
//          health_indicator.pn_countdown.textContent = time_remains--;
//          if (time_remains<0) timer_tgt();
//          else timer = setTimeout(countdown,1000);
//        }
//        return {
//          set : function(){
//            stop_if_running();
//            if (pref[embed_mode].auto_update) {
//              var period = pref[embed_mode].auto_update_period || 0.5;
//              if (!pref.catalog_auto_update_countdown) timer = setTimeout(timer_tgt,period*60000);
//              else {
//                time_remains = period*60;
//                countdown();
//              }
//            }
//          },
//          stop_if_running: stop_if_running,
//          timer_tgt : function(){
//            if (timer) {clearTimeout(timer); timer=null;}
//            timer_tgt(true);
//          }
//        }
//      })();
//      cataLog.auto_update = auto_update;
//      if (site.whereami!=='archive') auto_update.set();
//      var auto_update_timer = null; // working code.
//      function set_auto_update(){
//        if (auto_update_timer) {clearTimeout(auto_update_timer);auto_update_timer=null;}
//        if (pref.catalog_auto_update) {
//          var period = pref.catalog_auto_update_period;
//          auto_update_timer=setTimeout(function(){auto_update_timer=null;catalog_refresh(true,null,true);},period*60000);
//        }
//      }
//      set_auto_update();

////      pn_filter.getElementsByTagName('*')['filter.tag_search.str'].onkeyup = function(){ // working code.
////        pref_func.apply_prep(this,true);
////        liveTag.filter_onchange();
////      }
////      if (!pref.filter.show) pn_filter.style.display = 'none';
//      pn_filter.style.display = 'none';
////      pn_filter.getElementsByTagName('*')['filter.kwd.str'].onkeyup = function(){ // working code.
////        pref_func.apply_prep(this,true,true);
////        if ((!pref.filter.kwd.use && this.value!=='') || (pref.filter.kwd.use && this.value==='')) {
////          pref.filter.kwd.use = !pref.filter.kwd.use;
////          var use = pn_filter.getElementsByTagName('*')['filter.kwd.use'];
////          pref_func.apply_prep(use,false,true);
////        }
////      }

//      var filter_time_str = pn_filter.getElementsByTagName('*')['filter.time_str'];
////      pn_filter.getElementsByTagName('button')['now'].onclick = function(){set_time_str(Date.now());}
////      var filter_time_ago_str = pn_filter.getElementsByTagName('*')['filter.time_ago_str'];
////      filter_time_ago_str.onkeyup = ago_clicked;
////      pn_filter.getElementsByTagName('button')['ago'].onclick= ago_clicked;
//      function ago_clicked(e, time_in){
////        if (time_in===undefined) {
////          var str = filter_time_ago_str.value;
////          var time_in = (parseInt(str.replace(/:.*/,''),10)*60+parseInt(str.replace(/[^:]*:/,''),10))*60000;
////        }
//        var time = Date.now() - time_in;
//        filter_time_str.value = new Date(time).toLocaleString();
//        pref_func.apply_prep(filter_time_str,true,true);
//      }
//      pn_filter.getElementsByTagName('button')['copy'].onclick =
//        function(){
//          pn_filter.getElementsByTagName('textarea')['filter.time_mark_str'].value=filter_time_str.value;
//          pn_filter_changed();
//        }
//      var watch_list = pn_filter.getElementsByTagName('textarea')['filter.watch_list_str'];
//      var search_ex_list = pn_filter.getElementsByTagName('textarea')['filter.list_str'];
////      search_ex_list.onkeyup = pn_filter_changed;
//      var attr_list = pn_filter.getElementsByTagName('textarea')['filter.attr_list_str'];
//      attr_list.onkeyup = function(){if (pref.filter.attr_list) catalog_attr_changed();};
//      pn_filter.onchange = pn_filter_changed;
//      function pn_filter_changed(){
//        pref_func.apply_prep(pn_filter,true);
//        catalog_filter_changed();
//      }

////      function scan_tags(){ // working code.
////        var ths = [];
////        var j=0;
////        for (var i in threads) {
////          ths[j] = {};
////          ths[j++].tags = threads[i][0][brwsr.innerText].match(tags_scan_regex); // ATTENTION. DESCRIPTION IS ALSO EXIST IN CATALOG_FILTER_QUERY().
////        }
////        var str2 = scan_tags_common(ths,'');
////        scan_tags_init(str2,false);
////      }

//      function scan_tags_init(str2, reload){ // working code
//if (pref.test_mode['22']) {
//        var pn_tag_list = pn_filter.getElementsByTagName('div')['filter.tag_list'];
//        if (reload!==null) pn_tag_list.onchange = function(){prep_tag_str(null, reload, true);};
//        pn_tag_list.innerHTML = str2;
//        if (pn_tag_list.style.height=='') pn_tag_list.style.height = '30px';
//        if (pn_tag_list.style.width=='') pn_tag_list.style.width = '100px';
////        prep_tag_str(true,reload);
//        prep_tag_str(true,false);
//}
//      }
//      var filter_tags = [];
//      function prep_tag_str(keep,reload, from_onchange){
//        var pn_tag_list = pn_filter.getElementsByTagName('div')['filter.tag_list'];
//        var cbxes = pn_tag_list.getElementsByTagName('input');
//        if (keep===true) {
//          for (var i=0;i<filter_tags.length;i++) filter_tags[i] = filter_tags[i].toString().replace(/^\//,'').replace(/\(.*\n*.*/,'');
//          var tags = [];
//          for (var i=0;i<cbxes.length;i++) tags[i] = cbxes[i].nextSibling.textContent.replace(/ [0-9]*: /,'');
//          for (var i=0;i<filter_tags.length;i++)
//            for (var j=0;j<cbxes.length;j++)
//              if (tags[j]==filter_tags[i]) cbxes[j].checked = true;
//        }
//        filter_tags = [];
//        var tgts = {};
//        var flag = false;
//        for (var i=0;i<cbxes.length;i++)
//          if (cbxes[i].checked) {
//            filter_tags.push(new RegExp(cbxes[i].nextSibling.textContent.replace(/ [0-9]*: /,'').replace(/$/,'(#|,| |\\.|:|;|\n|$)'),(pref.filter.tag_ci)? 'i' : '')); // ATTENTION. REFER tag_scan_regex.
//            if (reload) {
//              var tags_tgt = (pref.filter.tag_ci)? site3[site.nickname].tags.ci[i].mem : site3[site.nickname].tags.cs[i].mem;
//              for (var j in tags_tgt) tgts[j] = null;
//            }
//            flag = true;
//          }
//        if (from_onchange && flag) {
//          pref.filter.tag = true;
//          pref_func.apply_prep(pn12_0_4.getElementsByTagName('input')['filter.tag'],false);
//        }
//        if (pref.filter.tag) catalog_filter_changed();
////        pn_filter_changed();
//        if (reload) {
////          for (var i in tgts) console.log(i);
//
////          load_list.tag.tgts = []; // working code.
////          for (var i in tgts) load_list.tag.tgts.push(i+'c0');
//////          load_list.tag.use_cache = !refresh;
////          load_list.tag.idx = 0;
////          load_list.tag.mutex = true;
////          load_list.tag.from_auto = false;
////          load_list.tag.tgts = trim_list(load_list.tag.tgts,false);
////          if (pref.catalog_refresh_clear) catalog_clear_threads(pref.catalog.max_threads_at_refresh);
////          if (load_list.tag.idx<load_list.tag.tgts.length) get_page(load_list.tag);
//          scan_boards.scan_init('refresh_tag',tgts, {lifetime:pref.scan.lifetime*60, cache_write:true});
////          set_auto_update();
//          filter_tags_refresh_mem = tgts;
//        }
//      }

//      function load_save_key(){
//        return pref.script_prefix + '.catalog.adv_str.' + pref.catalog_board_list_obj[board_sel.selectedIndex][0].key;
//      }
//      pn_filter.getElementsByTagName('button')['load_adv_str'].onclick = function(){
//        if (localStorage) {
//          var tmp = JSON.parse(localStorage.getItem(load_save_key()));
//          if (tmp) {
//            search_ex_list.value = tmp[0];
//            attr_list.value = tmp[1];
//          }
//        }
//      };
//      pn_filter.getElementsByTagName('button')['save_adv_str'].onclick = function(){if (localStorage) localStorage.setItem(load_save_key(),JSON.stringify([search_ex_list.value,attr_list.value]));};
      
//      pn_filter.childNodes[11].onclick= function(){
//        var str = pn_filter.childNodes[10].value;
//        var time = Date.now() - (parseInt(str.replace(/:.*/,''),10)*60+parseInt(str.replace(/[^:]*:/,''),10))*60000;
//        pn_filter.childNodes[8].value = new Date(time).toLocaleString();
//        pref_func.apply_prep(pn_filter,true);
//        catalog_filter_changed();
//      };
//      if (pn12_0_2.childNodes[0].checked) cnst.show_hide(pn_filter);
//      pn12_1.style.width  = '';
//      pn12_1.style.height = '';

      var scroll_event_src = (embed_embed)? ((site2[site.nickname].catalog_get_native_scroll_area)? site2[site.nickname].catalog_get_native_scroll_area() : window) :
                                            triage_parent.parentNode;
//      var scroll_event_src = (embed_embed)? window : triage_parent;
      var triage_merge_base = null;
      Clg.prototype.set_merge_base = function(name, attr, pn){
        var pn_old = triage_merge_base && triage_merge_base.pn;
        if (attr) attr.split(';').forEach(function(v){
          var s = v.split(':');
          pn.style[s[0]] = s[1];
          if (pn_old) pn_old.style[s[0]] = ''; // must be the latter for toggle erase
        });
        triage_merge_base = {name:name, attr:attr, pn:(pn!==pn_old)? pn : null};
      }
      Clg.prototype.triage_event = function(key_base,cmd,attr, datetime, pn, et){ // , expanded){
        var force_single = cmd.indexOf('s')!=-1;
        if (force_single) {
          cmd = cmd.replace(/s/,'');
          if (typeof(key_base)!=='string') key_base = key_base.keys[0];
        }
        var name = (typeof(key_base)==='string')? key_base : key_base.keys[0];
        if (cmd==='SPC') {return;}
        if (cmd==='GO') {this.click_thread(name, 'force'); return;}
        if (cmd==='MERGET' || cmd==='REMOVET' || cmd==='UNMERGET') {
          var href = et.parentNode.previousSibling.getAttribute('href');
          var tgt = site2[site.nickname].popups_href2dbtp(href).slice(0,3).join('');
          var no_insert = et.parentNode.dataset.cmd==='MERGETC';
          et.parentNode.innerHTML = triage.htmls[cmd];
          if (cmd==='MERGET') {
            this.merge_cross_links_fromUI(name, tgt, no_insert);
//            this.merge_bases.add_to_list_fromUI(name, tgt, this, no_insert);
            cmd = 'WATCH'; // return;
          } else if (cmd==='REMOVET') {this.remove_thread(tgt); return;}
          else cmd = cmd.slice(0,-1);
        }
        if (cmd==='MERGEN') {
          var th = liveTag.mems.getFromName(name).th;
          if (th.parse_funcs.near_threads) {
            var args = th.parse_funcs.near_threads(th, this.idxs).map(function(v){return 'MERGEC,'+v.sub.replace(/,/g,'')+','+v.tgt+',MERGECR,\u25c0,'+v.tgt;}).join('\n') || 'SPC,NONE,';
//            var args = th.parse_funcs.near_threads(th, this.idxs).join('\nMERGEC,') || 'NONE';
            triage.add_submenu(et.parentNode, args); // 'MERGEC,'+args+',');
          }
          return;
        }
        if (cmd==='MERGEB') {
          this.set_merge_base(name, attr, pn);
//          var pn_old = triage_merge_base && this.threads[triage_merge_base] && this.threads[triage_merge_base][0]; // pn of previous trial refers mergee itself at new merge of two.
//          if (attr) attr.split(';').forEach(function(v){
//            var s = v.split(':');
//            pn.style[s[0]] = s[1];
//            if (pn_old) pn_old.style[s[0]] = ''; // must be the latter for toggle erase
//          });
////          if (attr && pn_old) this.triage_multicast(triage_merge_base,'ATTR',attr.replace(/:[^;]*(;|$)/g,':;'), false, datetime, pn_old); // cause rollback to natural color sometime if merged
//          triage_merge_base = name;
////          if (attr) this.triage_multicast(name,'ATTR',attr, false, datetime, pn);
          return;}
        if (cmd.indexOf('MERGE')!=-1) {
          var history = pref4.mergeHist.proto;
          if (cmd==='UNDOMERGE') {
            if (history.length==0) return;
            var his = history.pop();
            pref.proto.merge_lv_str = his[1];
            pref_func.apply_prep_1n('proto.merge_lv_str', false, true, null, true); // make obj, call event
            pref.proto.merge_list_str = his[0];
            pref_func.apply_prep_1n('proto.merge_list_str', false, true, null, true); // make obj, call event
            return;
          } else {
            if (history.length>=pref4.mergeHist.max) history.shift();
            history.push([pref.proto.merge_list_str, pref.proto.merge_lv_str]);
          }
        }
        if (cmd==='MERGEE') {
          var key_tgt = triage.emb_get_key(et);
          this.merge_cross_links_fromUI(key_tgt, name, false); // not debugged since rewriting to use merge_cross_links_fromUI
//          this.merge_bases.add_to_list_fromUI(key_tgt, key_base, this);
          return;
        }
        var hist = cmd!=='MERGE' && cmd!=='UNMERGE' /*&& cmd!=='MERGEOP'*/;
        this.triage_multicast(key_base,cmd,attr,hist, datetime, pn); // , expanded);
        if (cmd==='MERGE') {
          if (triage_merge_base && triage_merge_base.pn) {
            var pn_base = this.threads[triage_merge_base.name] && this.threads[triage_merge_base.name][0]; // pn refers mergee itself at new merge of two in headline view
            if (triage_merge_base.pn!==pn_base) this.set_merge_base(triage_merge_base.name, triage_merge_base.attr, pn_base);
          }
        }
      };
      Clg.prototype.triage_multicast = function(key_base,cmd,attr,hist, datetime, pn){ // , expanded){
        var names = (typeof(key_base)==='string')? [key_base] : key_base.keys;
//        var names = (expanded)? ((typeof(key_base)==='string')? [key_base] : key_base.keys) : this.merge_bases.query_merged_keys(key_base);
        var mode_split = pref.test_mode['189'] || cmd.indexOf('ARC')!==-1 || cmd.indexOf('MERGE')!==-1 || ['KILL','TIME','WATCH','UNWATCH','TRACK','KILL_N','TIME_N'].indexOf(cmd.replace(/m/,''))!=-1; // datetime has dependency on name.
//        var mode_split = !pref.test_mode['189'] || cmd!=='NONE';
        if (cmd!=='UNDO' && mode_split) for (var i=names.length-1;i>=1;i--) this.triage_exe_broadcast(names[i],cmd,attr, hist? 'multi' : hist, null, null, null, true, pn);
        this.triage_exe_broadcast(mode_split? names[0] : names, cmd,attr,hist, datetime, undefined, undefined, undefined, pn);
//        if (!this.pref[this.mode].merge && this.pref[this.mode].merge_list) { // working code
//          var merged_others = this.merge_bases.query_others(name);
//          if (merged_others) for (var i=0;i<merged_others.length;i++) this.triage_exe_broadcast(merged_others[i],cmd,attr, hist? 'multi' : hist, null, null, null, true);
//        }
//        this.triage_exe_broadcast(name,cmd,attr,hist);
      };
//      function triage_event(){ // working code.
//        var flds = this.name.split(',');
//        var i = parseInt(flds[0].replace(/[^\(]*\(/,''),10);
//        var j = parseInt(flds[1].replace(/\).*/,''),10);
////      function triage(i,j){
//        var name = triage.get_triaged_thread_name();
//        var clg  = triage.get_triaged_catalog();
////        triage_exe(name,triage_str[i][j],triage_str[i][j+2],true);
//////        pref_func.apply_prep(search_ex_list,true);
//////        pref_func.apply_prep(attr_list,true);
//////        pref_func.apply_prep(watch_list,true);
////        if (triage_str[i][j]==='UNDO') catalog_attr_changed();
////        else catalog_attr_set(name,threads[name][0]);
////        if (triage_str[i][j]==='WATCH' || triage_str[i][j]==='UNWATCH') show_catalog(name);
////        else catalog_filter_changed();
//////        catalog_triage_out();
//        clg.triage_exe_broadcast(name,triage_str[i][j],triage_str[i][j+2],true);
//      }
      function triage_exe_pipe(args, stop_loop){
        var name = args[0];
        if (!site2[comf.fullname2dbt(name)[0]]) return; // for RSS
        var lth = liveTag.mems.getFromName(name);
        var time_last = gClg.Get_posted_time(gClg.AllThreads[name], null, name); // gClg.AllThreads[name][8][4]||gClg.AllThreads[name][8][0];
//        if (args[1]==='TRACK' && time_last===args[4]) return; // ignore TRACK at opening from child if equal // BUG, ignore the last TRACK also
        if (time_last<args[4]) gClg.UnsyncedTriages[lth.key] = args;
        else if (gClg.UnsyncedTriages[lth.key]) gClg.UnsyncedTriages[lth.key] = null;
        gClg.triage_exe_broadcast(args[0],args[1],args[2],args[3],args[4], stop_loop); // gClg for broadcast always if there are watchers.
      }
//      function triage_exe_0(name,tri_str_ex,tri_str_attr,hist,datetime){
//        pClg.triage_exe_broadcast(name,tri_str_ex,tri_str_attr,hist,datetime);
//      };
      Clg.prototype.Get_posted_time = function(tgt_th, tgt_th2, name){
        var d = comf.fullname2domain(name);
        var passiveWatch = site2[d] && site2[d].passiveWatch;
        return tgt_th && (passiveWatch? tgt_th[8][2] : (tgt_th[8][4] || tgt_th[8][0]))
          || tgt_th2 && (passiveWatch? tgt_th2[8][2] : (tgt_th2[8][4] || tgt_th2[8][0]));
//        return tgt_th && (pref.test_mode['167']? tgt_th[8][2] : (tgt_th[8][4] || tgt_th[8][0]));
      };
      Clg.prototype.triage_exe_broadcast = function(name,cmd,attr,hist,datetime, stop_loop, from_auto, merged_broadcast, pn){
//        if (cmd==='NONE_M') {var cmd_org = cmd; cmd = 'NONE';}
        if (cmd==='MERGEC' || cmd==='MERGECR' || cmd==='MERGECU') {
          var reverse = cmd==='MERGECR';
          this.merge_bases.add_to_list_1(reverse? name : attr, reverse? attr : name, this, cmd==='MERGECU');
          triage.off();
          return;
        }
        if (cmd==='MERGE') {this.merge_bases.add_to_list_1(triage_merge_base.name, name, this); triage.off(); if (attr) cmd='ATTR'; else return;}
        if (cmd==='UNMERGE') {this.merge_bases.add_to_list_1(null, name, this); triage.off(); return;}
        if (cmd==='UNMERGELV') {this.merge_bases.add_to_lv_list(name, null, this); triage.off(); return;}
//        if (cmd==='MERGEOP') {this.merge_cross_links_in_op_manually(name); triage.off(); return;}
        if (['ARC','ARC1','UNARC'].indexOf(cmd)!=-1) {
          if (!pref.test_mode['67']) archiver.start_1(cmd,liveTag.mems.getFromName(name),true); // test patch
          return;
        }
        if (!datetime && cmd==='WATCH' && pref.common.consolidated_watch_list) datetime = this.Get_posted_time(this.threads[name], gClg.AllThreads[name], name); // this.thread has priority.
        var changed_watch = false;
        for (var i=0;i<gClg.Clgs.length;i++) {
          var clg = gClg.Clgs[i];
          var broadcast = gClg.Clgs.length==1 || this===clg? false : this===gClg? true : this.pref[this.mode].board_list_sel==clg.pref[clg.mode].board_list_sel? 0 : true;
//          var broadcast = this!==clg && this.pref[this.mode].board_list_sel!=clg.pref[clg.mode].board_list_sel);
          var changed = clg.triage_exe(name,cmd,attr,hist,datetime, broadcast, stop_loop); // , from_pipe, stop_loop);
          if (!from_auto && (cmd!=='TRACK' || this.mode!=='thread')) clg.triage_exe_view(changed.name, changed, cmd!=='UNDO' && broadcast===false? pn : undefined); // , cmd.indexOf('m')!=-1); // changed.name for undo // TRACK trips child->catalog->child
          changed_watch |= changed.watch;
//          if (cmd_org==='NONE_M') if (clg.view==='headline') if (clg.merge_bases.bases[name]) clg.view_attr_set(name, true, true, clg.threads[name][3]); // TEST
        }
        Tooltips.hide();
        if (!stop_loop) if (changed_watch && pref.common.consolidated_watch_list && pref.common.sync_watch_list) gClg.Sync_watch_save([name,cmd,attr,hist,datetime], merged_broadcast); // Array.prototype.slice.call(arguments));
        if (cmd==='WATCH' && pref.archive.store_watched && this.threads && this.threads[name]) { // checking this.threads for triage_exe_pipe, which calls through gClg.
          var lth = liveTag.mems.getFromName(name);
          if (!lth.archived) archiver.start_1('ARC',lth,true);
        }
      };
      Clg.prototype.triage_exe_view = function(names, changeds, pn) { // , cmd_m){ // , from_pipe, stop_loop){
//        var changed = this.triage_exe(name,tri_str_ex,tri_str_attr,hist,datetime, broadcast);
//        name = changed.name; // for undo
//        if (!pref.triage.broadcast_attr && broadcast) changed.attr = false;
//        if (!pref.triage.broadcast_time && broadcast) {
//          changed.ex = false;
//          changed.watch = false;
//        }
        var pf = this.pref[this.mode];
        if (!Array.isArray(names)) names = [names];
//        var req_acc = 0;
//        var tgts = {};
        for (var i=0;i<names.length;i++) {
          var name = names[i];
          var changed = Array.isArray(changeds)? changeds[i] : changeds;
          var tgt_th = this.threads[name];
          if (changed.watch && !tgt_th && pf.refresh_src!=='bg') this.clone_thread_1(name);
          if (tgt_th) { // threads[name] might not exist at undo
            if (changed.attr) {
//              if (cmd_m && this.view==='headline' && this.merge_bases.bases[name]) this.view_attr_set(name, true, true, tgt_th[3]);
              this.view_attr_set(name, null, null, pn); // may be reorderd here
//              var req = this.view_attr_set(name, null, true, pn); // may be reorderd here
//              if (req) {
//                tgts[name] = null;
//                req_acc |= req;
//              }
            }
            if (changed.ex) this.filter_dirtify(tgt_th);
  //          if (changed.ex || changed.watch && pf.hide_unwatched) this.filter_dirtify(tgt_th);
            if (changed.watch) {
              var tmp;
              if (pf.hide_unwatched && !tgt_th[16].lth.watched_p) {
                this.func_hide_all((tmp = {}, tmp[name]=tgt_th, tmp));
                this.idx_remove(name);
              } else this.idx_reorder(name);
              if (this.view==='page' || this.view==='thread') tgt_th[16].check_and_expand(tgt_th[16].posts,true); // calls mark_newer_posts2 which is for DRAW-ONLY, so WNWATCH can't erase new posts's mark.
              if (pf.mark_new_posts) this.view_time_filter_changed(name); // to call mark_newer_posts2,
            }
            if (changed.ex || changed.watch) this.show_catalog_buf(name, true, true);
//            if (changed.ex || changed.watch) {
//              tgts[name] = null; // this.show_catalog(name, true, true); // redraw all tags to change color of them when threads are opened. // {this.show_catalog(name); triage.off();}
//              req_acc = 0x7;
//            }
          } // else if (changed.watch) {} // when the thread is added to watch list now. THIS SHOULD BE IMPLEMENTED, now watch list requres to refresh to appear the thread.
        }
//        if (Object.keys(tgts).length>0) {
//          this.show_catalog(tgts, req_acc&0x02, req_acc&0x04);
//          triage.off();
//        }
//        return changed;
//        if (broadcast===false) {
//          if (triage.get_trg().format_tgt) triage.format_buttons(changed.name);
//          pref_func.tooltips.hide();
//        }
//        if (!stop_loop) if (broadcast===false || from_pipe) // may issue multiple request if from_pipe===true, but OK.
//          if (changed.watch && pref.common.consolidated_watch_list) gClg.Sync_watch_save(Array.prototype.slice.call(arguments));
      };
      Clg.prototype.triage_exe = function(name,cmd,attr,hist,datetime, broadcast, from_LSmirror){ // KILL,TIME,UNDO,NONE,WATCH,UNWATCH,DELETE,GO
        var history = this.triage_history;
        var attr_list = this.components.attr_list;
        var watch_list = this.components.watch_list;
        var ex_list = this.components.ex_list;
//          if (cmd==='UNWATCH') comf.modify_bookmark(name,false);
        var changed = {ex:false, attr:false, watch:false, name:name};
        if (cmd!=='UNDO') {
          if (hist) {
            var hist_last = history.length>0 && history[history.length-1];
            if (hist_last && hist_last[0][3]==='multi') {
              hist_last[4].push(name);
              if (hist!=='multi') hist_last[0][3] = hist;
            } else {
              if (history.length>=pref.catalog_triage_hist) history.shift();
              history.push([Array.prototype.slice.call(arguments), ex_list.value, attr_list.value, watch_list.value, hist==='multi'? [name] : null]);
            }
          }
          var cmd_N = cmd==='KILL_N' || cmd==='TIME_N';
          cmd = cmd.replace(/m/,'').replace(/_N$/,'');
          if (Array.isArray(name)) {
            var attr_buf = {value:attr_list.value}; 
            var watch_buf = {value:watch_list.value}; 
            var ex_buf = {value:ex_list.value}; 
            for (var i=0;i<name.length;i++) {
              var c = changed[i] = this.triage_exe_1(name[i],cmd,attr,datetime, broadcast, from_LSmirror, attr_buf, watch_buf, ex_buf, this.threads[name[i]], cmd_N);
              changed.ex |= c.ex;
              changed.attr |= c.attr;
              changed.watch |= c.watch;
            }
            attr_list.value = attr_buf.value;
            watch_list.value = watch_buf.value;
            ex_list.value = ex_buf.value;
          } else changed = this.triage_exe_1(name,cmd,attr,datetime, broadcast, from_LSmirror, attr_list, watch_list, ex_list ,this.threads[name], cmd_N);
        } else {
          if (history.length==0) return changed;
          var his = history.pop();
          changed = {ex:ex_list.value!==his[1], attr:attr_list.value!==his[2], watch:watch_list.value!==his[3], name: his[0][0], names:his[4]};
          watch_list.value = his[3];
          attr_list.value = his[2];
          ex_list.value = his[1];
        }
//        if (cmd==='DELETE') { 
//          if (attr===null) return changed; // called as child
//          for (var i=0;i<attr.length;i++) {
//            var changed_next = this.triage_exe(attr[i],cmd,null,hist,datetime); // not change source for broadcast
//            changed.watch |= changed_next.watch;
//            changed.ex    |= changed_next.ex;
//            changed.attr  |= changed_next.attr;
//          }
////          if (attr===null) return changed; // recursive
////          else {
////            while (attr.length!=0) {
////              var name_next = attr.shift();
////              var changed_next = this.triage_exe(name_next,cmd,null,hist,datetime);
////              changed.watch |= changed_next.watch;
////              changed.ex    |= changed_next.ex;
////              changed.attr  |= changed_next.attr;
////            }
////          }
//        }
//        if (cmd==='WATCH' && pref.archive.store_watched) {
//          var lth = liveTag.mems.getFromName(name);
//          if (!lth.archived) archiver.start_1('ARC',lth,true);
//        }
//        if (!pref.test_mode['134'] && cmd==='DELETE') return changed; // no need to remake obj
        if (changed.watch && (pref.test_mode['190'] || cmd==='UNDO')) pref_func.apply_prep(watch_list,this.pref);
        if (changed.ex    && (pref.test_mode['190'] || cmd==='UNDO')) pref_func.apply_prep(ex_list,this.pref);
        if (changed.attr  && (pref.test_mode['190'] || cmd==='UNDO')) pref_func.apply_prep(attr_list,this.pref);
        if (cmd==='UNDO' && (changed.watch || his[0][1]==='KILL')) this.triage_rewind_watch(changed); // 'KILL' might not have changed watch-list, but watch status was changed when time-filter was active.
//        if (cmd==='UNDO' && (changed.watch || his[0][1]==='KILL')) { // re-order, 'KILL' doesn't change watch-list. 
////        if (cmd==='UNDO' && changed.watch) { // re-order
//          for (var i=0;i<names.length;i++) {
//          if (threads[changed.name]) {
//            site2['DEFAULT'].check_reply.set_watch_time(threads[changed.name][19], this.get_watch_time_of_a_thread(changed.name,threads[changed.name][8][1], null, true), changed.name);
////            threads[changed.name][19][0] |= 0x00010000;
//            scan_boards.scan_init('triage_undo', [changed.name], {refresh:true, priority:8});
//          }
//        }
        return changed;
      };
      Clg.prototype.triage_exe_1 = function(name,cmd,attr,datetime, broadcast, from_LSmirror, attr_list, watch_list, ex_list, tgt_th, cmd_N){ // KILL,TIME,UNDO,NONE,WATCH,UNWATCH,DELETE,GO
        var changed = {ex:false, attr:false, watch:false, name:name};
        var key = new RegExp('(^|,)'+name.replace(/\+/,'\\+')+'([\\^@;][^,\n]*)*(,|\n|$)','mg');
//          var key = new RegExp('(^|,)'+name.replace(/\+/,'\\+')+'([\\^@!][^,\n]*)*(,|\n|$)','mg');
//          if (cmd.search(/KILL|TIME|WATCH/)!=-1) { // contains UNWATCH
//if (!pref.test_mode['133'] && !broadcast /*|| tgt_th*/ || cmd==='DELETE' || cmd==='WATCH' && pref.common.consolidated_watch_list) { // TEMPORARILY PATCH FOR BROADCAST BEFORE MAKING this.threads
        if (['KILL','TIME','WATCH','UNWATCH','TRACK',/*'DELETE',*/].indexOf(cmd)!=-1) {
          if (!datetime) datetime = /*cmd!=='DELETE' && */ this.Get_posted_time(gClg.AllThreads[name], tgt_th, name) || 0; // to pass auto_list, gClg.AllThreads are set AFTER the auto_list procedure.
//            if (!datetime) datetime = cmd!=='DELETE' && (gClg.AllThreads[name][8][4] || gClg.AllThreads[name][8][0]) || 0; // auto_list doesn't work
          var millisec;
          var time_str = '@' + (datetime<0x10000? datetime : new Date(datetime).toLocaleString() + ((millisec = datetime%1000)? '.'+('00'+millisec).slice(-3) : ''));
//          var time_str = '@' + new Date(datetime).toLocaleString() + (millisec? '.'+('00'+millisec).slice(-3) : '');
          var wt_now = this.pref.filter.watch_list_str;
          var wt_str = wt_now.replace(key,'');
          var wt_has = wt_now.length !== wt_str.length 
          if (broadcast===false || wt_has || pref.common.consolidated_watch_list || pref.triage.sync_watch==='all' || broadcast===0 && pref.triage.sync_watch==='bg') {
            var wt_add = '';
//              if (cmd!=='DELETE') {
            if (cmd==='WATCH' || cmd==='TRACK' || cmd==='TIME') { //  && pref.auto_watch.watch) {
              wt_add = time_str; // TIME or WATCH
              if (/*broadcast===false ||*/ tgt_th) this.mark_read_thread(name, datetime, cmd==='TRACK' && attr); // attr has count of unread posts
            } else {
              if (cmd==='UNWATCH' && !wt_has) wt_add = '@0'; // explicit unwatch
              if (/*broadcast===false ||*/ tgt_th) this.mark_read_thread(name, null);
            }
//              }
            if (wt_add || wt_has) { // for speed up 'KILL'
              watch_list.value = this.pref.filter.watch_list_str = this.pref.filter.watch_list_obj2._revise(name, wt_add, wt_str); // = pref_func.fmt4str2(wt_add? wt_str+','+wt_add+'\n' : wt_str);
              changed.watch = true;
//              if (!pref.test_mode['190']) this.pref.filter.watch_list_obj2._revise(name, wt_add);
            }
          }
        }
//          if (cmd.search(/KILL|TIME|NONE/)!=-1) {
        if (!from_LSmirror) if (['KILL','TIME','NONE',/*'DELETE',*/'UNWATCH'].indexOf(cmd)!=-1) {
          if (broadcast===false || ex_list.value.search(key)!=-1 || pref.triage.sync_ex==='all' || broadcast===0 && pref.triage.sync_ex==='bg') {
            var ex_now = this.pref.filter.list_str;
            var ex_str = ex_now.replace(key,'');
            var ex_force = cmd==='KILL'; // force to add entry without ex_add
            var ex_add = tgt_th && cmd==='TIME'? time_str : '';
//            var ex_add = (cmd==='NONE' || cmd==='UNWATCH' || !tgt_th)? '' : cmd==='TIME'? time_str : true; // true is dummy for _reviseObj to make entry without args // BUG.
            if (ex_force || ex_add || ex_now.length !== ex_str.length) {
              ex_list.value = this.pref.filter.list_str = this.pref.filter.list_obj2._revise(name, ex_add, ex_str, ex_force); // = pref_func.fmt4str2(ex_add? ex_str+','+ex_add+'\n' : ex_str);
              changed.ex = true; // eliminate this flag as much as possible to speed up 'NONE'
//              if (!pref.test_mode['190']) this.pref.filter.list_obj2._revise(name, ex_add, ex_force);
            }
          }
        }
//}
////          if (['KILL','TIME','NONE','DELETE'].indexOf(cmd)!=-1) {
////            var at_str = attr_list.value.replace(key,',') + ((attr!=='')? ',' + name + '^' +attr : '') +'\n';
////            attr_list.value = at_str.replace(/,,+/g,',').replace(/^,/g,'').replace(/\n,/g,'\n').replace(/\n\n+/g,'\n').replace(/^\n/,'');
////            changed.attr = true;
////          }
        if (!from_LSmirror) if (!cmd_N && ['KILL','TIME','NONE',/*'DELETE',*/'ATTR','SHOW','UNSHOW','STICKY','UNSTICKY'].indexOf(cmd)!=-1 && (broadcast===false || pref.triage.sync_attr==='all' || broadcast===0 && pref.triage.sync_attr==='bg')) { //  || attr_list.value.search(key)!=-1)) {
          changed.attr = this.triage_exe_1_attr(attr_list, tgt_th,    name,cmd,attr, key);
        }
        return changed;
      };
      Clg.prototype.triage_exe_1_attr = function(attr_list, tgt_th,    name,cmd,attr, key){
//            var at_str;
//            if (cmd==='DELETE') at_str = '';
//            else {
////              var at_obj_all = pf.attr_list_obj2;
          var at_now = this.pref.filter.attr_list_str;
          if (tgt_th) {
            var matched = at_now.match(key);
            var at_desc = (matched? matched.join():'') + (cmd==='ATTR' && attr? (matched?',':'')+ name+';'+attr : '') || name;
            var at_obj = pref_func.str2obj2(at_desc, {}, true)[name];
//            var at_obj_all = pref_func.str2obj2((matched)? matched.join() : name, {}, true);
//            if (cmd==='ATTR' && attr) at_obj_all = pref_func.str2obj2(name+';'+attr, at_obj_all, true);
//            var at_obj = at_obj_all[name];
            var at_style = at_obj.style || {};
            var at_cmd = at_obj.cmd || {};
            if (cmd==='SHOW') at_cmd['SHOW'] = true;
            else if (cmd==='UNSHOW' || cmd==='KILL') delete at_cmd['SHOW'];
            else if (cmd==='STICKY') {
              if (tgt_th[16].systemSticky) delete at_cmd['STICKY'];
              else at_cmd['STICKY'] = true;
            } else if (cmd==='UNSTICKY') {
              var gattr = this.pref.catalog.style_general_list? pref_func.merge_obj5(name,pref.catalog.style_general_list_obj2,null) : null; // style_general_list is global, but use this.pref instead of pref intentionally for dashboard.rss.revise_general_list_str to emulate.
              if (tgt_th[16].systemSticky || gattr && gattr.cmd && gattr.cmd['STICKY']) at_cmd['STICKY'] = false;
              else delete at_cmd['STICKY'];
            }
            var style_str = (['KILL','TIME','NONE'].indexOf(cmd)!=-1)? attr
                : Object.keys(at_style).map(function(k){return k+':'+at_style[k];}).join(';');
            var cmd_str = Object.keys(at_cmd).map(function(k){return k+(at_cmd[k]===true? '' : ':'+at_cmd[k]);}).join(';');
            var at_add = (style_str? ';'+style_str:'') + (cmd_str? ';'+cmd_str:'');
//              at_str = name;
//              if (['KILL','TIME','NONE'].indexOf(cmd)!=-1) {
//                if (attr!=='') at_str += '^' +attr;
//              } else { // 'ATTR'
//                var at_obj_keys = Object.keys(at_obj.style);
//                if (at_obj_keys.length!=0) {
//                  for (var i=0;i<at_obj_keys.length;i++) at_obj_keys[i] += ':' + at_obj.style[at_obj_keys[i]];
//                  at_str += '^' + at_obj_keys.join(';');
//                }
//              }
//              at_obj_keys = Object.keys(at_obj.cmd);
//              if (at_obj_keys.length!=0) {
//                for (var i=0;i<at_obj_keys.length;i++) if (at_obj.cmd[at_obj_keys[i]]===false) at_obj_keys[i] += ':false';
//                at_str += '!' + at_obj_keys.join(';');
//              }
//              at_str = (at_str === name)? '' : at_str + '\n';
          }
          var at_str = (matched || !tgt_th)? at_now.replace(key,'') : at_now;
          if (matched || at_add || at_now.length!==at_str.length) { // for 'KILL'
            attr_list.value = this.pref.filter.attr_list_str = this.pref.filter.attr_list_obj2._revise(name, at_add, at_str); // = pref_func.fmt4str2(at_add? at_str+','+at_add+'\n' : at_str);
//            if (!pref.test_mode['190']) this.pref.filter.attr_list_obj2._revise(name, at_add);
            return true;
          }
          return false;
      };
      Clg.prototype.triage_rewind_watch = function(changed){
        var names = (changed.names || [changed.name]).filter(function(name){return this.threads[name];},this);
        for (var i=names.length-1;i>=0;i--) {
          var name = names[i];
          var lth = liveTag.mems.getFromName(name);
          this.set_watch_time_thread(name, this.threads[name][8][1], null, lth.wt, true, this.threads[name][8][2]); // this.threads[name][8][2] is redundant, it won't be used when rewind===true
//          var watch_time = this.get_watch_time_of_a_thread(name,this.threads[name][8][1], null); // working code
//          if (watch_time) site2['DEFAULT'].check_reply.set_watch_time(lth.wt, watch_time, name);
//          else site2['DEFAULT'].check_reply.set_unwatch(lth.wt);
          liveTag.dirtify_ur(lth); // probably redundant.
//          if (i!==0) this.triage_exe_view(name, changed);
//          this.idx_reorder(name); // slightly redundant because the first case will be executed in triage_exe_view, while no good way to avoid it because of following splice.
////            this.footer.queue_update_force(name);
////            this.ths_undo[name] = null; // too heavy in insert_thread_1, calling redundant idx_reorder is the better.
//          } else names.splice(i,1);
        }
        if (names.length!=0) scan_boards.scan_init('triage_undo', names, {refresh:this, priority:8, callback:this.triage_undo_callback.bind(this,names)});
      };
      Clg.prototype.triage_undo_callback = function(names){ // Undo can't rewind number of unread posts, so scan is needed. But it doesn't call idx_reorder and footer.update when there is no updates.(usual case)
        for (var i=0;i<names.length;i++) {
          this.idx_reorder(names[i]);
          this.footer.update_force(names[i]); // rewinds num of unread posts
        }
        this.show_catalog(0);
      };
      
      var triage = (function(){
        var trg_hover = null;
        var trg_menu = null;
        var trg_pMenu = null;
        var trg_now = null;
        var trg_submenu_root = null;
        var trg_submenu_leaf = null;
        function format_buttons(name, trg, clg){ // , expanded){
          var tgt = trg.format_tgt;
//          var tgt_th = clg.threads[name]; // get_clg().threads[name];
//          if (!tgt_th) return; // this is a patch for clone_thread, this will be able to be removed.
          var merge = typeof(name)==='string'? null : name;
//          var merge = (expanded)? (typeof(name)==='string'? null : name) : clg.merge_bases.bases[name];
          var lth = merge? null : liveTag.mems.getFromName(name);
          var tgt_th = merge? null : clg.threads[name];
          for (var i=0;i<tgt.length;i++) tgt[i][0].style.display = (merge? format_funcs.merge(merge.tgt_ths, merge.lths, tgt[i][1])
                                                                         : format_funcs[tgt[i][1]](tgt_th,lth))? 'none' : '';
//          if (merge) { // working code
//            for (var i=0;i<tgt.length;i++) {
//              for (var j=0;j<merge.lths.length;j++) if (!format_funcs[tgt[i][1]](merge.tgt_ths[j],merge.lths[j])) break;
//              tgt[i][0].style.display = (j===merge.lths.length)? 'none' : '';
//            }
//          } else {
//            var lth = liveTag.mems.getFromName(name);
//            for (var i=0;i<tgt.length;i++) tgt[i][0].style.display = (format_funcs[tgt[i][1]](tgt_th,lth))? 'none' : '';
//          }
//          if (trg===trg_menu) for (var i=trg.brs.length-1;i>=0;i--) {
//            var br = trg.brs[i];
//            var p = br;
//            while (1) {
//              if (!p.previousSibling || (p = p.previousSibling).tagName==='BR') {br.style.display = 'none'; break;}
//              if (p.style.display!=='none') {br.style.display = ''; break;}
//            }
//          }
        }
        var format_funcs = { // YOU MUST ALSO CHANGE toggles in Triage.
          'WATCH': function(tgt_th){return (tgt_th[19][0]&0x0c0c0000) && tgt_th[19][1]===0;},
          'UNWATCH': function(tgt_th){return !(tgt_th[19][0]&0x0c0c0000);},
          'SHOW': function(tgt_th){return tgt_th[16].showAlways;},
          'UNSHOW': function(tgt_th){return !tgt_th[16].showAlways;},
          'STICKY': function(tgt_th){return tgt_th[20];},
          'UNSTICKY': function(tgt_th){return !tgt_th[20];},
          'ARC': function(tgt_th,lth){return lth.archived;},
          'ARC1': function(tgt_th,lth){return lth.archived;},
          'UNARC': function(tgt_th,lth){return !lth.archived;},
          merge: function(tgt_ths, lths, cmd){
            for (var j=0;j<lths.length;j++) if (!this[cmd](tgt_ths[j],lths[j])) return false;
            return true;
          },
        };
//        function get_triaged_catalog(){
//          return triaged_catalog || (triaged_catalog = Clg.prototype.ppn2clg(ppn));
//        }
        var triaged_catalog = null; // pClg;
        var triaged_name = null;
        var tpn = pClg.ppn; // initial value for mode change
        var on_off = (function(){
          var triaged_pn = null;
//          var db_thread_out = new DelayBuffer(thread_out,100);
//          var thread_out_delayed = DelayBuffer.prototype.delayed_do.bind(db_thread_out);
//          var db_triage_off = new DelayBuffer(triage_off,pref.triage.popdown_delay);
          var triage_off_wdg = new Watchdog(triage_off, 100);
          function set_triage_style(trg, clg, pn_th, pn_menu){
            var style = trg.pn.style;
//            if (trg_now!==trg) triage_off();
//            trg_now = trg;
//            if (!triaged_name) {
//              if (mode) ppn.appendChild(trg.pn);
//              else style.display = '';
//            }
            var left = pn_th.offsetLeft - clg.ppn.scrollLeft + (pn_menu? pn_menu.getBoundingClientRect().left - pn_th.getBoundingClientRect().left - 10 : 0);
//            var left = pn_th.offsetLeft - clg.ppn.scrollLeft + (pn_menu? pn_menu.offsetLeft - 10 : 0);
//            var left = pn_th.offsetLeft - clg.ppn.scrollLeft + (pn_menu? pn_menu.offsetLeft + pn_menu.offsetWidth : 0);
            if (pn_menu || pref[clg.view].triage_place.slice(-4)==='Left') { // 'topLeft' || 'bottomLeft'
              style.left = left + 'px';
              style.right = null;
            } else {
              style.right = clg.ppn.offsetWidth - left - pn_th.offsetWidth + 'px';
              style.left = null;
            }
            var mode_float = clg.mode==='float';
            var top = pn_th.offsetTop - (mode_float? clg.pppn.scrollTop + clg.ppn.scrollTop : 0) + (pn_menu? pn_menu.getBoundingClientRect().top - pn_th.getBoundingClientRect().top + pn_menu.offsetHeight : 0);
//            var top = pn_th.offsetTop - clg.pppn.scrollTop + (pn_menu? pn_menu.getBoundingClientRect().top - pn_th.getBoundingClientRect().top + pn_menu.offsetHeight : 0);
//            var top = (!mode_float && pn_menu)? pn_menu.getBoundingClientRect().top + window.pageYOffset + (pn_menu? pn_menu.offsetHeight : 0)
////                    : (!mode_float && pn_menu && pref[clg.mode].env.thread_pos_static)? pn_menu.offsetTop
//                    : pn_th.offsetTop - clg.pppn.scrollTop + (!mode_float && pn_menu? pn_menu.offsetTop : 0);
//            var top = pn.offsetTop - pppn.scrollTop;
            if (pn_menu || pref[clg.view].triage_place[0]==='t') { // 'topLeft' || 'topRight'
              style.top    = top + 'px';
              style.bottom = null;
            } else {
              style.bottom = ((mode_float)? clg.pppn.offsetHeight + clg.ppn.offsetTop : window.innerHeight) - top - pn_th.offsetHeight + 'px';
              style.top    = null;
            }
//            style.zIndex = pref.triage.zIndex;
          }
          function thread_in(e, name, pn_th, clg){
//            triaged_catalog = clg;
//            ppn = pref.test_mode['141']? clg.ppn.parentNode : clg.ppn;
//          function thread_in(e, name, ppn_in){
//            if (ppn!==ppn_in) triaged_catalog = null;
//            ppn = ppn_in;
            if (pref[clg.view].triage_hover) {
//              if (!pref.test_mode['127']) {
                if (triaged_pn===pn_th) return;
                if (pref.triage.popdown) triage_off_wdg.restart(pref.triage.popdown_delay);
                show_triage(trg_hover, name, pn_th, clg);
//              } else { // working code
//                var pn = e.currentTarget;
//                db_thread_out.cancel();
//                if (triaged_pn===pn) return; // for faster execution.
//                if (!triaged_name) {
//                  if (mode) pn12_triage = ppn.appendChild(pn12_triage);
//                  else pn12_triage.style.display = '';
//                }
//                if (pref.triage.popdown) {db_triage_off.cancel();db_triage_off.delayed_do();}
//                if (triaged_pn) triaged_pn.removeEventListener('mouseout', thread_out_delayed, false);
//                var left = pn.offsetLeft - ppn.scrollLeft;
//                if (pref.catalog_triage_place==='topLeft' || pref.catalog_triage_place==='bottomLeft') {
//                  pn12_triage.style.left = left + 'px';
//                  pn12_triage.style.right = null;
//                } else {
//                  pn12_triage.style.right = ppn.offsetWidth - left - pn.offsetWidth + 'px';
//                  pn12_triage.style.left = null;
//                }
//                var top = pn.offsetTop  - ppn.scrollTop;
//                if (pref.catalog_triage_place==='topLeft' || pref.catalog_triage_place==='topRight') {
//                  if (top<ppn.offsetTop) top = ppn.offsetTop;
//                  pn12_triage.style.top   = top + 'px';
//                  pn12_triage.style.bottom = null;
//                } else {
//                  pn12_triage.style.bottom = window.innerHeight - top - pn.offsetHeight + 'px';
//                  pn12_triage.style.top = null;
//                }
//                triaged_pn = e.currentTarget;
//                triaged_name = triaged_pn.name;
//                triaged_pn.addEventListener('mouseout', thread_out_delayed, false);
//                if (trg_hover.format_tgt) format_buttons(triaged_name, trg_hover);
//              }
            } else if (triaged_pn!==pn_th) triage_off();
          }
//          function thread_out(){
//            if (triaged_pn) {
//              triaged_pn.removeEventListener('mouseout', thread_out_delayed, false);
//              triaged_pn = null;
//            }
//            triage_off();
//          }
// CAUTION, change_mode DOESN'T WORK with watcher, NOT IMPRELEMTED YET.
          var mode = true; // add/remove or show/hide
          function change_mode(){
            tpn.appendChild(trg_hover);
            tpn.appendChild(trg_menu);
            mode = false;
          }
          function triage_on(trg, name){
            if (trg_now!==trg) triage_off();
            trg_now = trg;
            if (!triaged_name) {
              if (mode) tpn.appendChild(trg.pn);
              else trg.pn.style.display = '';
            }
            triaged_name = name;
          }
          function triage_off(){
            if (trg_now) Tooltips.hide_if(trg_now.pn);
            if (triaged_name) {
              if (trg_submenu_root) {
                trg_submenu_root.remove();
                trg_submenu_root = null;
              }
              if (mode) trg_now.pn.parentNode.removeChild(trg_now.pn); // for reentry, triage_parent wasn't updated when !pref[embed_mode].embed, for example, catalog -> thread -> catalog wiil cause an error.
//              if (mode) trg_now = triage_parent.removeChild(trg_now);
              else trg_now.pn.style.display = 'none';
              triaged_name = null;
              trg_now = null;
              if (pn_menuOpen) pn_menuOpen.classList.remove('menuOpen');
              pn_menuOpen = null;
            }
          }
          function thread_out2(e){
            triaged_pn = null;
            triaged_catalog = null;
            triage_off();
          }
//          function onTriage_or_off(e){
//            var pn = e.target;
//            while (pn!=e.currentTarget){
//              if (pn.className==='catalog_triage_parent') {
//                triage_off_wdg.stop();
//                return;
//              } else pn = pn.parentNode;
//            }
//            triaged_pn = null;
//            triage_off();
//          }
          var pn_menuOpen = null;
          function make_menu(et, name, clg, pn_th, post){
//            triaged_catalog = clg;
//            ppn = pref.test_mode['141']? clg.ppn.parentNode : clg.ppn;
            var trg = post? trg_pMenu : trg_menu;
            if (triaged_pn===pn_th && trg_now===trg && name===triaged_name) {triage_off(); if (!post || pn_menuOpen===et) return;} // toggle
            triage_off_wdg.stop();
            show_triage(trg, name, pn_th, clg, et);
            var ddEcW = document.documentElement.clientWidth;
            var left = parseInt(trg.pn.style.left,10);
            if (trg.pn.getBoundingClientRect().left + trg.pn.offsetWidth>=ddEcW) trg.pn.style.left = left - (trg.pn.offsetWidth - et.offsetWidth - 10*2)+'px'; // trg.pn.offsetWidth is reqiured this to be after displaying, it's 0 before show.
//            if (left + trg.pn.offsetWidth>=ddEcW) {trg.pn.style.right = 0; trg.pn.style.left = null;} // trg.pn.offsetWidth is reqiured this to be after displaying, it's 0 before show.
            var ddEcH = document.documentElement.clientHeight;
            var top = parseInt(trg.pn.style.top,10);
            if (trg.pn.getBoundingClientRect().top + trg.pn.offsetHeight>=ddEcH) trg.pn.style.top = top - trg.pn.offsetHeight - et.offsetHeight +'px'; // works fine.
            pn_menuOpen = et;
            et.classList.add('menuOpen');
          }
          function show_triage(trg, name, pn_th, clg, pn_menu){
            triaged_catalog = clg;
            tpn = pref.test_mode['141']? clg.pppn : clg.ppn;
            set_triage_style(trg, clg, pn_th, pn_menu);
            triaged_pn = pn_th; // SHOULD USE PN FOR MULTI CATALOG.
            if (trg.format_tgt) format_buttons(name, trg, clg); // , pn_menu);
            triage_on(trg, name);
          }
          return {
            change_mode: change_mode,
            thread_in:  thread_in,
            off: triage_off,
//            off_delay: (!pref.test_mode['127'])? null : function(){db_triage_off.delayed_do(500);},
            triage_in: /* (!pref.test_mode['127'])?*/ function(e){triage_off_wdg.stop();e.stopPropagation();gGEH.triage_in2(e); if (gGEH.triage_in) gGEH.triage_in(e);}/*
                                                : function(){                              db_triage_off.cancel();    db_thread_out.cancel();}*/,
            triage_out: /*(!pref.test_mode['127'])?*/ function(){if (trg_now!==trg_menu) if (pref.triage.popdown) triage_off_wdg.restart(pref.triage.popdown_delay);}/*
                                                : function(){if (pref.triage.popdown) db_triage_off.delayed_do();db_thread_out.delayed_do();}*/,
//            onTriage_or_off: onTriage_or_off,
            thread_out2: thread_out2,
            make_menu: make_menu,
            get triaged_pn(){return triaged_pn;},
            get pn_menuOpen(){return pn_menuOpen;},
          }
        })();
        function make_1(str, args){
          var trg = new Triage(str, args);
          trg.pn.onmouseover = on_off.triage_in;
          trg.pn.onmouseout  = on_off.triage_out;
          if (!pref.triage.hide_toggle) trg.format_tgt = null;
          return trg;
        }
        function triage_wheel(e){ // patch
          if (triaged_catalog.mode!=='float') return;
          on_off.off();
          Tooltips.hide();
  //        triage_parent.dispatchEvent(e); // copy is required.
          e.preventDefault();
          var evt = document.createEvent('MouseEvents');
          evt.initMouseEvent(e.type, e.canBubble, e.cancelable, e.view,
                     e.detail, e.screenX, e.screenY, e.clientX, e.clientY,
                     e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
                     e.button, e.relatedTarget);
          triaged_catalog.ppn.dispatchEvent(evt);
        };
        function triage_onclick(cmd,attr, et){
          triage_event(triaged_catalog, triaged_name, cmd, attr, et);
          if (cmd!=='MERGEN') on_off.off();
        }
        function triage_event(clg, key, cmd, attr, et, pn_th, e){
          if (cmd==='MENU' || cmd==='PMENU') {
            if (key) {
              on_off.make_menu(et, key, clg, pn_th, cmd==='PMENU');
              site2['DEFAULT'].popups_posts.pop_down_temporarily(e);
            }
          } else if (cmd[0]==='P') {
//            while (pn_src.classList.contains(pref.cpfx+'postMenu')) pn_src = pn_src.parentNode; // this is needed when postMenu is configurable.
            var keyTime = site2[site.nickname].general_event_handler[site.whereami].menu2keyTime(on_off.pn_menuOpen);
//            var keyTime = on_off.pn_menuOpen.getAttribute('data-attr').split(/[#@]/);
            if (cmd==='PWATCH') clg.triage_event(keyTime[0], 'WATCH', '', keyTime[2]);
            else hiddenPosts.cmd(clg, keyTime[0], keyTime[1], on_off.pn_menuOpen, cmd); // keyTime[0] instead of key is for merge
          } else clg.triage_event(key,cmd,attr, undefined, pn_th || on_off.triaged_pn, et); // trg_now!==trg_hover);
        }
        function make_triage(){
          var args = {onclick:triage_onclick, onmousewheel:triage_wheel, style:'pointer-events:none;position:absolute'}; // , name:'pn_catalog_triage'}
          trg_hover = make_1(pref.catalog_triage_str, args);
          trg_menu  = make_1(pref.triage.menu_str, {menu:true, child_style:'width:100%;', class:'menu', __proto__:args});
          trg_pMenu = make_1(pref.triage.postMenu_str, {menu:true, child_style:'width:100%;', class:'postMenu', __proto__:args});
        }
        make_triage();

        function embTri(cmd, caption){
          return '<a class="'+pref.cpfx+'embeddedTriage" data-cmd="'+cmd+'">'+caption+'</a>';
        }
        var htmls = {
          'REMOVET': '['+embTri('MERGET','Merge')+']',
          'MERGET': '['+embTri('REMOVET','Remove')+']['+embTri('UNMERGET','Unmerge')+']',
          'UNMERGET': '['+embTri('MERGET','Merge')+']['+embTri('REMOVET','Remove')+']',
//          mergee: ' <a class="'+pref.cpfx+'button" data-type="triage" data-cmd="MERGEE">[M]</a>',
        };
        return {
//          get_triaged_thread_name: function(){return triaged_name;},
//          get_triaged_catalog: function(){return triaged_catalog;},
          change_mode: on_off.change_mode,
          thread_in:  on_off.thread_in,
          off: on_off.off,
//          off_delay: on_off.off_delay,
          triage_in:  on_off.triage_in,
          triage_out: on_off.triage_out,
//          set_trg: function(trg){trg_hover = trg;},
//          get_trg: function(){return trg_hover;},
//          onTriage_or_off: on_off.onTriage_or_off,
          thread_out2: on_off.thread_out2,
          destroy: function(){
            this.off();
            trg_hover.destroy();
            trg_menu.destroy();
            trg_pMenu.destroy();
          },
          remake: function(){
            this.destroy();
            make_triage();
          },
//          make_menu: on_off.make_menu,
          embedded_onclick: function(e,clg, tr){
            var key = gGEH.get_key_recursive(e.target, e.currentTarget, true);
            if (!tr) tr = Triage.prototype.et2tr(e.target);
            var pn_th = cataLog.GEH.prototype.geh.recSearch_thread(e.target, e.currentTarget, clg.view==='headline'? pref.cpfx+'headline' : undefined); // copied from popups_posts.triage
            triage_event(clg, key, tr.cmd, tr.attr, e.target, pn_th, e);
//            if (tr.cmd==='MENU' || tr.cmd==='PMENU') {
//              if (key) on_off.make_menu(e.target, key, clg, pn_th, tr.cmd==='PMENU');
//            } else clg.triage_event(key,tr.cmd,tr.attr, undefined, pn_th);
          },
//          postMenu: function(attr){
//            var pn = Triage.prototype.postMenu.cloneNode(true);
//            pn.setAttribute('data-attr',attr);
//            return pn;
//          },
          htmls: htmls,
          doms: {
            emMerge: cnst.dom('<span>'+htmls['REMOVET']+'</span>'),
            postMenu: cnst.dom(embTri('PMENU','\u25b6')),
          },
          emb_get_key: function(et){
            var href;
            while (!(et.getAttribute && (href = et.getAttribute('href')))) et = et.previousSibling;
            var domain = site2['DEFAULT'].popups_posts.href2domain(href);
            return site2[domain].popups_href2dbtp(href).slice(0,3).join('');
          },
          add_submenu(ppn, args){
            var pn = make_1(args, {onclick:triage_onclick, onmousewheel:triage_wheel, style:'pointer-events:auto;position:absolute;top:0px;left:'+(ppn.offsetWidth-5)+'px;width:700px'}).pn; // width is temporarily
            ppn.appendChild(pn);
            trg_submenu_leaf = pn;
            if (trg_submenu_root) trg_submenu_root.remove();
            trg_submenu_root = pn;
          },
        };
      })();
      Triage.prototype.doms = triage.doms;
      var pn12_triage;
      cataLog.triage = triage;
                

//      function insert_thread_with_test_from_catalog_json(th, snoop, date_load){
//        if (threads[th.key] && threads[th.key][8][0]>=th.time_bumped && threads[th.key][8][2]==th.nof_posts && threads[th.key][8][3]==th.nof_files) return false;
//        site2[th.domain].parse_funcs['catalog_json'].entry(th,['key','pn']);
//
//        var date = [th.time_bumped, th.time_created, th.nof_posts, th.nof_files]; // temporal
//        th.search_obj = [ th.com, th.sub, th.name, '', '', '', '', '']; // temporal
//        var url = site2[th.domain].make_url3(th.board, th.no, '0'); // temporal
//        insert_thread(th.pn, th.domain, th.page, date_load, th.key, th.pn.innerHTML, date, th.search_obj, url, true, th);
//        return true;
//      }
//      function insert_thread_with_test_from_catalog_html(th, snoop, date_load){
//        if (threads[th.key] && threads[th.key][8][0]>=th.time_bumped && threads[th.key][8][2]==th.nof_posts && threads[th.key][8][3]==th.nof_files) return false;
////        var th2 = site2[th.domain].catalog_from_native_1(th.pn,th.board);
////        insert_thread_from_native(th2, th.domain, th.board, snoop, date_load);
//        site2[th.domain].parse_funcs['catalog_html'].entry(th,['sub','name','com','page']);
//        var date = [th.time_bumped, th.time_created, th.nof_posts, th.nof_files]; // temporal
//        th.search_obj = [ th.com, th.sub, th.name, '', '', '', '', '']; // temporal
//        var url = site2[th.domain].make_url3(th.board, th.no, '0'); // temporal
//        insert_thread(th.pn, th.domain, th.page, date_load, th.key, th.pn.innerHTML, date, th.search_obj, url, true, th);
//        return true;
//      }

      function insert_thread_with_test(th, type, date_load){
        var name = th.key;
//        if (threads_candidates_of_deletion && threads_candidates_of_deletion[name]) delete threads_candidates_of_deletion[name];
        if (threads[name] &&
//            ((threads[name][8][4] || threads[name][8][0])>=(th.time_posted || th.time_bumped)) && // vichan has inconsistency in time between catalog.json and thread.json.
            (((threads[name][8][0]>threads[name][8][4])? threads[name][8][0] : (threads[name][8][4] || threads[name][8][0])) >= ((th.time_bumped > th.time_posted)? th.time_bumped : (th.time_posted || th.time_bumped))) && // vichan has inconsistency in time between catalog.json and thread.json.
            threads[name][8][2]==th.nof_posts && threads[name][8][3]==th.nof_files
          && (!threads[name][21] || (th.type_parse!=='thread_html' && th.type_parse!=='thread_json'))) {
//        if (threads[name] && threads[name][8][0]>=th.time_bumped && threads[name][8][2]==th.nof_posts && threads[name][8][3]>=th.nof_files) {
////////          if (threads[name] && pref.catalog_footer_show_page && threads[name][24] && threads[name][24][2]!=th.page) insert_footer3(name,null,th.page);
          return false;
        }
if (pref.test_mode['0']) {
        site2[th.domain].parse_funcs[type].entry(th,site2[th.domain].parse_funcs[type]['after_test']);
}
        insert_thread_passed_test(th, type, date_load);
        return true;
      }
      function insert_thread_passed_test(th, type, date_load){
//        var date = [th.time_bumped, th.time_created, th.nof_posts, th.nof_files]; // temporal
//        th.search_obj = [ (th.com)? th.com : '', (th.sub)? th.sub : '', (th.name)? th.name : '', '', '', '', '', '']; // temporal
        var url = null;
////        if (type==='thread_html' || type==='page_html') {
//////          if (site.nickname!==th.domain) site2[th.domain].absolute_link(th.pn, th.board); // patch for url. BUT BUG.
////          url = site2[th.domain].get_thread_link(th.pn,th.board,pref.catalog_click!='expand',th.key);
////          th.html_org = th.pn.innerHTML; // patch, must be before trim.
////if (!pref.test_mode['5']) {
////          trim_html(th.pn, th.domain, pref.catalog_format.show, th.key); // temporal
////}
////        }
//////        } else url = site2[th.domain].make_url3(th.board, th.no, '0'); // temporal
//////        site2[th.domain].parse_funcs[type]['finisher'](th);
//        insert_thread(null, th.domain, th.page, date_load, th.key, (th.html_org)? th.html_org : th.pn.innerHTML, date, th.search_obj, url, true, th, type);
        pClg.insert_thread(null, th.domain, th.page, date_load, th.key, null, th.search_obj, url, true, th, type);
      }

////////      function insert_thread_from_native(th, nickname, board, snoop, date_load){
////////        var name = nickname + board + th.no;
////////        var date = [th.time_bumped, th.time_created, th.nof_posts, th.nof_files];
////////        var url = site2[nickname].make_url3(board, th.no, '0');
//////////        if (threads[name] && threads[name][8][0]>=date[0]) return 0;
////////        if (threads[name] && threads[name][8][0]>=date[0] && threads[name][8][2]==th.nof_posts && threads[name][8][3]==th.nof_files) return 0;
//////////        if (threads[name] && threads[name][8][0]==date[0] && threads[name][8][2]!=th.nof_posts && nickname==='8chan' && pref.catalog.order.find_sage_in_8chan) threads[name][21] = true; // for 8chan.
////////        return insert_thread(th.pn, nickname, th.page_no, date_load, name, th.pn.innerHTML, date, th.search_obj, url, true, th);
////////      }

////////      function re_trim_html(){
////////        for (var name in threads) {
////////          var date = get_mark_time(name,pref.filter.time_mark,pref.filter.list_mark_time,pref.filter.watch_list_mark_time);
////////          if (date===0) date = Infinity;
////////          var dbt = cnst.name2domainboardthread(name,true);
////////          site2[dbt[0]].mark_newer_posts(threads[name][0],date);
////////        }
////////      }
////      function trim_html(src,nickname,format, name){ // working code
////if (!pref.test_mode['7']) { // memory leak debug.
////  if (!pref.test_mode['8']) {
////    if (!pref.test_mode['9']) {// safe
////      if (!pref.test_mode['10']) { // safe 30 min.
////if (!pref.test_mode['11']) {
////        if (!format.fileinfo) site2[nickname].remove_files_info(src);
////}
////if (!pref.test_mode['12']) {
//////        if (!format.posts)    site2[nickname].remove_posts(src,0);
////        if (pref[embed_mode].t2h_num_of_posts>=0) site2[nickname].remove_posts(src,(!format.posts)? 0 : pref[embed_mode].t2h_num_of_posts);
////}
////if (!pref.test_mode['13']) {
////        if (format.contents)  site2[nickname].format_thread_contents(src);
////}
////      }
////        if (format.layout)    site2[nickname].format_thread_layout(src);
////        if (format.style)     site2[nickname].format_thread_style(src);
////                              site2[nickname].format_thread_always(src);
////    }
////        if (pref[embed_mode].localtime) site2[nickname].localtime(src);
////    //        if (pref.filter.time_mark) site2[nickname].mark_newer_posts(src,date);
////  }
////}
////      }
////      function trim_html_mark_time(src,nickname,format, name){
////        var date = get_mark_time(name, pref[embed_mode].mark_new_posts, pref[embed_mode].mark_new_posts, pref[embed_mode].mark_new_posts);
////        if (date>0) site2[nickname].mark_newer_posts(src,date);
////        if (pref[embed_mode].format.thumb.resize) site2[nickname].format_remove_tn_area_size(src);
////      }
////////      function insert_thread_from_page(src, nickname, boardname, op_no, page_no, nof_posts, nof_files, snoop, date_load){
////////        var name = nickname + boardname + op_no;
////////        if (snoop && !pref.catalog_promiscuous && !threads[name]) {
////////          var hit = false;
////////          for (var i=0;i<load_list.refresh.tgts.length;i++)
////////            if (load_list.refresh.tgts[i][0].indexOf(nickname+boardname+'p'+page_no)!=-1 ||
////////                load_list.refresh.tgts[i][0].indexOf(nickname+boardname+op_no)!=-1) {hit=true;break;}
////////          if (!hit) return 0;
////////        }
////////        var date = site2[nickname].get_time_of_posts(src);
////////        if (threads[name] && threads[name][13]<date_load) update_page_in_footer(name,page_no,date_load);
////////        if (threads[name] && threads[name][8][0]>=date[0]) return 0;
//////////console.log('In :'+name);
////////        var url = site2[nickname].get_thread_link(src,boardname,pref.catalog_click!='expand',name);
//////////        var date_mark = Date.parse(pref.filter.time_str) - pref.localtime_offset*3600000;
//////////        console.log(name+', '+date);
//////////        var src1 = document.createElement('div');
//////////        src1.innerHTML = src.innerHTML;
////////        var html_org = src.innerHTML;
////////        var src2 = document.createElement('div');
////////        src2.innerHTML = src.innerHTML;
////////        var cross_domain = pref.catalog_board_list_obj[board_sel.selectedIndex][0]['domain']!=nickname;
////////        var cross_board  = pref.catalog_board_list_obj[board_sel.selectedIndex][0]['board']!=boardname;
////////        date = date.concat(site2[nickname].insert_footer(src,page_no,((cross_domain)? nickname : '')+((cross_board)? boardname : ''),pref.catalog_footer,date,nof_posts,nof_files));
//////////        trim_html(src,  nickname, pref.catalog_format.show, date_mark);
////////////        trim_html(src1, nickname, pref.catalog_format.hover, date_mark);
//////////        trim_html(src2, nickname, pref.catalog_format.search, date_mark);
////////        trim_html(src,  nickname, pref.catalog_format.show, name);
////////        trim_html(src2, nickname, pref.catalog_format.search, name);
////////        return insert_thread(src, nickname, page_no, date_load, name, html_org, date, src2, url, false, {board:boardname});
////////      }
//      function convert_html(name,th){
//        if (!embed_catalog && pref.catalog.text_mode.mode==='text') {
//          var text = document.createElement('span');
//          text.innerHTML = ((pref.catalog.text_mode.sub  && threads[name][4][1][0])? '&emsp;' + threads[name][4][1][0] : '') +
//                           ((pref.catalog.text_mode.name && threads[name][4][2][0])? '&emsp;' + threads[name][4][2][0] : '') +
//                           ((pref.catalog.text_mode.com  && threads[name][4][0][0])? '&emsp;' + threads[name][4][0][0] : '');
//          var footer_new = document.createElement('span');
//          footer_new.innerHTML = th.footer.innerHTML; // make footer on demand.
//          th.footer = footer_new;
//          th.pn.innerHTML = '';
//          th.pn.appendChild(th.footer);
//          th.pn.appendChild(text);
//          threads[name][18] = 'text';
//        } else {
//          th.pn.innerHTML = threads[name][25][1];
//          th.footer = threads[name][25][0];
//        }
//      }

////////
//////// safe758
////////              page                  catalog + thread
//////// adoptNode    works with errors     works with errors
//////// importNode   works with errors     works with errors
//////// None         works with no errors  works with no errors
////////
//////// Memory leak was found with None (document leaks when I read KC's one), but I'll go with None.
////////
//      function insert_thread(src, nickname, page_no, date_load, name, html_org, src2, url, from_native, th, type){
      function merge_deleted_posts(lth, posts, num, t2h){
        return site2[lth.domain].update_posts_merge_prep(posts, lth.pd, (num===false)? posts.length-1 : (num===-1)? -1 : num, true); // don't have to consider num of deleted posts, it's a sync issue between thread and catalog. Unread deleted posts are in unread posts always.
//        return site2[lth.domain].update_posts_merge_prep(posts, lth.pd, (num===false)? posts.length-1 : (num===-1)? -1 : num + ((t2h==='unread' || t2h==='N_unread')? lth.pd.length : 0), true); // patch; count of unread posts doesn't contain deleted posts.
      }
      var tgt_th16_proto = {
        get no(){return this.dbt[2];},
        get board(){return this.dbt[1];},
        get domain(){return this.dbt[0];},
        get key(){return this.dbt.join('');},
        get nof_posts(){return liveTag.mems.getFromName(this.key).nof_posts;},
        get nof_files(){return this.threads[this.key][8][3];},
//        get time(){return this.posts[0].time || threads[name][8][1];},
////        get sub(){return threads[this.key][4].sub;}, // this.posts && this.posts[0].sub;}, // for reentry in meguca
////        get com(){return threads[this.key][4].com;}, // this.posts && this.posts[0].com;}, // for reentry in meguca
////        get txt(){return threads[this.key][4].txt;}, // this.posts && this.posts[0].txt;}, // for reentry in meguca
        get tags(){return liveTag.mems.getFromName(this.key);},
        recent_posts: function(num, posts_in) { // , remake){
          if (num===undefined || num===null) num = this.get_t2h_num_of_posts();
          var posts = posts_in;
          if (num!==false && this.lth.ta) posts = site2[this.lth.domain].update_posts_replace_prep(null, this.lth.ta.posts, num); // cut // if (lth.ta) is required for merge/merge_list.
////          if (num!==false && !remake) { // working code
////            if (!pref.test_mode['113']) {
////              if (this.lth.ta) posts = site2[this.lth.domain].update_posts_replace_prep(null, this.lth.ta.posts, num); // cut // if (lth.ta) is required for merge/merge_list.
////            } else {
////              if (this.th && this.th.posts) posts = site2[this.lth.domain].update_posts_replace_prep(posts || this.th.posts, this.th.posts, num); // merge, posts===this.th.posts occurs also.
//////            var posts = this.th && this.th.posts;
////              if (this.posts && (num!=-1 || this.posts!==posts)) posts = site2[this.lth.domain].update_posts_replace_prep(posts || this.posts, this.posts, num); // merge // MUST CHANGE ADDRESS OF th.posts, 'th.posts===th_old.posts' is used 'insert_thread_format_html'
////            }
////          } 
//          if (pref.test_mode['135']) if (!posts_in) {
//            if (posts && this.lth.pd) posts = posts.filter(function(v){return !v.deleted_after;}); // posts may contain >num deleted posts.
//            if ((!posts || num<0 || posts.length<=num) && this.lth.ta) posts = site2[this.lth.domain].update_posts_replace_prep(posts || this.lth.ta.posts, this.lth.ta.posts, num);
//          }
          if (!posts) posts = [this.op];
//          var posts = this.th && this.th.posts || this.lth.ta && this.lth.ta.posts && site2[this.lth.domain].update_posts_replace_prep(this.posts || this.lth.ta.posts, this.lth.ta.posts, num) || this.posts;
          return (!pref.test_mode['64'] && pref[embed_mode].deleted_posts.merge && this.lth.pd)? merge_deleted_posts(this.lth, posts, num, pref[embed_mode].t2h_sel) : posts;
        },
        get_t2h_num_of_posts: function(){
          var t2h_sel = this.t2h_sel || pref[embed_mode].t2h_sel;
          return (t2h_sel==='page')? this.t2h_page || pref[embed_mode].t2h_num_of_posts
            : (t2h_sel==='L')? pref[embed_mode].t2h_L
            : (t2h_sel==='M')? pref[embed_mode].t2h_M
            : (t2h_sel==='N')? pref[embed_mode].t2h_num_of_posts
            : (t2h_sel==='N_unread')? pref[embed_mode].t2h_num_of_posts + this.lth.nr
            : (t2h_sel==='unread')? (this.lth.watched? this.lth.nr : this.t2h_page || pref[embed_mode].t2h_num_of_posts)
            : (t2h_sel==='no')? 0 : -1; // 'ALL' // and 'ALL_agg'
        },
        expand_num: function(){
          var num = this.get_t2h_num_of_posts()+1;
          var nof_posts = this.lth.nof_posts || this.lth.th.nof_posts; // this.lth.th.nof_posts for initial condition in page without any refresh
          return (num==0 || num>nof_posts)? nof_posts : num;
        },
        expand_event: function(idx, kwd_active){
          this.t2h_sel = format_html.get_t2h_from_index(idx);
          if (!kwd_active) if (this.check_and_expand(this.recent_posts(), true)) scan.scan_ui('expand_page', {tgts:[this.key], options:{refresh:this.clg}});
        },
        check_and_expand: function(posts, contract){
          var expand = posts.length < this.expand_num();
          if (expand) {
            this.expand_posts = true;
            scan.list_nup.add(this.key, 1); // for vichan which has only one post in catalog.
//            scan.scan_ui('expand_page', {tgts:[this.key], options:{refresh:this.clg}});
          } else if (contract) {
            var th = {posts:posts, __proto__:this.threads[this.key][7]};
//            site2[posts[0].domain_html].page_json2html3_add_omitted_info(th, this.posts, th.posts); // redundant, it's in 'update_posts_replace'.
            this.clg.insert_thread_prepare_html_lazy(this.threads[this.key], false, false, null, th);
            cataLog.show_catalog(this.key, true);
          }
          gClg.Maintain_AllThreads(this.key);
          return expand;
        },
      };
      Clg.prototype.insert_thread_dummy = function(domain, board, no){
        var proto = site2['DEFAULT'].wrap_to_parse.get_getters();
        var posts = [{domain:domain, board:board, no:no, key:domain+board+no,
                      sub:'', com:'DUMMY, waiting for archive...', body:'', name:'', id:undefined, trip:undefined, country:undefined, flag:null, op_img_url:undefined,
                      time_bumped:1, time_posted:1, time_created:1, nof_posts:1, nof_files:0,
                      parse_funcs:{type_com:'html'},
                      sticky:false,
                      __proto__:proto}];
        var th = {posts:posts, __proto__:posts[0]};
        liveTag.prep_tags(th);
        this.insert_thread(th, 0, null, {}, {});
        this.threads[domain+board+no][16].missing_info = 10000;
      };
      Clg.prototype.insert_thread = function(th, date_load, sb_merge, nos, sb_merge_op_not_done){
//        var date_load = date_load;
        var name = th.key;
        var insert_thread_from_native = (!pref.test_mode['94'] || th.pn) && th.insert_thread_from_native;
////if (!pref.test_mode['28']) {
////        if (site2[th.domain].preprocess_doc && !insert_thread_from_native) site2[th.domain].preprocess_doc(th.pn);
////}
////if (pref.test_mode['26']) { // Leak is caused by DOMParser itself.
//////        if (!insert_thread_from_native && th.type_data==='html') document.adoptNode(th.pn);  // KC causes memory leak if I don't use adoptNode(). // causes error in page mixing.
////        if (!insert_thread_from_native && th.type_data==='html') th.pn = document.adoptNode(th.pn);
////}
////if (pref.test_mode['27']) {
////        if (!insert_thread_from_native && th.type_data==='html') th.pn = document.importNode(th.pn, true);
////}
//        if (th.pn.parentNode) th.pn.parentNode.removeChild(th.pn); // redundant.
////        if (site.nickname!==th.domain && th.type_data==='html') site2[th.domain].absolute_link(th);
//////        if (site.nickname!==th.domain) site2[th.domain].absolute_link(th); // make th.pn implicitly.
        var init_new = false;
        var tgt_th = this.threads[name];
        var date = [th.time_bumped, th.time_created, th.nof_posts, th.nof_files, th.time_posted];
        if (tgt_th && tgt_th[16].missing_info && (tgt_th[16].missing_info>((th.missing_info|0) - (site2[th.domain].revise_thread? 1:0)))) { // revise thread for BBC
          if (!th.page && tgt_th[7]) th.page = tgt_th[7].page;
          this.remove_thread(name, true); // , false, true);
          tgt_th = undefined;
          var flag_overwrite = true;
        }
        var lth = th.lth;
        if (tgt_th==undefined) {
if (pref.test_mode['19']) { // stability test.
          if (th.parse_funcs_html.th_init) th.parse_funcs_html.th_init(th); // BUG, should be moved into show_catalog() because threads aren't shown all the time and cause memory leak.
}
          init_new = true;
          tgt_th = [(insert_thread_from_native)? th.pn : false, // lazy generation, null may be returned by DOM, so 'false' is used here.
                           insert_thread_from_native && site.whereami!=='archive',
                           null, // not used // (!pref.test_mode['127'])? null : (embed_mode==='page' || !pref.test_mode['110'])? [triage.thread_in, null] : [func_in, func_pop_up]
                           null, // 3: merge info at view==='catalog'
//                           [html_org || th.pn.innerHTML, nickname], // PATCHED. THIS WILL BE REMOVED.
                           null, // not used // th.posts && th.posts.slice(0,1), // 4 // {sub:th.sub, com:th.com, name:th.name, trip:th.trip, filename:th.filename}
//                           click_thread, // 5, click function.
                           [], // 5, click source
                           null,
                           null, // (!pref.test_mode['49'])? th : undefined, // 7, th, // test
                           date,
                           [null,0,null,0], // [true,0,true,0], // filter result
//                           (from_native)? click_thread_native : click_thread, null, url, date, true,
                           null, // timestamp for posts search
                           null, null, date_load, th.page, 0,
//                           (from_native && brwsr.ff)? th.init_func : null, // 16
//                           null, // 16, NOT USED
                           { dbt: comf.name2domainboardthread(name,true),
//                             th_destroy: th.parse_funcs_html.th_destroy,
                             parse_funcs: th.parse_funcs,
                             systemSticky: th.sticky,
                             type_html: th.type_html, // for insert_thread_from_native
                             domain_html: th.domain_html,
                             parse_funcs_html: th.parse_funcs_html,
                             icon_sticky: null,
                             icon_showAlways: null,
                             showAlways: null,
//                             op_img_src_url: null,
                             th: th,
//                             popups: null, posts:null,
                             lth: lth, // th.lth,
                             op: th.posts && th.posts[0], // required in recent_posts to produce search source if no posts are stored in catalog mode. 'th.posts &&' must be redundant, but I haven't checked consistency yet.
                             threads:this.threads,
                             clg:this,
                             __proto__: tgt_th16_proto
                           }, // 16, others
//                           ch[brwsr.innerText].match(tags_scan_regex), // 17, tag
                           null, // 17, tag // NOT USED
                           th.type_html, // 18, type of html
                           null, // 19, tracking info
//                           [0,0,0,0,null,false,-2, 0, true, [], 0], // debug
                             // 19, time_of_checked, num_of_unread_replies_TO_ME, num_of_unread_replies,
                             //     time_of_checked_time_internal, args_for_desktop_notification, init,
                             //     time_of_checked_old, num_of_unread_old, inital_loop // for faster execution.
                             //     tag_temp, num_of_checked_posts_so_far
                           th.sticky, // 20 sticky.
//                           (nickname==='8chan' && from_native && pref.catalog.order.find_sage_in_8chan), // 21, watch.
                           false, // 21, watch.
                           null, // 22, attr info for rollback. // not used 
                           null, // 23, for missing info
                           null, // 24, footer.
//                           null, // 25, text_mode [0]: footer, [1]: html_backup
//                           th.op_img_url, // 26 op_iamge_url
                           ];
          if (th.missing_info) tgt_th[16].missing_info = th.missing_info;
          if (flag_overwrite) { // patch for merge, keep threads[name]. see site2['DEFAULT'].update_posts_merge_bases.base_factory
            for (var i=0;i<tgt_th.length;i++) this.threads[name][i] = tgt_th[i];
            tgt_th = this.threads[name];
          } else this.threads[name] = tgt_th;
//          if (name in threads_last_deleted) tgt_th[8][4] = threads_last_deleted[name].last_post_time;
//          tgt_th[17] = liveTag.prep_tags(th);
          tgt_th[19] = lth.wt; // liveTag.mems[th.domain][th.board][th.no].wt;
          if (!(embed_mode==='catalog' && th.domain===site.nickname) &&
              ((th.domain==='KC' && th.type_source==='catalog') // doesn't have time info
              || th.posts[0].editing // th.domain==='meguca' // doesn't have op. FIXED in v3.
              )) {
            tgt_th[16].missing_info = 10000;
            if (th.type_source==='catalog') scan.list_nup.add(th.key);
          } else if (th.type_source==='archive') { // for 4chan archive
            tgt_th[16].missing_info = 10000;
            scan.list_nup.add_scan(th.key);
          }
          if (th.archiveFile) tgt_th[16].archiveFile = th.archiveFile;
////          if (th.parse_funcs.missing_info) { // working code.
////            tgt_th[23] = comf.shallow_copy_1(th.parse_funcs.missing_info);
////            if (pref.network.fetch_actively) th.parse_funcs.missing_info_fetch(th);
////          }

////          if ((embed_mode==='page' || embed_mode==='thread') && th.nof_posts>1 && th.posts.length===1) {
//////            cataLog.scan_init('refresh_inserted', [th.key], {refresh:true}); // DOESN'T WORK BECAUSE OF SO MANY REQUEST STOP PREVIOUS REQUESTS.
////            catalog_liveTag_scan_ui('scan_ui', {tgts:[th.key], options:{refresh:true}});
////          }
//          if (!(tgt_th[19][0]&0x000c0000) && (tgt_th[19][0]&0x00010000) && tgt_th[19][2]===0) {
//            var time_watch;
//            if (date[4]||date[0]) time_watch = get_watch_time_of_a_thread(name,date[1],date[4]||date[0], true); // THIS MAY BE REDUNDANT, threads pass 'prep_tags' always.
//            if (time_watch===0 && embed_mode==='thread' && pref.auto_watch.watch) time_watch = (th.posts[th.posts.length-1].time || th.time);
//            if (time_watch!==0) site2[th.domain].check_reply.set_watch_time(tgt_th[19], time_watch);
//          }
////          if (site2[site.nickname].historyAPI) { // for reentry
////            tgt_th[16].tn_w = th.tn_w;
////            tgt_th[16].tn_h = th.tn_h;
////            tgt_th[16].op_img_url = th.op_img_url;
////          }
          if (this.mode==='thread') tgt_th[16].t2h_sel = (sb_merge)? pref[embed_mode].t2h_sel : 'all'; // intentional lower leters, work as 'ALL' but distinguishable.
//          if (this.mode==='thread' && merge) {
//            tgt_th[16].t2h_page = tgt_th[16].get_t2h_num_of_posts();
//            tgt_th[16].t2h_sel = 'page';
//          }
        }
//        if (!pref.test_mode['170']) if (th.parse_funcs.posts_full) th.parse_funcs.posts_full(th);
        lth.th = th;
        if (!date[0]) date[0] = tgt_th[8][0];
        if (!date[4]) date[4] = (tgt_th[8][4]<date[0])? date[0] : (tgt_th[8][4])? tgt_th[8][4] : site2[th.domain].check_reply.get_checked_time(tgt_th[19]);
        tgt_th[8] = date;
//        if ((tgt_th[19][0]&0x00840000)===0x00800000 && (date[1]>0||date[4]>0)) { // moved to liveTag.prep_tags
//          set_watch_time_thread(name, embed_mode, date[1],date[4], th, tgt_th[19]); // for meguca or those don't have time at first.
////          if ((tgt_th[19][0]&0x00840000)!==0x00800000) scan.list_nup.add_scan(th.key); // patched by trim_list:'force_init'
////            if (th.type_source==='thread') scan.list_nup.add_scan(th.key);
////            else scan.list_nup.add_board(th.domain+th.board);
//        }

////        if (tgt_th[16].posts && tgt_th[16].posts.length===1 && (tgt_th[16].parse_funcs.missing_info > (th.parse_funcs.missing_info || -1))) { // PATCH
////          th.posts[0].pn = tgt_th[16].posts[0].pn;
////          tgt_th[16].posts = th.posts; // this cause inconsistency between posts shown and posts stored.
////          tgt_th[16].parse_funcs = th.parse_funcs;
////          tgt_th[16].parse_funcs_html = th.parse_funcs_html;
////          format_html.prepare_html_prep_posts(th);
////        }
        
//        var flag_posts_stored = embed_mode==='catalog' && pref[embed_mode].t2h_sel==='no' && th.parse_funcs.has_posts && pref.filter.kwd.posts_active && 
//          th.posts && // patch for 4chan catalog.
//          (pref[embed_mode].popup2==='sr' || (pref[embed_mode].popup2==='srpv' && catalog_filter_query_keyword.kwd(pref.filter.kwd, th.posts, th.domain, th))); // for simple search with scan.
//        if (flag_posts_stored) {
//          th.posts = catalog_filter_query_keyword.kwd_make_result(th.posts, th.domain, th);
//          if (th.posts) {
//            th.posts[0].__proto__.search_result = th.posts[0].search_result; // patch. see 'catalog_filter_query_keyword.kwd_make_result'
//            th.posts[0] = th.posts[0].__proto__;
//          }
//        }

//        tgt_th[9] = (picked_up_by_kwd_filter)? [true] : catalog_filter_query(name); // moved to later to apply t2h_num_of_posts
//        if (insert_thread_from_native) threads[name][16].posts = th.posts; // for reentry // BUG, html update exist in embed_thread.
        if (insert_thread_from_native) tgt_th[16].posts = insert_thread_from_native.posts_shown;
//        if (init_new && tgt_th[0]) threads[name][16].posts = th.posts;
        if (th.type_source==='page' || th.domain==='4chan' && th.type_parse==='catalog_json') tgt_th[16].t2h_page = th.posts.length-1;

        if (tgt_th[16].expand_posts && (tgt_th[16].expand_num<=th.nof_posts || th.type_source==='thread')) tgt_th[16].expand_posts = false; // prevent infinite loop caused by !tgt_th[1]
//if (pref.test_mode['135']) {
//        var t2h_num_of_posts = ((this.view!=='page' && this.view!=='thread') || (tgt_th[16].t2h_sel || pref[embed_mode].t2h_sel)!=='page')? tgt_th[16].get_t2h_num_of_posts() : false;
//        if (t2h_num_of_posts!==false) {
//          if (init_new && (t2h_num_of_posts<0 || t2h_num_of_posts+1>th.posts.length) && th.posts.length<th.nof_posts && (this.view==='page' || this.view==='thread')) { // load to add
////          if (init_new && (t2h_num_of_posts<0 || t2h_num_of_posts+1>th.posts.length) && th.posts.length<th.nof_posts) { // doesn't work for short links(last 50 posts)
//            tgt_th[16].expand_posts = true;
//            scan.list_nup.add_scan(th.key);
////            catalog_liveTag_scan_ui('scan_ui', {tgts:[name], options:{refresh:true}});
//          }
//        }
////        var lth = th.lth;
//        if (th.parse_funcs.has_posts) th.posts = tgt_th[16].recent_posts(t2h_num_of_posts, th.posts); // moved to 'update_posts_in_page'
//} else {
        if ((this.view==='page' || this.view==='thread') && init_new) tgt_th[16].check_and_expand(th.posts);
//}
//        if (th.parse_funcs.has_posts && (pref.test_mode['49'] || !flag_posts_stored)) th.posts = tgt_th[16].recent_posts(t2h_num_of_posts, th.posts);
////        if (th.parse_funcs.has_posts && (pref.test_mode['49'] || !flag_posts_stored)) { // working code
////          if (t2h_num_of_posts!==false) {
////            if (tgt_th[16].th) th.posts = site2[th.domain].update_posts_replace_prep(th.posts, tgt_th[16].th.posts, t2h_num_of_posts); // merge
////            if (tgt_th[16].posts) th.posts = site2[th.domain].update_posts_replace_prep(th.posts, tgt_th[16].posts, t2h_num_of_posts); // merge // MUST CHANGE ADDRESS OF th.posts, 'th.posts===th_old.posts' is used 'insert_thread_format_html'
//////            if (embed_mode==='catalog' && th.posts.length<((t2h_num_of_posts>0)? t2h_num_of_posts : th.nof_posts) && lth.ta && lth.ta.posts.length>th.posts.length) th.posts = site2[th.domain].update_posts_replace_prep(th.posts, lth.ta.posts, t2h_num_of_posts); // merge, ONLY FOR FLAGS, CAN BE REMOVED AFTER CHANGING TO INCREMENTAL FLAGS.
////            // CAUSE BUG??? may cayse mixing posts which should use different prototype, posts_html and posts_json.
////          }
////          if (!pref.test_mode['64'] && pref[embed_mode].deleted_posts.merge && lth.pd) th.posts = merge_deleted_posts(lth, th.posts, t2h_num_of_posts, pref[embed_mode].t2h_sel);
//////            th.posts = site2[th.domain].update_posts_merge_prep(th.posts, th.lth.pd, 
//////              (t2h_num_of_posts===false)? th.posts.length-1 : t2h_num_of_posts + ((t2h_num_of_posts==='unread' || t2h_num_of_posts==='N_unread')? th.lth.pd.length : 0), true);
////        }
        if (!th.page && tgt_th[16].th) th.page = tgt_th[16].th.page;
        if (!init_new && th.parse_funcs.has_posts && (this.view==='page' || this.view==='thread')) tgt_th[16].th = th;
        if ((!pref.test_mode['49'] && pref[embed_mode].t2h_sel!=='no' || site.hasViewSel || site2[site.nickname].historyAPI) && (!tgt_th[7] || th.parse_funcs.has_posts)) tgt_th[7] = th; // patch for reentry
////        if (!pref.test_mode['49'] && pref[embed_mode].t2h_sel!=='no') tgt_th[7] = th;
//        if (pref.test_mode['135']) if (pref[embed_mode].t2h_sel==='no') th.posts = th.posts.slice(0,1); // don't change th.posts itself, some other catalog may grep that. // moved to 'update_posts_in_page'
////        if (!pref.test_mode['49'] && th.parse_funcs.has_posts && pref.filter.kwd.posts_active && !flag_posts_stored) tgt_th[7] = th; // BUG at catalog.
        this.filter_dirtify(tgt_th,true);
//        tgt_th[9][0] = null;
//        tgt_th[9][2] = null;
////        tgt_th[9] = (pref.filter.disLWKA && picked_up_by_kwd_filter)? [true] : catalog_filter_query(name);
//        tgt_th[9] = catalog_filter_query(name, true); // slightly redundant.

//        catalog_attr_set(th.key,tgt_th[0]); // for cmd '!show'.
        var pff = this.pref.filter;
        if (insert_thread_from_native || (tgt_th[16].expand_posts && tgt_th[1] && !pff.kwd.posts_active) || pref.test_mode['36']) this.insert_thread_prepare_html_lazy(tgt_th, init_new, insert_thread_from_native, null, th);
////        else {
////          var th_old = tgt_th[16].th;
////          if (th_old.posts.length<=1 || th.posts.length>1 && th.posts[th.posts.length-1].no > th_old.posts[th_old.posts.length-1].no) tgt_th[16].th = th;
////          else if (th.page) th_old.page = th.page;
////          catalog_attr_set(th.key,tgt_th[0]); // for 'show'.
////        }
        if (init_new) {
          if (pff.auto_list.use) { // must be after making html, because this may inboke show_catalog
            if (!pff.list_obj2[th.key] && !pff.watch_list_obj2[th.key] && !pff.attr_list_obj2[th.key]) { // CAUTION, obj2 are accessed directly.
              var auto_obj = pref_func.merge_obj5a(th,pff.auto_list.obj7,null);
              if (auto_obj) this.apply_auto_list_filter(th, auto_obj);
            }
          }
//          if (pref.test_mode['177']) pref3.proto.merge_list_obj2c_update(th.key); // DOESN'T WORK, tgts may NOT be registered yet.
          var pfm = this.pref[this.mode];
          var pf_op = pfm.merge_op;
          if (pfm.merge_list && (pf_op.auto || pfm.merge_auto) && (!pref.test_mode['211'] || this.view==='catalog' || this.view==='headline')) {
//          if ((pfm.merge_list /*|| pfm.merge_list_delayed*/) && pfm.merge_op_auto && (this.view==='catalog' || this.view==='headline')) { // merge_list_delayed is depatched, see delayed_merge
            var hop = this.merge_op_hops[name];// lth.mh || 0;
            var done = !pref.test_mode['215'] && !sb_merge_op_not_done[name] && ((name in pref3.proto.merge_list_obj2) || (name in pref3.proto.merge_lv_obj2));
            if (pf_op.auto && (hop || !done)) { // merge_lv is used as an end flag // CAUTION, obj2 are accessed directly.
//            if (!pref3.proto.merge_list_obj2[name] && !pref3.proto.merge_lv_obj2[name]) { // merge_lv is used as an end flag // CAUTION, obj2 are accessed directly.
              var done_tgts = {};
              if (th._links) for (var i=th._links.length-1;i>=0;i--) {
                var link = th._links[i];
                var tgt = link.slice(0,3).join('');
                if (tgt in done_tgts) continue;
                if (!this.threads[tgt] && (link[0]!==th.domain || link[1]!==th.board || !(link[2] in nos))) {
//                if (nos && link[0]===th.domain && link[1]===th.board && !(link[2] in nos)) {
//                  if (!pref.test_mode['147'] || !pfm.merge_op_auto_hop) continue;
                  if (!pf_op.fetch || hop >= pf_op.hop) continue;
                  if (this.merge_op_tgts) this.merge_op_tgts[this.merge_op_tgts.length] = tgt; else this.merge_op_tgts = [tgt];
//                  scan.scan_ui('mergeAuto', {tgts:[tgt], options:{refresh:this}});
//                  scan.list_nup.add_scan(tgt); // BUG, REQUIRES REQUEST FLAG or promiscuous mode.
                  if (!this.merge_op_hops[tgt] || this.merge_op_hops[tgt] > hop +1) this.merge_op_hops[tgt] = hop +1; // for racing condition
//                  var tgt_lth = liveTag.mems.init({domain:link[0], board:link[1], no:link[2]});
//                  if (!tgt_lth.mh || tgt_lth.mh > hop +1) tgt_lth.mh = hop +1; // for racing condition
////                  liveTag.mems.init({domain:link[0], board:link[1], no:link[2]}).mh = hop +1;
                  if (!pref.test_mode['213']) if (link[0]===th.domain || link[1]===th.board) nos[link[2]] = null; // add mask because rm_items_404_check will be run right after this.
                }
                if (!(tgt in pref3.proto.merge_list_obj2) && !(tgt in pref3.proto.merge_lv_obj2)) sb_merge_op_not_done[tgt] = true; //  for racing condition between threads in the same catalog especially for initial. !this.threads[name] is NOT required, the thread may exist with missing_info.
                if (pref.debug_mode['43'] && !sb_merge_op_not_done[name] && ((name in pref3.proto.merge_list_obj2) || (name in pref3.proto.merge_lv_obj2))) console.log('merge_op: '+th.no+', '+th.sub+', '+tgt);
                this.merge_tgts(name, [tgt], false, true, pf_op);
                if (!(name in pref3.proto.merge_lv_obj2)) this.merge_bases.add_to_lv_list(name, '', this); // add an end mark
//                if (pfm.merge_op_lv_add) this.merge_bases.add_to_lv_list(th.key, pfm.merge_btn_lv, this); // levelize old one, instead of new one.
//                this.merge_bases.add_to_list_1(tgt, th.key, this, null, true); // merge new one to old one to cascade.
////              this.force_all_named = true;
//                if (pfm.merge_op_attr || pfm.merge_op_list) {
//                  var attr = (pfm.merge_op_attr)? this.view_attr_get_str(tgt) : null;
//                  var list = (pfm.merge_op_list)? pff.list_obj2[tgt] : null; // CAUTION, obj2 are accessed directly.
//                  if (attr || list) this.triage_exe_broadcast(th.key, !list? 'NONE' : list.time? 'TIME' : 'KILL', attr || '', false, list && list.time || null, false, true); // CAUTION: TRIAGE IS SLOW BECAUSE IT IS DESIGNED TO BE USED AS GUI, it redraws doms and remake objs always.
//                }
//                break;
                done_tgts[tgt] = null;
              }
              delete this.merge_op_hops[name];
            } else if (pfm.merge_auto && done && pref.test_mode['212']) {
              var tgts = this.merge_bases.auto_merge_list(name, this);
              if (tgts) for (var i=0;i<tgts.length;i++) {
                var tgt = tgts[i]
                var dbt = comf.fullname2dbt(tgt);
                if (!this.threads[tgt] && (dbt[0]!==th.domain || dbt[1]!==th.board || !(dbt[2] in nos))) {
                  if (this.merge_op_tgts) this.merge_op_tgts[this.merge_op_tgts.length] = tgt; else this.merge_op_tgts = [tgt];
                  if (!pref.test_mode['213']) if (dbt[0]===th.domain || dbt[1]===th.board) nos[dbt[2]] = null; // add mask because rm_items_404_check will be run right after this.
                }
              }
            }
          }
          this.view_attr_set(th.key, null, true); // after making html to remove redundant check of !footer in footer.draw. // for cmd '!show' and sticky:false
        }

        if (th.domain==='meguca') {
//        if (th.domain==='meguca' && tgt_th[16].needs_update!==true) {
          if (lth.ed_t) {
            lth.ed_u = th.replyTime; // meguca don't update JSON while editing.
//          var ed_p = lth.ed_p || lth.ed_t; // share lth.ed_t when pref.liveTag.from==='post'
//          if (ed_p) {
//            var i=0;
//            while (i<ed_p.length && typeof(ed_p[i])==='string') i++;
//            lth.ed_u = (!th.posts[1] || th.posts[1].no<=ed_p[i])? -1 : th.replyTime; // -1 for force update when editing posts are in scope. // 0 can't be used, because lth.ed_u becomes falsy.
          } else if (lth.ed_u) lth.ed_u = undefined; // null can't be used because (null<123) is true.
//          if (ed_p || tgt_th[16].needs_update<th.replyTime || tgt_th[16].needs_update===null)
//            tgt_th[16].needs_update = (ed_p && !(th.posts[1] && th.posts[1].no<=ed_p[ed_p.length-1]))? null : th.replyTime; // null means 'in scope and force update'.
//        if (th.domain==='meguca' && tgt_th[16].needs_update!==true) tgt_th[16].needs_update = th.lastUpdated || ((th.lth.ed_p)? null : undefined); // lastUpdated may be undefined.
//        if (th.parse_funcs.has_editing && tgt_th[16].needs_update!==true) tgt_th[16].needs_update = (th.lth.ed_p)? 1 : null;
        }
//        if (!pref.test_mode['114'] && Clg.prototype.Clgs.length>=2 && pref.test_mode['133']) for (var i=1;i<Clg.prototype.Clgs.length;i++) clone_thread(Clg.prototype.Clgs[i],name);
        return tgt_th;
      };
      Clg.prototype.apply_auto_list_filter = function(th, obj){
        for (var i=0;i<obj.length;i++) {
          if (obj[i].use!==false && catalog_filter_query_keyword.kwd({use:true, __proto__:obj[i]}, th.posts, th.domain)) {
            var cmds = obj[i].cmds.split(',');
            for (var j=0;j<cmds.length;j++) {
              var idx = cmds[j].indexOf(':');
              this.triage_exe_broadcast(th.key, idx===-1? cmds[j] : cmds[j].slice(0,idx), idx===-1? '' : cmds[j].slice(idx+1), false, null, false, true); // CAUTION: TRIAGE IS SLOW BECAUSE IT IS DESIGNED TO BE USED AS GUI, it redraws doms and remake objs always.
            }
          }
        }
      };
      Clg.prototype.set_watch_time_thread = function(name, time_created, time_posted, watch, rewind, nof_posts){
        var time_watch = this.get_watch_time_of_a_thread(name,time_created,time_posted);
        if (!rewind && time_watch===0 && this.mode==='thread' && pref.auto_watch.watch && name===site.key) time_watch = site2[site.nickname].passiveWatch? nof_posts : time_posted || time_created; // name===site.key for archive, which has a mode of store_watched
        if (time_watch>0) site2['DEFAULT'].check_reply.set_watch_time(watch, time_watch, name);
//        else if (time_watch===0 && embed_mode==='thread' && pref.auto_watch.watch && name===site.key) site2['DEFAULT'].check_reply.set_watched_to_last(watch, time_posted || time_created, name); // name===site.key for archive, which has a mode of store_watched
        else site2['DEFAULT'].check_reply.set_unwatch(watch);
        // dirtify_ur() is not required here because this function is called only in initialization.
      };
//      function set_watch_time_thread(name, embed_mode, time_created, time_posted, th, tgt_th19){
//        var time_watch = get_watch_time_of_a_thread(name,time_created,time_posted, true);
//        if (time_watch===0 && embed_mode==='thread' && pref.auto_watch.watch) time_watch = (th.posts[th.posts.length-1].time || th.time);
//        site2[th.domain].check_reply.set_watch_time(tgt_th19, time_watch);
//      }

      Clg.prototype.insert_thread_prepare_html_lazy = function(tgt_th, init_new, insert_thread_from_native, from_lazy, th){
//        var embed_mode_old = embed_mode;
//        var threads_old = threads;
//        threads = this.threads;
        var th = tgt_th[16].th || th;
        if (!from_lazy || !pref.filter.kwd.posts_active) if (this.view==='page' || this.view==='thread' && (th.lth.pd || (tgt_th[16].t2h_sel!=='ALL' && tgt_th[16].t2h_sel!=='all') || th.type_source!=='thread')) th.posts = tgt_th[16].recent_posts(undefined, th.posts); // merge deleted posts here // should be consolidated with 'format_html.replace_posts_by_search'
//        if (!from_lazy || !pref.filter.kwd.posts_active) if (this.view==='page' || this.view==='thread' && (th.lth.pd || tgt_th[16].t2h_sel==='page')) th.posts = tgt_th[16].recent_posts(this.view==='thread' && tgt_th[16].t2h_sel!=='page'? -1 : undefined); // merge deleted posts here // should be consolidated with 'format_html.replace_posts_by_search'
//        embed_mode = this.mode; // wrap to change the world // after recent_posts, because it uses embed_mode.
        format_html.prepare_html_prep_posts(th); // need extraction for posts search // wrap always for popup2, including "embed_mode==='catalog'".
//        site2[th.domain].wrap_to_parse.posts(th); // don't need extraction, or KC needs it??? // WILL BE this after on-demand extraction is implemented
        if (init_new || (embed_mode==='page' || embed_mode==='thread')) this.prepare_html(tgt_th, th, init_new);

        if (init_new) this.insert_thread_format_th_pn(tgt_th, th);
//          convert_html(name,th);
//          threads[name][25] = [threads[name][24][0], threads[name][0].innerHTML, threads[name][18]];
////////          insert_footer3(name,th.flags,th.page,threads[name][17]);
        if (tgt_th[0]) this.insert_thread_format_html(th,th.key,init_new,insert_thread_from_native);
//if (pref.test_mode['124']) { // must be redundant, but not debugged, so this is left as test code for a while.
//        if (embed_mode==='catalog' && th.parse_funcs.has_posts) {
//          var lth = th.lth;
//          if (lth.ta && lth.ta.posts.length<th.posts) tgt_th[16].posts = th.posts; // patch for 4chan.
//          else if (tgt_th[16].posts) tgt_th[16].posts = null;
//        }
//}
        tgt_th[16].th = null;
//        threads = threads_old;
//        embed_mode = embed_mode_old;
      };
      Clg.prototype.insert_thread_format_th_pn = function(tgt_th, th, popup){
//        if (init_new) {
//          tgt_th[16].op_img_src_url = (th.parse_funcs.get_op_src)? th.parse_funcs.get_op_src(th) : th.op_img_url;
          var ch = tgt_th[0];
//          tgt_th[3] = [ch.innerHTML, th.domain]; // PATCHED. THIS WILL BE REMOVED.
//          ch.name = th.key; // ch.setAttribute('name',name); // for native html // obsolete
          gGEH.pns_all_keys.set(ch,th.key);
//          if (this.mode==='float' && this.view==='catalog' && this.pref.float.format.fc.resize) { //  || (th.domain!==site.nickname && !pref.catalog.mimic_base_site)) {
//            ch.style.width  = this.pref.float.format.fc.width  + 'px';
//            ch.style.height = this.pref.float.format.fc.height + 'px';
//            ch.style.height = this.pref.float.format.fc.height + 'px';
////            ch.style.width  = ((pref.catalog.text_mode.mode==='text')? pref.catalog_size_text_width  : pref.catalog_size_width ) + 'px';
////            ch.style.height = ((pref.catalog.text_mode.mode==='text')? pref.catalog_size_text_height : pref.catalog_size_height) + 'px';
//            ch.style.float = 'left';
//            ch.style.overflow = 'hidden';
////            ch.style.background = '#e5ecf9';
//          }
//////          if (!ch.style) ch['style'] = {};

//          if (pref.test_mode['127']) tgt_th[0].addEventListener('mouseover', tgt_th[2][0], false);

//if (pref.test_mode['104']) { // working code
//          comf.dom_addEventListener(tgt_th[5], th.pn, 'click', click_thread_whole);
//          comf.dom_addAttribute(th.pn, 'class', pref.cpfx+'thread');
//          var tn_as = th.parse_funcs_html.tn_as(th);
//          if (this.mode!=='page' && this.mode!=='thread') for (var i=0;i<tn_as.length;i++) {
//            tn_as[i].onclick = click_thread_tn; // preventDefault is in this event handler.
//            comf.dom_addAttribute(tn_as[i], 'class', pref.cpfx+'thumbnail');
//          }
//}
          if (this.pref[this.view].format.thumb.resize || th.domain!==site.nickname && th.size_from_img) this.image_resize_onload_add(th.parse_funcs_html.tn_imgs(th), th);
//          if (pref[this.mode].format.thumb.resize) { // working code
//            var tn_imgs = th.parse_funcs_html.tn_imgs(th);
//            if (!this.image_resize1_onload_bound) this.image_resize1_onload_bound = image_resize1_onload.bind(this);
//            if (!this.image_resize2_onload_bound) this.image_resize2_onload_bound = image_resize2_onload.bind(this);
//            for (var i=0;i<tn_imgs.length;i++) tn_imgs[i].addEventListener('load',(i==0)?this.image_resize1_onload_bound : this.image_resize2_onload_bound, false);
//          }

//          if (tgt_th[20]===true) tgt_th[16].icon_sticky = (insert_thread_from_native)? site2[th.domain_html].get_icon(tgt_th[0],th.type_html, 'sticky', tgt_th[16]) :
//                                                                                       site2[th.domain_html].add_icon(tgt_th[0],th.type_html, 'sticky', tgt_th[16]); // first time only.
  
////          if (embed_mode==='float' && pref.catalog.text_mode.mode==='text') { // place after th.op_img_url. // NEED TO MODIFY REMOVE_THREAD TO PREVENT MEMORY LEAK.
////            var text = document.createElement('span');
////            text.innerHTML = ((pref.catalog.text_mode.sub && th.sub)? '&emsp;' + th.sub : '') +
////                             ((pref.catalog.text_mode.name && th.name)? '&emsp;' + th.name : '') +
////                             ((pref.catalog.text_mode.com && th.com)? '&emsp;' + th.com : '');
////            var footer = th.footer; // make footer on demand.
////            var footer_new = document.createElement('span');
////            footer_new.innerHTML = footer.innerHTML;
////            th.footer = footer_new;
////            th.pn.innerHTML = '';
////            th.pn.appendChild(th.footer);
////            th.pn.appendChild(text);
////            tgt_th[18] = 'text';
////          }
        if (!popup) tgt_th[24] = this.footer.prep_footer3(th, tgt_th[24]); // for remake_html // for reentry in meguca
//          if (from_lazy) insert_footer3(th.key, null, th.page, th.tags, th); // consolidated into show_catalog
          this.view_attr_set(th.key, popup, true); // required to be here to add style to html, catalog_attr_set in insert_thread might not add because of NO html.
//        }
      }

      Clg.prototype.insert_thread_format_html = function(th,name,init_new,insert_thread_from_native){
//        if (force_prep_html) format_html.prepare_html(th); // for json
        site2['DEFAULT'].check_reply.set_own_posts(th);
//        if (th.type_source==='page') threads[name][16].t2h_page = th.posts.length-1;
        var init_or_refresh = init_new || insert_thread_from_native && site.nickname==='meguca'; // for reentry // TEMPORAL PATCH!!!
        if (init_or_refresh) {
//        if (init_new || insert_thread_from_native && site.nickname==='meguca') { // for reentry // TEMPORAL PATCH!!!
//        if (init_new || insert_thread_from_native) { // for reentry // BUG, html update mode exist in embed_thread.
//        if (init_new) {
          if (th.type_html==='page' || th.type_html==='thread') {
            var links = th.parse_funcs_html.get_thread_links(th.pn? th : {pn:this.threads[name][0], __proto__:th}); // for reentry. // THIS MUST BE HERE BEFORE update_posts_in_page, OP is also removed from th.pn at merge mode.
//            var links = th.parse_funcs_html.get_thread_links(th); // THIS MUST BE HERE BEFORE update_posts_in_page, OP is also removed from th.pn at merge mode.
            if (links) for (var i=0;i<links.length;i++) links[i].onclick = click_link;
          }
        }

        var tgt_th = this.threads[name];
        var mb = this.merge_bases;
        var merge_base = mb.bases[th.key] || (init_new && (tgt_th[1] || !mb.initialized)) && mb.query(th.key, tgt_th, th, true, this); // !mb.initialized for initial kick
//        var merge_base_tgts = (init_new && (tgt_th[1] || !mb.initialized))? mb.base_tgts(th.key) : null; // !mb.initialized for initial kick // working code
//        var merge_base = (tgt_th[1] && merge_base_tgts)? mb.query(th.key, tgt_th, th, merge_base_tgts, true, this) : mb.bases[th.key];
//        if (merge_base_tgts && !tgt_th[1] && !mb.initialized) mb.setup_hook();

// working code, but BUG. this makes 'threads[name][16].posts === th.posts' BEFORE merging th.posts which contains deleted posts in 'embed_mode==='thread', so no merge will occur.
// But I remember I had troubled here..., safe1309-1308.
        if (this.view==='page' || this.view=='thread') {
          var posts_used = this.update_posts_in_page(th,name, init_or_refresh, insert_thread_from_native, tgt_th, merge_base);
          if (pref[embed_mode].popup) {
            if ((!this.pref.filter.kwd.posts_active || pref.test_mode['125']) && !pref[this.mode].backlink_all) site2[th.domain].popups_release_all_not_exist(th.lth, tgt_th[16].posts);
//            site2[th.domain].popups_add(tgt_th[16].posts, th, !this.pref.filter.kwd.posts_active);
            if (pref.test_mode['211'] && pref[this.mode].merge_op.auto && posts_used[0]===tgt_th[16].posts[0]) this.merge_cross_links_in_op(th.lth, tgt_th[16].posts[0]);
          }
        }
//        } else {
//          if (pref.test_mode['147'] && init_new && this.pref[this.mode].merge_list && pref[this.mode].merge_op_auto) {
////            site2[th.domain].popups_add_1(th, th, false, th.lth.q, false, insert_thread_from_native, true); // doesn't work
//////            site2[th.domain].popups_add(tgt_th[16].posts, th, !this.pref.filter.kwd.posts_active); // doesn't work
//            var btns = th.pn.getElementsByClassName(pref.cpfx+'button');
//            for (var i=0;i<btns.length;i++) if (btns[i].dataset.type==='triage' && btns[i].dataset.cmd==='MERGEE') { // ATTENTION!!! behavior differs from clicking
//              var tgt = Triage.prototype.MERGEE_get_key(btns[i]);
//              if (pref.proto.merge_btn_lv_add) this.merge_bases.add_to_lv_list(tgt, pref[this.mode].merge_btn_lv, this); // levelize old one, instead of new one.
//              this.merge_bases.add_to_list_1(tgt, th.key, this, true); // merge new one to old one to cascade.
////                this.merge_bases.add_to_list_fromUI(Triage.prototype.MERGEE_get_key(btns[i]), th.key, this, true);
////                  setTimeout(this.triage_event.bind(this, th.key, btns[i].dataset.cmd, btns[i].dataset.attr, 0, btns[i]),0);
////                  this.triage_event(th.key, btns[i].dataset.cmd, btns[i].dataset.attr, 0, btns[i]);
//            }
//          }
//        }
//        } else if (pref.test_mode['124']) if (init_or_refresh) {
//          this.threads[name][16].posts = th.posts;
//          posts_used = th.posts;
//        }
//        if (!init_or_refresh) if (th.type_source!=='catalog' && embed_mode==='float') threads[name][3][0] = (th.pn)? th.pn.innerHTML : null; // PATCHED TEMPORARILY, this will be deleted.
//        if (init_or_refresh) {
//          if (embed_mode==='page' || embed_mode=='thread') posts_used = format_html.update_posts_in_page(th,name, true, insert_thread_from_native); // threads[name][16].posts!==th.posts is moved into 'update_posts_in_page'
//          else {
//            threads[name][16].posts = th.posts;
//            posts_used = th.posts;
//          }
//////          if (embed_mode==='page' || embed_mode==='thread') {
////          if (embed_mode==='page' && threads[name][16].posts && threads[name][16].posts!==th.posts) format_html.update_posts_in_page(th,name,null, true);
////          else threads[name][16].posts = th.posts;
////          if ((embed_mode==='page' || embed_mode==='thread') && !pref.test_mode['64'] && pref[embed_mode].deleted_posts.merge && th.lth.pd) format_html.update_posts_in_page(th,name,null, true);

//          if (insert_thread_from_native && embed_mode==='page' && pref[embed_mode].use_expander_always) site2[th.domain_html].page_json2html3_replace_expander(th.posts, format_html.get_t2h_index(threads[name]), name);
//          if (th.type_html==='page' || th.type_html==='thread') {
////            var links = th.parse_funcs_html.get_thread_links(th);
////            if (links) for (var i=0;i<links.length;i++) links[i].onclick = click_link;
////            if (th.parse_funcs.time_unit!==1) { // BUG!!! REFER 'update_posts_1'
////              var tu = th.parse_funcs.time_unit;
////              for (var i=0;i<th.posts.length;i++) th.posts[i].time *= th.parse_funcs.time_unit;
////            }
//            var pref_env = (!insert_thread_from_native)? pref[embed_mode] :
//                                                         {
//                                                           colorID: pref[embed_mode].colorID && !pref[embed_mode].env.colorID_native,
//                                                           backlink: pref[embed_mode].backlink && !pref[embed_mode].env.backlink_native, // do nothing because all posts don't have post.backlinks because this is before calling 'popups_add'.
//                                                           localtime: pref[embed_mode].localtime && !pref[embed_mode].env.localtime_native,
//                                                         };
//            for (var i=0;i<th.posts.length;i++) format_html.prepare_html_post(th, th.posts[i], th.tags.q, pref_env);
////            for (var i=0;i<th.posts.length;i++) site2[th.domain_html].format_pn(th.posts[i].pn, th.tags.q && th.tags.q[th.posts[i].no], pref_env, th.posts[i]);
//          }
//        } else {
//          if (th.domain==='KC' && embed_mode==='page' && pref[embed_mode].use_expander_always) site2[th.domain_html].page_json2html3_replace_expander(th.posts, format_html.get_t2h_index(threads[name]), name); // test patch for KC, I don't know why but other than KC generates ERROR.
//          if (embed_mode==='page' || embed_mode==='thread') if (th.parse_funcs.has_posts) posts_used = format_html.update_posts_in_page(th,name, null, insert_thread_from_native);
//          if (th.type_source!=='catalog' && embed_mode==='float') threads[name][3][0] = (th.pn)? th.pn.innerHTML : null; // PATCHED TEMPORARILY, this will be deleted.
//        }
////        if (embed_mode==='page' && th.posts && th.posts[0].search_result!==undefined && threads[name][16].posts) site2[th.domain_html].update_posts0_class(threads[name][16].posts[0].pn, th.posts[0].search_result); // place here, because sometimes filter is given at initial, and that is 'init_new===true' // patch for 4chan catalog. moved to 'format_pn'
////        if (th.posts && th.posts[0].search_result!==undefined && threads[name][16].posts) site2[th.domain_html].update_posts0_class(threads[name][16].posts[0].pn, th.posts[0].search_result); // place here, because sometimes filter is given at initial, and that is 'init_new===true'

//if (pref.test_mode['124']) { // must be redundant, but not debugged, so this is left as test code for a while.
//        var idx_new_post = (init_new)? 0 : 1;
//        if (embed_mode=='page' && th.type_data==='json') { // TEMPORAL PATCH
//          th.domain_html = site.nickname;
//          th.parse_funcs_html = site2[th.domain_html].parse_funcs.page_html;
//          for (var i=idx_new_post;i<th.posts.length;i++) if (!th.posts[i].pn) th.posts[i].pn = site2[th.domain_html].post_json2html(th.posts[i],th.board);
//          for (var i=idx_new_post;i<th.posts.length;i++) th.posts[i].tn_imgs = th.parse_funcs_html.tn_imgs(th.posts[i]);
//          posts_used = th.posts.slice(idx_new_post);
//        }
//}
////        insert_thread_format_html_add_events(th, posts_used, insert_thread_from_native);
//        if ((embed_mode==='page' || embed_mode==='thread') && pref[embed_mode].popup && th.type_html!=='catalog') site2[th.domain].popups_add(threads[name][16], th, insert_thread_from_native && pref[embed_mode].env.popup_native && !pref[embed_mode].env.event_dynamic, insert_thread_from_native);
////        if (pref.filter.kwd.post && th.posts) threads[name][4] = th.posts;
////        threads[name][9] = catalog_filter_query(name);
////if (pref.test_mode['5']) {
////        if (init_new && th.type_html!='catalog' && (type==='thread_html' || type==='page_html')) { // modify HTML at end because of DYNAMIC PARSE.
//////          trim_html(threads[name][0], th.domain, pref[embed_mode].format.show, th.key);
////          if (pref[embed_mode].localtime) site2[nickname].localtime(th.pn); // PATCH
////        }
////}
        if (this.view==='page' || this.view==='thread') {
          if (pref.test_mode['158'] && (pref.postFilter.use || pref.filter.postFilter.use)) format_html.postFilter(this, th, posts_used);
          if (pref.test_mode['162']) hiddenPosts.hide_if_hit(th, posts_used);
          if (pref[this.mode].hide_posts_without_images) format_html.hide_posts_without_images(this, posts_used);
          this.view_update_draw(name, this.threads[name][16].posts);
//        if (insert_thread_from_native) this.insert_thread_format_html_add_events(th, posts_used, insert_thread_from_native);
        }
        if (pref.test_mode['155'] && th.type_data==='json') pref.test_mode['136'] = false;
      }
      Clg.prototype.merge_cross_links_in_op = function(lth, post){
        var hop = lth.mh || 0;
        if (hop >= pref[this.mode].merge_op.hop) return;
        var quotes = lth.q[post.no].quotes;
        var tgts = quotes && quotes.filter(function(v){return v[0]!==lth.q;}).map(function(v){return v[0].key;}).filter(function(v){return !this.threads[v];},this);
        if (tgts && tgts.length>0) {
          scan.scan_ui('mergeAuto', {tgts:tgts, options:{refresh:this}});
          this.merge_tgts(lth.key, tgts, false, true, this.pref[this.mode].merge_op);
          for (var i=0;i<tgts.length;i++) {
            var dbt = comf.fullname2dbt(tgts[i]);
            var lth = liveTag.mems.init({domain:dbt[0], board:dbt[1], no:dbt[2]});
            if (!lth.mh || lth.mh > hop +1) lth.mh = hop +1; // for racing condition
//            liveTag.mems.init({domain:dbt[0], board:dbt[1], no:dbt[2]}).mh = hop +1;
          }
        }
      };
      Clg.prototype.merge_cross_links_fromUI = function(dst, tgt, no_insert){
        var pfm = this.pref[this.mode];
        if (!this.threads[tgt]) scan.scan_ui('mergeUI', {tgts:[tgt], options:{refresh:this}});
        this.merge_tgts(dst, [tgt], no_insert, false, pfm.merge_btn);
        var mg_mode = pfm.merge_mode==='all'? 'merge' : 'merge_list';
        if (!pfm[mg_mode]) {
          pfm[mg_mode] = true;
          pref_func.apply_prep(this.components[mg_mode], false, true, this.mode!=='float');
        }
      };
      Clg.prototype.merge_tgts = function(dst, tgts, no_insert, prep_only, pf){
        this.merge_bases.add_to_lv_list_pf(dst, tgts, this, pf);
        for (var i=0;i<tgts.length;i++) {
          if (this.pref[this.mode].merge_mode!=='all') this.merge_bases.add_to_list_1(dst, tgts[i], this, no_insert, prep_only);
          if (pf.attr || pf.list) {
            var attr = (pf.attr)? this.view_attr_get_str(dst) : null;
            var list = (pf.list)? this.pref.filter.list_obj2[dst] : null; // CAUTION, obj2 are accessed directly.
            if (attr || list) this.triage_exe_broadcast(tgts[i], !list? 'NONE' : list.time? 'TIME' : 'KILL', attr || '', false, list && list.time || null, false, true); // CAUTION: TRIAGE IS SLOW BECAUSE IT IS DESIGNED TO BE USED AS GUI, it redraws doms and remake objs always.
          }
        }
      };
//      Clg.prototype.merge_cross_links_in_op_manually = function(name){ // working code
//        var lth = liveTag.mems.getFromName(name);
//        site2[lth.domain].popups_prep(lth);
//        format_html.prepare_html_post(lth.th, lth.th.posts[0]);
//        var quotes = lth.q[lth.th.posts[0].no].quotes;
//        var tgts = quotes && quotes.filter(function(v){return v[0]!==lth.q;}).map(function(v){return v[0].key;}).filter(function(v){return this.threads[v];},this);
//        if (tgts && tgts.length!=0) {
//          this.merge_tgts(lth.key, tgts, lth.mh || 0);
//          for (var i=0;i<tgts.length;i++) this.merge_bases.add_to_list_1(name, tgts[i], this);
//        }
//      };
      var format_html = {
        prepare_html_prep_posts: function(th, force){
          site2[th.domain].wrap_to_parse.posts(th);
//          if (th.parse_funcs.posts_full) th.parse_funcs.posts_full(th); // BUG, DOESN'T HIT FILTER BEFORE BEING SHOWN.
          if (th.type_data==='html' && (force || th.domain!==site.nickname)) this.prepare_html_extract_params(th); // extraction function assumes that it has all of properties, this must be after 'posts_full'.
        },
        prepare_html_extract_params: function(th){
          if (!th.extracted && th.pn) { // checking th.pn for changing view
            if (th.parse_funcs) {
              if (th.parse_funcs.filename) {
                for (var i=0;i<th.posts.length;i++) if (th.posts[i].pn) th.posts[i].filename = th.parse_funcs.filename(th.posts[i]); // deleted posts may be merged.
//                for (var i=0;i<th.posts.length;i++) th.posts[i].filename = th.parse_funcs.filename(th.posts[i]);
                th.filename = th.parse_funcs.filename(th);
              }
              if (th.parse_funcs.prep_mimic) th.parse_funcs.prep_mimic(th);
            }
            if (th.posts) for (var i=0;i<th.posts.length;i++) if (th.posts[i].parse_funcs.prep_mimic && th.posts[i].pn) th.posts[i].parse_funcs.prep_mimic(th.posts[i]);
            th.extracted = true;
          }
        }
      };
      Clg.prototype.prepare_html = function(tgt_th, th, init_new){
//          var tgt_th = threads[th.key];
//        if ((th.domain!==site.nickname || site.whereami+'_html'!=type) && pref.catalog.mimic_base_site && th.pn && site2[site.nickname].catalog_json2html3) { // for KC.
//        if ((th.domain!==site.nickname || site.whereami!='catalog') && pref.catalog.mimic_base_site && th.pn && site2[site.nickname].catalog_json2html3) { // cause document leak in KC
//          site2[th.domain].wrap_to_parse.posts(th);
//          if (th.parse_funcs.posts_full) th.parse_funcs.posts_full(th);
          var mimic_force = th.domain!==site.nickname && th.type_data==='html'; // && th.domain_html!==site.nickname; // must be added this for consistency. // prioritize cloning is for popups_add_1's being called from popups_fetched_1 in advance. This is observable when thread+watcher is used, backlinks will be added to lth.q.pn, this way can track single entity, but can't track multiple entities.
          var mimic_tgt = this.view==='thread'? 'page' : this.view; // (mode==='float' || pref.test_mode['144'] && mode!=='thread')? view : (mode==='page' || mode==='thread')? 'page' : 'catalog';
          var mimic = !th.pn || ((th.domain!==site.nickname || mimic_tgt!==(th.type_mimic==='thread'? 'page' : th.type_mimic)) && pref.catalog.mimic_base_site) || this.mode==='float'; // !th.pn for reentry from unmerge with catalog_html data
          if (pref.test_mode['92']) mimic_tgt = 'catalog'; // for 4chan-X catalog.
//          if (init_new || !mimic)
//            if (mimic_tgt==='page' && th.type_data==='html') this.prepare_html_extract_params(th); // for popup2, always wrapping is required.
          if (mimic) { //  || th.hasOwnProperty('pn')) { // don't call getter of pn, TEMPORAL
//            this.prepare_html_extract_paramsfunction(th); // WILL BE on-demand extraction
            if (init_new) {
              var pftn;
              var tmp_pn = (mimic_tgt==='headline')? site2[site.nickname].headline_json2html(th)
                : (pftn = this.pref.INST[mimic_tgt].thumb, pftn = pftn[pftn.size],
                  (mimic_tgt==='page')? site2[site.nickname].page_json2html3(th, !mimic_force, tgt_th[16], pref[this.view].use_expander_always || this.mode==='thread' && tgt_th[16].t2h_sel!=='all', undefined, pftn) // t2h_sel for merge
                                      : site2[site.nickname].catalog_json2html3(th,th.board,th.op_img_url, pftn));
              Object.defineProperty(th, 'pn', {value: tmp_pn, writable:true, enumerable:true, configurable:true});
            }
            th.type_html = mimic_tgt;
            th.domain_html = site.nickname;
            th.parse_funcs_html = site2[site.nickname].parse_funcs[th.type_html+'_html']; // overwrite.
          } else if (th.type_data==='html') site2.common.remove_by_tagname(th.pn,'script');
          if (init_new) {
            if (pref[this.mode].popup && mimic_tgt==='page') site2[th.domain].popups_prep(th);
            if (tgt_th[0]===false) { // patch
              tgt_th[0] = th.pn;
              tgt_th[16].type_html = th.type_html;
              tgt_th[16].domain_html = th.domain_html;
              tgt_th[16].parse_funcs_html = th.parse_funcs_html;
            }
            Object.defineProperty(th, 'footer', {value: th.parse_funcs_html.footer(th), writable:true, enumerable:true, configurable:true});
            if (th.parse_funcs_html.footer2) Object.defineProperty(th, 'footer2', {value: th.parse_funcs_html.footer2(th), writable:true, enumerable:true, configurable:true});
            format_html.prep_anchor_links(th.pn, th); // Files in op are out of posts[0].pn sometimes, so these must be called with 'th'.
//            if (th.localArchive) if (!pref.test_mode['67']) archiver.url2file(th.localArchive, th); // merged to root html generator to prevent needless prefetch.
//            if (th.parse_funcs.has_editing) for (var i=0;i<th.posts.length;i++) if (th.posts[i].editing)
//              th.posts[i].pn.classList.add(pref.cpfx+'post_editing');
//              th.posts[i].pn.style.background = '#cec952';
          }
//          else {
//            var pfunc_pn = site2[th.domain_html].parse_funcs['post_json'].pn;
//            for (var i=1;i<th.posts.length;i++) {
//              if (!th.posts[i].pn) th.posts[i].pn = pfunc_pn(th.posts[i]);
//              site2[th.domain].toplevel_anchor(th.posts[i], th.no);
//              if (site.nickname!==th.domain) site2[th.domain].absolute_link(th.posts[i]);
//              if (th.localArchive) if (!pref.test_mode['67']) archiver.url2file(th.localArchive, th.posts[i]);
//              if (th.posts[i].editing) th.posts[i].pn.style.background = '#cec952';
//            }
//          }
      };
      comf.Object_defProps(format_html,{
        prep_anchor_links: function(pn, th){
          if ((!pref.test_mode['109'] || th.type_data==='html') && (th.no!=site.no || th.board!=site.board || th.domain!=site.nickname)) site2[th.domain].toplevel_anchor(pn, th);
          if (site.nickname!==th.domain) site2[th.domain].absolute_link(pn, th);
        },
        prepare_html_post: function(th, post, thq, pref_env, not_anchor,  deactivate, from_not_popup, force){
          if (pref.test_mode['161'] && !force && post.pn) { // requires synchronization with page_json2html3, this can be solved by using setter, but in case of type_data==='html' it is difficult.
            if (post.type_data==='json' || post.extracted || th.domain===site.nickname) return;
            post.extracted = true;
//            this.prepare_html_extract_paramsfunction(th); // WILL BE on-demand extraction
          }
          if (!post.pn) {
            post.pn = site2[th.domain_html].parse_funcs['post_json'].pn(post);
            if (!thq) thq = th.lth.q; // for expand old posts, to add backlinks.
          }
          if (!not_anchor) this.prep_anchor_links(post.pn, th);
//          if (th.localArchive) if (!pref.test_mode['67']) archiver.url2file(th.localArchive, post); // merged to root html generator to prevent needless prefetch.
          site2[th.domain_html].format_pn(post.pn, (thq)? thq[post.no] : null, pref_env, post, th);
          if (post.editing) post.pn.classList.add(pref.cpfx+'post_editing');
//          if (post.editing) post.pn.style.background = '#cec952';
          if (pref[embed_mode].popup) site2[th.domain].popups_add_1(th, post, deactivate, thq || th.lth.q, from_not_popup, (pref_env? pref_env : pref[cataLog.embed_mode]).backlink);
          if (!post.pn_menu && pref[embed_mode].postMenu) post.pn_menu = this.add_postMenu(th, post);
        },
        add_postMenu(th, post){
          var pn_menu = triage.doms.postMenu.cloneNode(true);
//          var pn_menu = triage.postMenu(th.key+'#'+post.no+'@'+post.time_tu); // should be use pn2no and pn2time
          var bks = site2[th.domain_html].add_backlinks_bks_query(post.pn, true);
          if (bks) bks.parentNode.insertBefore(pn_menu, bks);
          else site2[th.domain_html].add_backlinks_bks_append(post.pn, pn_menu);
          return pn_menu;
        },
        get_t2h_from_index: function(idx){
          return ['page','L','M','N','N_unread','unread','all'][idx];
        },
        get_t2h_index: function(tgt_th){
          var t2h_sel = tgt_th[16].t2h_sel || pref[embed_mode].t2h_sel; 
          return (t2h_sel==='page')? 0 :
                 (t2h_sel==='L')? 1 :
                 (t2h_sel==='M')? 2 :
                 (t2h_sel==='N')? 3 :
                 (t2h_sel==='N_unread')? 4 :
                 (t2h_sel==='unread')? 5 : 6;
        },
//        get_t2h_num_of_posts: function(tgt_th, type_source){ // moved to tgt_th[16].prototype
//          var t2h_sel = tgt_th[16] && tgt_th[16].t2h_sel || pref[embed_mode].t2h_sel; 
//          if (t2h_sel!=='page' || type_source!=='page' || tgt_th[16].expand_posts) {
//            return (t2h_sel==='page')? tgt_th[16].t2h_page || pref[embed_mode].t2h_num_of_posts :
//                   (t2h_sel==='L')? pref[embed_mode].t2h_L :
//                   (t2h_sel==='M')? pref[embed_mode].t2h_M :
//                   (t2h_sel==='N')? pref[embed_mode].t2h_num_of_posts :
//                   (t2h_sel==='N_unread')? pref[embed_mode].t2h_num_of_posts + ((tgt_th[19][0]&0x000c0000)? (tgt_th[19][1]&0x0000ffff) : 0) :
//                   (t2h_sel==='unread')? ((tgt_th[19][0]&0x000c0000)? (tgt_th[19][1]&0x0000ffff) : tgt_th[16].t2h_page) :
//                   (t2h_sel==='no')? 0 : -1; // 'ALL' and 'ALL_agg'
//          } else return false;
//        },
        prep_blacklist_click_tn: function(th, posts, insert_thread_from_native, pf_mode){ // removed code for static(test_mode['104']) and simplified
          if (insert_thread_from_native && pf_mode.env.expand_thumbnail_inline_native && !(pf_mode.env.event_dynamic && th.domain===site.nickname))
            for (var j=0;j<posts.length;j++) {
              var tn_imgs = th.parse_funcs_html.tn_imgs(posts[j]);
              if (tn_imgs) for (var i=0;i<tn_imgs.length;i++) GEH.prototype.blacklist_click.add(tn_imgs[i]);
            }
        },
      });
      Clg.prototype.update_posts_in_page = function(th,name, init_new, insert_thread_from_native, tgt_th, merge_base){
//        update_posts_in_page: function(th,name, force_prep_html, remove){
//          if (!th.parse_funcs.has_posts && !remove) return;
//          if (force_prep_html) this.prepare_html(th); // for json
//          var tgt_th = this.threads[name];
//          var posts_used = [];
//          var mb = site2['DEFAULT'].update_posts_merge_bases; // working code, moved to upper hierarchy to be used for catalog also.
//          var merge_base_tgts = (init_new && (tgt_th[1] || !mb.initialized))? mb.base_tgts(th.key) : null;
//          var merge_base = (tgt_th[1] && merge_base_tgts)? mb.query(th.key, tgt_th, th, merge_base_tgts, true, this) : mb.bases[th.key];
////          var merge_base = (tgt_th[1] && merge_base_tgts)? mb.query((tgt_th[16].posts && tgt_th[16].posts!==th.posts)? {pn:tgt_th[0], __proto__:tgt_th[16]} : th, merge_base_tgts, true) : mb.bases[th.key]; // redundant but for saster execution, shall works correctly if this doesn't exist. (not debugged.)
//          if (merge_base_tgts && !tgt_th[1] && !mb.initialized) mb.setup_hook();
////          var merge_base = (pref[embed_mode].merge && tgt_th[1])? site2['DEFAULT'].update_posts_merge_bases.query((tgt_th[16].posts && tgt_th[16].posts!==th.posts)? {pn:tgt_th[0], __proto__:tgt_th[16]} : th, 'ALL', true) : site2['DEFAULT'].update_posts_merge_bases.bases[th.key]; // redundant but for saster execution, shall works correctly if this doesn't exist. (not debugged.)
////          if (init_new && merge_base && merge_base.pn!==th.pn) site2[th.domain_html].update_posts_merge_init(th, merge_base);

          var pf = pref[embed_mode];
          var pf_env = (!insert_thread_from_native)? pf
              : {colorID: pf.colorID && !pf.env.colorID_native,
                 backlink: pf.backlink && !pf.env.backlink_native,
                 localtime: pf.localtime && !pf.env.localtime_native};
          var not_anchor = init_new && th.key===site.key;
          var deactivate = insert_thread_from_native && pref[this.mode].env.popup_native && !pref[this.mode].env.event_dynamic;
          var posts_used = !insert_thread_from_native? (init_new? th.posts.slice() : [])// slice for merging deleted posts
                         : insert_thread_from_native.init? insert_thread_from_native.posts_shown.slice() : insert_thread_from_native.posts_updated;// slice for merging deleted posts
          for (var i=0;i<posts_used.length;i++) format_html.prepare_html_post(th, posts_used[i], th.lth.q, pf_env, not_anchor, deactivate, true, true);
          if (insert_thread_from_native) format_html.prep_blacklist_click_tn(th, posts_used, insert_thread_from_native, pref[this.mode]);
          if (merge_base || tgt_th[16].posts && tgt_th[16].posts!==th.posts) {
//          if (merge_base || tgt_th[16].posts && ((!insert_thread_from_native || insert_thread_from_native.init) && tgt_th[16].posts!==th.posts)) {
////          if (!init_new || tgt_th[16].posts && tgt_th[16].posts!==th.posts) {
//            if (init_new) {
//              posts_used = th.posts.slice(); // patch for merging deleted posts at initial.
//              for (var i=0;i<th.posts.length;i++) format_html.prepare_html_post(th, th.posts[i], th.lth.q, pf_env, not_anchor, deactivate, true, insert_thread_from_native);
//            }
            if (merge_base && !tgt_th[16].posts) tgt_th[16].posts = th.posts.slice(); // patch for initial merge
            site2[th.domain_html].update_posts_replace(th,tgt_th[16], tgt_th[0], merge_base, tgt_th[1], posts_used, this, deactivate);
//          site2[th.domain_html].update_posts_replace(th,tgt_th[16],(pref[embed_mode].merge)? site2['DEFAULT'].update_posts_merge_base : tgt_th[0], pref[embed_mode].merge, tgt_th[1], posts_used);
//            if (tgt_th[16].expand_posts) tgt_th[16].expand_posts = null;
          } else { // by native update or initialize without merge
            tgt_th[16].posts = th.posts.slice(); // slice for kwd filter of posts
//            posts_used = th.posts;
////            posts_used = !insert_thread_from_native? th.posts : (insert_thread_from_native.init)? insert_thread_from_native.posts_shown : insert_thread_from_native.posts_updated;
//            for (var i=0;i<posts_used.length;i++) format_html.prepare_html_post(th, posts_used[i], th.lth.q, pf_env, not_anchor, deactivate, true, insert_thread_from_native);
            if (!pref.test_mode['140']) if (init_new && insert_thread_from_native && pref[this.view].use_expander_always) site2[th.domain_html].page_json2html3_add_omitted_info(th,tgt_th[16], true, true);
          }
          if (pref.test_mode['140']) if (init_new && insert_thread_from_native && (this.view==='page' || this.view==='thread') && pref[embed_mode].use_expander_always) site2[th.domain_html].page_json2html3_replace_expander(th.posts, format_html.get_t2h_index(tgt_th), name);
//          if (insert_thread_from_native) posts_used = (insert_thread_from_native.init)? insert_thread_from_native.posts_shown : insert_thread_from_native.posts_updated;

          if (common_obj.thread_reader) common_obj.thread_reader.updated(posts_used, insert_thread_from_native && insert_thread_from_native.init); // for merge // BUG. can't call with init because thread_reader doesn't exist at the time of initial. own_posts_tracker in thread_reser doesn't work. // patched
          return posts_used;
      };
      comf.Object_defProps(format_html,{
        replace_posts_by_search: function(tgt_th, pref){ // tgt_th[16].th may be set by updater. // pref for temporal
          var th = tgt_th[7];
          if (pref.filter.kwd.posts_active) {
            if (tgt_th[10]!=pref.filter.kwd.timestamp || tgt_th[16].th) { // existence of tgt_th[16].th means thread is updated.
              tgt_th[10] = pref.filter.kwd.timestamp;
              var th_new = catalog_filter_query_keyword.kwd_make_result(tgt_th[16].recent_posts(pref[embed_mode].sourceOfSP==='auto'? -1 : null), th.domain);
//              var th_new = catalog_filter_query_keyword.kwd_make_result(th.posts, th.domain, th);
              if (!th_new) {
                tgt_th[9][0] = false;
                tgt_th[16].th = null;
              } else {
                var flag = (!tgt_th[16].posts || tgt_th[16].posts.length!==th_new.length);
                if (!flag) {
                  var i=th_new.length;
                  while (--i>=0) if (tgt_th[16].posts[i].no!==th_new[i].no) {flag=true; break;} // for no OP
//                  if (!flag && tgt_th[16].posts && tgt_th[16].posts[0] && tgt_th[16].posts[0].pn && // BUG in page mode, tgt_th[16].posts[0].pn exists always even if flag==true.
//                      (tgt_th[16].posts[0].pn.classList.contains('CatChan_search_miss')==th_new[0].search_result))
//                    site2[th.domain_html].update_posts0_class(tgt_th[16].posts[0].pn,th_new[0].search_result);
                }
                if (tgt_th[16].posts && tgt_th[16].posts[0] && tgt_th[16].posts[0].pn &&
                    (tgt_th[16].posts[0].pn.classList.contains('CatChan_search_miss')==th_new[0].search_result))
                  site2[th.domain_html].update_posts0_class(tgt_th[16].posts[0].pn,th_new[0].search_result);
                if (flag) {    
                  format_html.prepare_html_prep_posts(th);
                  tgt_th[16].th = {posts:th_new, __proto__:th};
//                  tgt_th[10] = pref.filter.kwd.timestamp;
                } else if (tgt_th[16].th) tgt_th[16].th = null;
              }
            }
          } else if (tgt_th[10] || tgt_th[16].th){ // rollback
            if (!tgt_th[16].th) tgt_th[16].th = {posts:tgt_th[16].recent_posts(), __proto__:th}; // {posts:tgt_th[16].recent_posts(null,null,true), __proto__:th};
//            if (!tgt_th[16].th) tgt_th[16].th = scopy_th(th);
            tgt_th[10] = false;
          }
          if (tgt_th[16].th && tgt_th[7]) // updated by this or updater.
            if (tgt_th[16].posts===tgt_th[7].posts) tgt_th[7].posts = tgt_th[7].posts.slice(); // backup
          return !pref.filter.kwd.posts_active || tgt_th[9][0];
        },
        posts_search_inactivated: function(){
          for (var name in threads) {
            var th = threads[name][7];
            var tgt_th16 = threads[name][16];
            if (tgt_th16.posts && tgt_th16.posts[0] && tgt_th16.posts[0].pn && tgt_th16.posts[0].pn.classList.contains('CatChan_search_miss')) {
              site2[th.domain_html].update_posts0_class(tgt_th16.posts[0].pn,true);
//              delete th.posts[0].search_result; // can't update omitted info in page mode.
            }
          }
          pClg.drawn_idx = 0; // lazy filter.
        },
        hide_posts_without_images: function(clg, posts){
          var hide = clg.pref[clg.mode].hide_posts_without_images;
          for (var i=0;i<posts.length;i++) {
            if (posts[i].nofFiles===0)
              if (hide) posts[i].pn.classList.add(pref.cpfx+'hiddenWOF');
              else posts[i].pn.classList.remove(pref.cpfx+'hiddenWOF');
          }
        },
//        hide_posts_without_images: function(th,name){
//          var posts = threads[name][16].posts;
//          for (var i=0;i<posts.length;i++) {
//            var tn_imgs = th.parse_funcs_html.tn_imgs(th.posts[i]); // for test
//            if (tn_imgs.length===0) posts[i].pn.style.display = 'none';
//          }
//        },
        postFilter: function(clg, th, posts, obj_remove, obj_added, obj_remove_global){
          var objs_global = (pref.postFilter.use || obj_remove_global)? pref_func.merge_obj5a(th, obj_remove_global||pref.postFilter.obj7, null) : null;
          var objs = (clg.pref.filter.postFilter.use || obj_remove)? pref_func.merge_obj5a(th, obj_remove||obj_added||clg.pref.filter.postFilter.obj7, objs_global) : objs_global; //  || [{str:pref.filter.kwd.str, com:true, ci:true, match:0, style:{border:'10px solid red'}, get rexps(){return pref.filter.kwd.rexps;}}];
          if (objs) for (var i=0;i<posts.length;i++) for (var j=0;j<objs.length;j++) {
            var obj = objs[j];
            if (obj.use!==false && (!obj.boards || obj.boards.indexOf(th.board.slice(1,-1))!=-1)) {
              if (catalog_filter_query_keyword.kwd_1({use:true, __proto__:obj}, posts[i], th.domain)) {
                var pn = posts[i].pn; // (site.hasPostContainer)? posts[i].pn.parentNode : posts[i].pn; // this is not slow critically, because filterd posts are not so many.
                if (!pn) continue;
                if (obj_remove) {
                  if (obj.hide) pn.classList.remove(pref.cpfx+'hiddenAuto');
                  if (obj.style) {
                    if (obj.style[0]==='.') pn.classList.remove(obj.style.slice(1));
                    else pn.removeAttribute('style',obj.style);
                  }
                } else {
                  if (obj.hide) pn.classList.add(pref.cpfx+'hiddenAuto');
                  if (obj.style) {
                    if (obj.style[0]==='.') pn.classList.add(obj.style.slice(1));
                    else pn.setAttribute('style',obj.style);
                  }
                }
              }
            }
          }
        },
      });
      cataLog.format_html = format_html;
      comf.Object_defProps(Clg.prototype,{
        view_update_draw_merged: function(th) { // from popup
          if (this.pref[this.mode].mark_new_posts) {// draw only
            var base = this.merge_bases.bases[th.key];
            var mark_time = !base? (this.threads[th.key][19][0]&0x000c0000) && this.get_mark_time(th.key)
              : base.lths.map(function(lth){return lth.watched && this.get_mark_time(lth.key);},this).reduce(function(a,c){return c>a? c : a;});
            if (mark_time>0) site2[th.domain].mark_newer_posts2(th.posts, mark_time, this.mode==='thread' && pref.thread_reader.unmark_on_hover, true);
          }
        },
        view_update_draw: function(name, posts) { // , pn, th){
          if (this.pref[this.mode].mark_new_posts && (this.threads[name][19][0]&0x000c0000)) {// draw only
            var mark_time = this.get_mark_time(name);
            if (mark_time===0 && this.view==='thread') mark_time = site2['DEFAULT'].check_reply.get_checked_time(this.threads[name][19]);
            if (mark_time>0) site2[this.threads[name][16].domain].mark_newer_posts2(posts, mark_time, this.mode==='thread' && pref.thread_reader.unmark_on_hover, true);
          }
        },
        view_update: function(name){ // draw and ERASE.
          if (!this.threads[name][16].posts) return;
          var date = (this.pref[this.mode].mark_new_posts && (this.threads[name][19][0]&0x000c0000))? this.get_mark_time(name) : 0;
          if (date===0) date = Infinity; // to erase mark
          site2[this.threads[name][16].domain].mark_newer_posts2(this.threads[name][16].posts, date, this.mode==='thread' && pref.thread_reader.unmark_on_hover, false);
        },
        view_time_filter_changed: function(name){ // draw and ERASE.
          if (this.view==='page' || this.view==='thread')
            if (name) this.view_update(name);
            else for (name in this.threads) this.view_update(name);
        },
      });

      Clg.prototype.get_watch_time_of_a_thread = function(name, time_created, time_posted){
        var pf = this.pref.filter;
        var ret_time = this.get_mark_time(name, true);
        return ret_time<0? -ret_time-1 : // time in watchlist
               (time_posted && ret_time>time_posted)? 0 :
               (pf.time.w>1 && ret_time>time_created)? 0 : // (!pf.time_watch && pf.time_watch_creation && ret_time>time_created)? 0 :
               ret_time;
      };

//      function func_in(e){pop_up_delay(e,this.name);triage.thread_in(e);}
//      function func_pop_up(e){pop_up_delay(e,this.name);}

// Footer was here

      comf.Object_defProps(Clg.prototype, (function(){ // threads_idx, threads_odl and drawn_idx must be separated.
//        var threads_odl = {};
//        var threads_odl_default = { '8': [0, undefined, undefined, undefined, undefined], // disable 'on demand mode' at indexing of 1,2,3
        var threads_odl_default = { '8': [0, 0, 0, 0, 0],
                                   '19': [0,0,0]};
        function get_threads_odl_default(str){
          var page = parseInt(str.substr(str.lastIndexOf('/')+2),10);
          return (page===0)? {'8': [undefined, undefined, undefined, undefined, undefined], __proto__: threads_odl_default} : // force to top
                             {'8': [-page, -page, -page, -page, -page], __proto__: threads_odl_default};  // force to last
        }
        var threads_odl,threads;
        var start,end;
        var kwd_odl = ':DL:';
        var check_funcs = {
          reply_to_me: function(tgt_th){return (tgt_th[19][0]&0x0c0c0000) && (tgt_th[19][1]>>16)!=0 && 3;},
          reply: function(tgt_th){return (tgt_th[19][0]&0x0c0c0000) && tgt_th[19][1]!=0 && 2;},
          watch_first: function(tgt_th){return tgt_th[19][0]&0x0c0c0000 && 1;},
          watch_last: function(tgt_th){return !(tgt_th[19][0]&0x0c0c0000) && 1;},
          sticky_first: function(tgt_th){return tgt_th[20] && 4;},
          sticky_last: function(tgt_th){return !tgt_th[20] && 4;},
          both_cascade: function(func, func_cas, func2_cas){ // for faster .sort()
            return func && func2_cas? function(tgt_th){
              return (func(tgt_th)|0) + (func2_cas(tgt_th)|0);} : func_cas || func2_cas || 0;
          },
//          both: function(val, func, func2){
//            return func && func2? function(tgt_th){return func(tgt_th) && func2(tgt_th) && val;} : 0;
//          },
          cascade: function(func, func2){
            return func && func2? function(tgt_th){return func(tgt_th) || func2(tgt_th);} : func || func2 || 0;
          },
          funcs_lvl: function(pf){
            var arr = [0, this['watch_'+pf.watch] || 0, pf.reply && this.reply, pf.reply_to_me && this.reply_to_me, this['sticky_'+pf.sticky]];
            var arr_raw4 = arr[4];
            for (var i=2;i<4;i++) arr[i] = this.cascade(arr[i], arr[i-1]);
            for (var i=5;i<8;i++) arr[i] = this.both_cascade(arr_raw4, arr[4], arr[i%4]);
//            if (arr[4]) for (var i=5;i<8;i++) arr[i] = this.both(i, arr[4], arr[i%4]);
//            for (var i=2;i<8;i++) arr[i] = this.cascade(arr[i], arr[i-1]);
////            var arr = [0];
////            arr[1] = this['watch_'+pf.watch] || 0;
////            arr[2] = this.cascade(pf.reply && this.reply, arr[1]);
////            arr[3] = this.cascade(pf.reply_to_me && this.reply_to_me, arr[2]);
////            arr[4] = this.cascade(this['sticky_'+pf.sticky], arr[3]);
////            arr[5] = this.cascade(this.both(5, 'sticky_'+pf.sticky, 'watch_'+pf.watch), arr[4]);
////            arr[6] = this.cascade(this.both(6, 'sticky_'+pf.sticky, pf.reply && 'reply'), arr[5]);
////            arr[7] = this.cascade(this.both(7, 'sticky_'+pf.sticky, pf.reply_to_me && 'reply_to_me'), arr[6]);
            return arr;
          },
        }
        function get_tgt_th(str){
          return (str[0]===':')? (threads_odl[str] || get_threads_odl_default(str)) :
                                  threads[str];
        }
        function check_sub(myself, check_func, idxs){
          var ref = start;
          while (ref<end && check_func(get_tgt_th(idxs[ref]))) ref++;
          if (check_func(myself)) end = ref;
          else start = ref;
        }
        var now;
        var get_date;
        var date;
        var funcs_get_date = {
          0: function(tgt_th8){return tgt_th8[0] || tgt_th8[4];},
          4: function(tgt_th8){return tgt_th8[4] || tgt_th8[0];},
          get 5(){now = Footer.ts_refresh; return this.f5;}, // for faster execution
          f5: function(tgt_th8){return (now - tgt_th8[1])/tgt_th8[2];}, // this allows minus value, which is caused by unprecise local clock.
//          get 5(){var now = Footer.ts_refresh; return function(tgt_th8){return (now - tgt_th8[1])/tgt_th8[2];}}, // this allows minus value, which is caused by unprecise local clock.
//          get 5(){var now = Date.now(); return function(tgt_th8){return (now - tgt_th8[1])/tgt_th8[2];}}, // this allows minus value, which is caused by unprecise local clock.
          1: function(tgt_th8){return tgt_th8[1];},
          2: function(tgt_th8){return tgt_th8[2];},
          3: function(tgt_th8){return tgt_th8[3];},
          c0: function(tgt_th8){return date>=get_date(tgt_th8);},
          c1: function(tgt_th8){return date<=get_date(tgt_th8);},
          c0b: function(tgt_th8){return date>get_date(tgt_th8);}, // for backward insertion
          c1b: function(tgt_th8){return date<get_date(tgt_th8);},
        }
        function insert(name, clg){
//          if (pref.test_mode['169']) var ref_bulk = insert_bulk([name], {idxs: clg.idxs.slice(), __proto__:clg});
          var idxs = clg.idxs;
          threads = clg.threads;
          threads_odl = clg.threads_odl;
          start = 0;
          end = idxs.length;
          var pf = clg.order;
          var myself = get_tgt_th(name);
          if (pf.sticky!=='dont_care') check_sub(myself, (pf.sticky==='last')? check_funcs.sticky_last : check_funcs.sticky_first, idxs);
          if (pf.reply_to_me) check_sub(myself, check_funcs.reply_to_me, idxs);
          if (pf.reply) check_sub(myself, check_funcs.reply, idxs);
          if (pf.watch!=='dont_care') check_sub(myself, (pf.watch==='last')? check_funcs.watch_last : check_funcs.watch_first, idxs);
          get_date = funcs_get_date[pf.ordering%6];
//          var indexing  = pref.catalog.indexing%6;
//          var indexing2 = pref.catalog.indexing;
//          var get_date = (indexing===0 || indexing===4)? function(tgt_th){return tgt_th[8][indexing] || tgt_th[8][(indexing===0)?4:0];} :
//                         (indexing===5)?                 (function(){var now = Date.now(); return function(tgt_th){return (now - tgt_th[8][1])/tgt_th[8][2];}})() :
//                                                         function(tgt_th){return tgt_th[8][indexing];};
          date = get_date(myself[8]);
          var check_func = (pf.ordering>=5 && pf.ordering<11)? funcs_get_date.c0 : funcs_get_date.c1;
//          var check_func = (pf.ordering>=5 && pf.ordering<11)? function(tgt_th){return date>=get_date(tgt_th[8]);} : // not optimized: optimiezed too many times
//                                                               function(tgt_th){return date<=get_date(tgt_th[8]);};
          var ref = start;
          while (ref<end && check_func(get_tgt_th(idxs[ref])[8])) ref++;
  
          if (ref==idxs.length) idxs[idxs.length] = name;
          else idxs.splice(ref,0,name);
  //        if (ref<=drawn_idx) drawn_idx = 0; // can't track by 'drawn_idx = ref; drawn_y = 0;'. drawn_xxx must be synchronized.
          if (!pref.test_mode['148']) { // base may not exist here because base is generated in show_catalog, but the thread will be appended at the case and order won't broken.
            var base = clg.merge_bases.bases[name];
            if (base) base.reorder_req |= 5; // base.reorder(key, clg); // direct call causes chattering
          }
//          if (pref.test_mode['169'] && ref_bulk!==ref) {
//            console.log('ERROR: insert_bulk: bulk:'+ref_bulk+', single:'+ref);
//            insert_bulk([name], {idxs: clg.idxs.slice(0,ref).concat(clg.idxs.slice(ref+1)), __proto__:clg});
//          }
          return ref;
        }
        var retval_first_idx;
        function insert_bulk(names, clg, almost_sorted, from_merged, rm_dic,    checked){ // Merges unsorted names with sorted idxs.
          if ((/*pref.test_mode['169'] ||*/ pref.test_mode['172']) && !checked) {
            var idxs_before = almost_sorted? [] : clg.idxs;
            var names_before = names.slice();
            var rm_dic_before = rm_dic && Object.keys(rm_dic).reduce(dic_null,{});
            var idxs_new = insert_bulk(names, clg, almost_sorted, from_merged, rm_dic, true);
            check_bulk(idxs_new, clg.idxs_single, clg, clg.loose_check, idxs_before, names_before, almost_sorted, from_merged, rm_dic_before);
            return idxs_new;
          }
          if (!from_merged && /*!pref.test_mode['169'] &&*/ !pref.test_mode['148']) { // base may not exist here because base is generated in show_catalog, but the thread will be appended at the case and order won't broken.
            var mb_base = clg.merge_bases.bases;
            for (var i=0;i<names.length;i++) {
              var base = mb_base[names[i]];
              if (base) base.reorder_req |= 5; // base.reorder(key, clg); // direct call causes chattering
            }
          }
          threads = clg.threads;
          threads_odl = clg.threads_odl;
          var pf = clg.order;
          funcs_lvl = check_funcs.funcs_lvl(pf);
//          funcs_0 = pf.sticky!=='dont_care'? [pf.sticky==='last'? check_funcs.sticky_last : check_funcs.sticky_first] : [];
//          if (pf.reply_to_me) funcs_0[funcs_0.length] = check_funcs.reply_to_me;
//          if (pf.reply) funcs_0[funcs_0.length] = check_funcs.reply;
//          if (pf.watch!=='dont_care') funcs_0[funcs_0.length] = pf.watch==='last'? check_funcs.watch_last : check_funcs.watch_first;
          get_date = funcs_get_date[pf.ordering%6];
          var func_1f = (pf.ordering>=5 && pf.ordering<11)? funcs_get_date.c0 : funcs_get_date.c1;
          var func_1b = (pf.ordering>=5 && pf.ordering<11)? funcs_get_date.c0b : funcs_get_date.c1b;
          var func_1 = (!pref.test_mode['171'] || pf.ordering>=6)? func_1f : func_1b;

          // SOULD I USE .sort() instead of implementing them by myself?
          if (names.length===1 && !pref.test_mode['175'] && (!rm_dic || Object.keys(rm_dic).length===0)) return insert_bulk_1(clg.idxs, names[0], func_1, pf.ordering<6, func_1b);
          // test_mode['173'](insert_sort) is too slow with 4000 threads for usual usage in 5ch.
          //   insert_sort requires 100-120ms for average, whereas insert_bulk requires 20-30ms. see debug_mode['42']
          // 700 threads * 5 boards are merged with 3500 threads in 2 catalogs, ordering 'fast' and 'create' refresh:
          //   insert_sort: 268.0ms, 167.6ms = 134.2ms +  33.4ms (insert_bulk_all + insert_bulk_1by1)
          //                305.9ms, 686.9ms = 676.9ms +  10.3ms
          //                 54.9ms, 162.6ms = 162.1ms +   0.5ms
          //                274.6ms, 390.9ms = 352.5ms +  38.4ms
          //                125.7ms, 391.4ms = 266.8ms + 124.6ms
          //                 82.6ms,  66.8ms =  42.7ms +  24.1ms (insert_bulk_all(filter) + insert_bulk_1by1)
          //                 99.0ms,  27.9ms =  21.4ms +   6.5ms
          //                199.5ms,  35.2ms =  29.3ms +   5.9ms
          //                146.6ms,  55.8ms =  46.9ms +   8.9ms
          //                209.8ms, 107.5ms =  99.0ms +   8.5ms
          //                293.5ms, 111.3ms =  88.1ms +  21.6ms + 1.6ms (insert_bulk_all(filter) + insert_bulk_1by1 + insert_sort(!pref.test_mode['174'])
          //                 89.7ms,  37.3ms =  33.6ms +   2.8ms + 0.9ms
          //                186.0ms,  57.9ms =  55.5ms +    ?    + 2.4ms // ? means under 0.4
          //                208.8ms,  96.5ms =  77.6ms +  18.5ms + 0.4ms
          //                312,4ms, 142.8ms = 120.0ms +  20.2ms + 2.6ms 
          if (pref.test_mode['173']) return (almost_sorted || from_merged)? insert_sort(names, null, clg) : insert_sort(clg.idxs, names, clg, rm_dic);
//          if (pref.test_mode['173']) { // equivalent fast to insert_bulk_all even if 700 threads are merged with 3500 threads.
//            if (almost_sorted || from_merged) return insert_sort(names, null, clg);
//            var idxs_old = clg.idxs; // .slice(0, clg.drawn_idx===true? undefined : clg.drawn_idx); // clg.idxs is NOT changed, sort will be executed in a shallow copied array.
//            var old_clean = clg.drawn_idx===true? idxs_old.length : clg.drawn_idx;
//            var idxs_new = insert_sort(clg.idxs, names, clg, rm_dic);
//            var has_posts = clg.view==='page' || clg.view==='thread';
//            for (var i=0;i<old_clean;i++) if (idxs_old[i]!==idxs_new[i] || has_posts && (idxs_new[i] in rm_dic)) break;
//            retval_first_idx = i;
//            return idxs_new;
//          }
          if (almost_sorted) return !pref.test_mode['176']? insert_sort(names, null, clg) : insert_bulk_1by1(names, func_1b);
          if (names.length===1) return insert_bulk_all(clg.idxs, names, func_1, rm_dic || true);
          if (!pref.test_mode['174']) return insert_bulk_all(clg.idxs, insert_sort(names, null, clg), func_1, rm_dic || true);

//          var step = names.length;  // in-place // not debugged.
//          while (step>8) step = Math.ceil(step/2);
//          for (var k=0;k<names.length;k+=step) arr_overwrite(names, insert_bulk_1by1(names.slice(k,step)), k);
//          for (var s=step;s<names.length;s*=2)
//            for (var k=0;k<names.length;k+=s*2) arr_overwrite(names, insert_bulk_all(names.slice(k,s), names.slices(k+s,k+s*2), func_1f), k);
          var names_slices = []; // not in-place
          while (names.length>0) names_slices[names_slices.length] = insert_bulk_1by1(names.splice(0,8), func_1b);
//          var k=0;
//          while (k<names.length) {
//            var idxs_tmp = [names[k]];
//            while (++k<names.length && k%8!=0) insert_bulk_init(idxs_tmp, names[k]);
//            names_slices[names_slices.length] = idxs_tmp;
//          }
          while (names_slices.length>1) { // keep order
            var k=0;
            while (names_slices.length>k+1) {insert_bulk_all(names_slices[k],   names_slices.splice(k+1,1)[0], func_1f); k++;}
            if (names_slices.length>k) insert_bulk_all(names_slices[k-1], names_slices.splice(k,1)[0], func_1f);
          }
//          while (names_slices.length>1) names_slices.push(insert_bulk_1.apply(null, names_slices.splice(0,2))); // shuffles order, not stable
          return insert_bulk_all(clg.idxs, names_slices[0], func_1, rm_dic || true);
        }
//        function arr_overwrite(dst, src, start){ // not debugged.
//          for (var i=0;i<src.length;i++) dst[start+i] = src[i];
//        }
        function insert_bulk_1(idxs, name, func_1, forward, func_1b){ // in-place merge 1
          var func = func_1;
          var found = -1;
          var func_lvl = funcs_lvl[7] || function(){return 0;};
          var src_th = get_tgt_th(name);
          var src_lvl = func_lvl(src_th)|0; // func_lvl() may return undefined or false
          date = get_date(src_th[8]);
          var p = forward? 0 : idxs.length-1;
          var end = forward? idxs.length : -1;
          var step = forward? 1 : -1;
          while (p!=end) {
            var tgt = idxs[p];
            if (found==-1 && tgt===name) {found = p; p += step; func = func_1b; continue;} // keep position
//            if (found==-1 && tgt===name) {found = p; p += step; continue;}
            var tgt_th = get_tgt_th(tgt);
            var tgt_lvl = func_lvl(tgt_th)|0;
            if (forward ^ !(src_lvl==tgt_lvl && !func(tgt_th[8]) || src_lvl>tgt_lvl)) break;
            p += step;
          }
          if (found===-1) {
            found = forward? idxs.indexOf(name,p+1) : idxs.lastIndexOf(name,p-1);
            if (found===-1) {
              found = idxs.length;
              if (!forward) p++;
            }
          } else {
            p -= step;
            if (found===p) return idxs; // p;
          }
          if (p<found) for (var i=found;i>p;i--) idxs[i] = idxs[i-1];
          else for (var i=found;i<p;i++) idxs[i] = idxs[i+1];
          idxs[p] = name;
          retval_first_idx = p<found? p : found;
          return idxs;
        }
//        function insert_bulk_1(idxs, name, func_1, forward){ // in-place merge 1
//          var removed = -1;
//          var func_lvl = funcs_lvl[7] || function(){return 0;};
//          var src_th = get_tgt_th(name);
//          var src_lvl = func_lvl(src_th)|0; // func_lvl() may return undefined or false
//          date = get_date(src_th[8]);
//          if (forward) {
//            var p = -1;
//            while (++p<idxs.length) {
//              var tgt = idxs[p];
//              if (removed==-1 && tgt===name) {removed = p; continue;}
//              var tgt_th = get_tgt_th(tgt);
//              var tgt_lvl = func_lvl(tgt_th)|0;
//              if (src_lvl==tgt_lvl && !func_1(tgt_th[8]) || src_lvl>tgt_lvl) break;
//            }
////            if (removed==-1) removed = idxs.indexOf(name,p+1); // not debugged
////            return insert_bulk_1_move(idxs, name, removed>=0? --p : p, removed);
//////            if (removed>=0) return (removed===--p)? p : insert_bulk_1_move_b(idxs, name, removed, p); // -1 for removed // not debugged
//////            else {
//////              var e = idxs.indexOf(name,p+1);
//////              return insert_bulk_1_move_f(idxs, name, p, e==-1? idxs.length : e);
//////            }
//            if (removed>=0) {
//              p--; // -1 for removed
//              if (removed===p) return p;
//              for (var s=removed;s<p;s++) idxs[s] = idxs[s+1];
//              idxs[p] = name;
//              return removed;
//            } else {
//              var e = p;
//              while (e<idxs.length && idxs[++e]!==name);
//              for (var i=e;i>p;i--) idxs[i] = idxs[i-1];
//              idxs[p] = name;
//              return p;
//            }
//          } else {
//            var p = idxs.length;
//            while (--p>=0) {
//              var tgt = idxs[p];
//              if (removed==-1 && tgt===name) {removed = p; continue;}
//              var tgt_th = get_tgt_th(tgt);
//              var tgt_lvl = func_lvl(tgt_th)|0;
//              if (src_lvl==tgt_lvl &&  func_1(tgt_th[8]) || src_lvl<tgt_lvl) break;
//            }
////            if (removed==-1) removed = idxs.lastIndexOf(name,p-1); // not debugged
////            return insert_bulk_1_move(idxs, name, removed>=0? p : ++p, removed);
//////            if (removed>=0) return (removed===++p)? p : insert_bulk_1_move_f(idxs, name, p, removed); // not debugged
//////            else {
//////              var s = idxs.lastIndexOf(name,p-1);
//////              return (s>=0)? insert_bulk_1_move_b(idxs, name, s, p)
//////                           : insert_bulk_1_move_f(idxs, name, ++p, idxs.length);
//////            }
//            if (removed>=0) {
//              p++;
//              if (removed===p) return p;
//              for (var e=removed;e>p;e--) idxs[e] = idxs[e-1];
//              idxs[p] = name;
//              return p;
//            } else {
//              var s = p;
//              while (--s>=0 && idxs[s]!==name);
//              if (s>=0) for (var i=s;i<p;i++) idxs[i] = idxs[i+1];
//              else {
//                p++;
//                for (var i=idxs.length;i>p;i--) idxs[i] = idxs[i-1];
//              }
//              idxs[p] = name;
//              return s>=0? s : p;
//            }
//          }
//        }
////        function insert_bulk_1_move_b(idxs, name, start, end){ // not debugged
////          for (var i=start;i<end;i++) idxs[i] = idxs[i+1];
////          idxs[end] = name;
////          return start;
////        }
////        function insert_bulk_1_move_f(idxs, name, start, end){ // not debugged
////          for (var i=end;i>start;i--) idxs[i] = idxs[i-1];
////          idxs[start] = name;
////          return start;
////        }
        function filterInPlace(idxs, names, rm_dic){ // x5-10 faster than filter
          if (idxs.length==0 || !rm_dic && names.length==0) return -1;
          if (!rm_dic && names.length==1) {
            var idx_old = idxs.indexOf(names[0]);
            if (idx_old!=-1) idxs.splice(idx_old,1);
            return idx_old;
          }
          if (!rm_dic || rm_dic===true) rm_dic = names.reduce(dic_null,{});
          var i=-1;          
          while (++i<idxs.length) if (idxs[i] in rm_dic) break;
          if (i==idxs.length) return -1;
          idx_old = i;
          var p = i;
          while (++i<idxs.length) if (!(idxs[i] in rm_dic)) idxs[p++] = idxs[i];
          idxs.length = p;
          return idx_old;
        }
        function insert_bulk_all(idxs, names, func_1, rm_dic){ // idxs and names must be sorted in advance
          if (idxs.length==0) return names;
          var idx_old = (rm_dic)? filterInPlace(idxs, names, rm_dic) : -1;
//          if (clg && idxs.length>0) { // remove old entries
//            if (names.length==1) {
//              var idx_old = idxs.indexOf(names[0]);
//              if (idx_old!=-1) idxs.splice(idx_old,1);
//            } else {
//              var rm_dic = names.reduce(function(a,c){a[c]=null;return a;},{});
//              idx_old = filterInPlace(idxs, rm_dic);
////              idxs = idxs.filter(function(v,i){var rm = (v in rm_dic); if (rm && idx_old===undefined) idx_old = i; return !rm;});
////              clg.idxs = idxs;
//////              var i=-1;
//////              while (++i<idxs.length) if (idxs[i] in rm_list) {idx_old=i; idxs.splice(i--,1); break;} // splice is to heavy
//////              var count = 1;
//////              while (++i<idxs.length) if (idxs[i] in rm_list) {idxs.splice(i--,1); if (++count>=names.length) break;}
//            }
//          }
          func_0 = funcs_lvl[7];
          if (idxs.length==0 || !func_0(get_tgt_th(idxs[0])) && !func_0(get_tgt_th(names[0]))) func_0 = 0; // for faster execution in no-sticky boards
          var i=1;
          if (!pref.test_mode['180']) { // in-place
            idxs = Array(names.length).concat(idxs);
            p_out = 0;
            var pos = insert_bulk_111InPlace(idxs, names[0], names.length, func_1);
            var idx_first = p_out -1;
            while (i<names.length) pos = insert_bulk_111InPlace(idxs, names[i++], pos, func_1);
          } else {
            var pos = insert_bulk_111(idxs, names[0], 0, func_1);
            var idx_first = pos;
            while (++pos<idxs.length && i<names.length) pos = insert_bulk_111(idxs, names[i++], pos, func_1);
            while (i<names.length) idxs[pos++] = names[i++];
          }
          retval_first_idx = (idx_old>0 && idx_old<idx_first)? idx_old : idx_first;
          return idxs;
        }
        function insert_bulk_1by1(names, func_1b){
          var idxs = [names[names.length-1]];
          for (var i=names.length-2;i>=0;i--) {
            func_0 = funcs_lvl[7];
            insert_bulk_111(idxs, names[i], 0, func_1b);
          }
          return idxs;
        }
//        function insert_bulk_init(idxs, name, func_1){
//          func_0 = funcs_lvl[7];
//          if (idxs.length==0 || !func_0(get_tgt_th(name)) && !func_0(get_tgt_th(idxs[0]))) func_0 = 0; // for faster execution in no-sticky boards
////          func_0 = funcs_0[0];
////          j = 1;
//          return insert_bulk_111(idxs, name, 0, func_1);
//        }
        var p_out;
        function insert_bulk_111InPlace(idxs, name, pos, func_1){
          var src_th = get_tgt_th(name);
          var src_lvl = func_0 && func_0(src_th)|0; // func_0() may return undefined or false
          date = get_date(src_th[8]);
          while (pos<idxs.length) {
            var tgt = idxs[pos];
            var tgt_th = get_tgt_th(tgt);
            var ref_lvl = func_0 && func_0(tgt_th)|0;
            if (src_lvl!=ref_lvl) {
              if (src_lvl<ref_lvl) {
                idxs[p_out++] = tgt;
                while (++pos<idxs.length && src_lvl<(func_0(get_tgt_th(idxs[pos]))|0)) idxs[p_out++] = idxs[pos];
                func_0 = funcs_lvl[src_lvl];
              } else { // 'if (src_lvl!=ref_lvl) if (src_lvl>ref_lvl)' is dangerous, this causes infinite loop if the values are undefined and false
                func_0 = funcs_lvl[src_lvl];
                idxs[p_out++] = name; // idxs.splice(pos,0,name);
                return pos;
              }
            } else if (!func_1(tgt_th[8])) {
              idxs[p_out++] = name; // idxs.splice(pos,0,name);
              return pos;
            } else {idxs[p_out++] = tgt; pos++;} // pos++;
          }
          idxs[p_out++] = name;
          return pos;
        }
        var funcs_lvl;
//        var func_1;
//        var j;
        var func_0;
        function insert_bulk_111(idxs, name, pos, func_1){
          var src_th = get_tgt_th(name);
          var src_lvl = func_0 && func_0(src_th)|0; // func_0() may return undefined or false
          date = get_date(src_th[8]);
          while (pos<idxs.length) {
            var tgt_th = get_tgt_th(idxs[pos]);
            var ref_lvl = func_0 && func_0(tgt_th)|0;
            if (src_lvl!=ref_lvl) {
              if (src_lvl<ref_lvl) {
                while (++pos<idxs.length && src_lvl<(func_0(get_tgt_th(idxs[pos]))|0));
                func_0 = funcs_lvl[src_lvl];
              } else { // 'if (src_lvl!=ref_lvl) if (src_lvl>ref_lvl)' is dangerous, this causes infinite loop if the values are undefined and false
                func_0 = funcs_lvl[src_lvl];
                idxs.splice(pos,0,name);
                return pos;
              }
            } else if (!func_1(tgt_th[8])) {
//              if (pos===0) idxs.unshift(name); else // should this be used for pre-sorted array?
              idxs.splice(pos,0,name);
              return pos;
            } else pos++;
          }
          idxs[pos] = name;
          return pos;
        }
//        function insert_bulk_111(idxs, name, pos){ // works, but BUG, different categorization.
//          var tgt_th = get_tgt_th(name);
//          var mine = func_0 && func_0(tgt_th);
//          date = get_date(tgt_th[8]);
//          while (pos<idxs.length) {
//            tgt_th = get_tgt_th(idxs[pos]);
//            if (func_0 && mine^func_0(tgt_th) && !mine) { // mine!=func_0(tgt_th) doesn't work, mine may be undefined // BUG, different categorization.
//              while (++pos<idxs.length && func_0(get_tgt_th(idxs[pos])));
//              func_0 = funcs_0[j++];
//              mine = func_0 && func_0(get_tgt_th(name));
//            } else if (func_0 && mine || !func_1(tgt_th[8])) {
//              idxs.splice(pos,0,name);
//              return pos;
//            } else pos++;
//          }
//          idxs[pos] = name;
//          return pos;
//        }
        function insert_sort_up(a,b)  {return b[0] - a[0] || a[1] - b[1] || a[2] - b[2];}
        function insert_sort_down(a,b){return b[0] - a[0] || b[1] - a[1] || a[2] - b[2];}
        function insert_sort(idxs, names, clg, rm_dic){
          var func_lvl = funcs_lvl[7] || function(){return 0;};
          var sort_func = (clg.order.ordering>=5 && clg.order.ordering<11)? insert_sort_up : insert_sort_down;
//          var polarity = (clg.order.ordering>=5 && clg.order.ordering<11)? 1 : -1;
////          var sort_func0 = func_lvl? function(v){return func_lvl(get_tgt_th(v))|0;} : function(){return 0;};
////          var sort_func1 = function(v){return get_date(get_tgt_th(v)[8]);};
//          var sort_func = function(a,b){return (b[0] - a[0]) || (a[1] - b[1])*polarity || a[2] - b[2];};
          var sort_key = function(v,i){return [func_lvl(get_tgt_th(v))|0, get_date(get_tgt_th(v)[8]), i, v];};
          if (rm_dic) idxs = idxs.filter(function(v){return !(v in rm_dic);}); // retains old idxs to use it later for making opt index of modified part.
          if  (names) {
            var offset = (!pref.test_mode['171'] || clg.order.ordering%4>=2)? idxs.length : -names.length; // takes 2,3,6,7,10,11
            rm_dic = names.reduce(function(a,c,i){a[c] = i+offset; return a;}, {});
          }
          var idxs_key = idxs.map((names)? function(v,i){var idx = rm_dic[v]; return sort_key(v,idx? (rm_dic[v]=i,idx) : i);} : sort_key); // keep order as much as possible
          return (!names? idxs_key : offset<0? names.reduce(function(a,c){var idx = rm_dic[c]; if (idx<0)       a[a.length] = sort_key(c,idx); return a;},[]).concat(idxs_key)
                                             : names.reduce(function(a,c){var idx = rm_dic[c]; if (idx>=offset) a[a.length] = sort_key(c,idx); return a;}, idxs_key)
                 ).sort(sort_func).map(function(v){return v[3];});
//          if  (names) filterInPlace(idxs, names, rm_dic);
//          return (!names? idxs : (!pref.test_mode['171'] || clg.order.ordering>=6)? idxs.concat(names) : names.concat(idxs)).map(function(v,i){return [sort_func0(v), sort_func1(v), i, v];}).sort(sort_func).map(function(v){return v[3];});
////          if  (names) {
////            if (!rm_dic) rm_dic = names.reduce(function(a,c){a[c] = null; return a;},{});
////            var idxs_removed = idxs.filter(function(v){return !(v in rm_dic);});
////          }
////          return (!names? idxs : (!pref.test_mode['171'] || clg.order.ordering>=6)? idxs_removed.concat(names) : names.concat(idxs_removed)).map(function(v,i){return [sort_func0(v), sort_func1(v), i, v];}).sort(sort_func).map(function(v){return v[3];});
        }
        function check_bulk(idxs_bulk, idxs, clg, out_of_order, idxs_before, names, almost_sorted, from_merged, rm_dic){
          var ref = insert_sort(idxs_before.slice(), names, clg, rm_dic);
          if (idxs && idxs_bulk.length!==idxs.length) console.log('ERROR: check_bulk: length: bulk:'+idxs_bulk.length+', single:'+idxs.length);
          if (idxs_bulk.length!==ref.length) console.log('ERROR: check_bulk: length: bulk:'+idxs_bulk.length+', ref:'+ref.length);
//          var ref = idxs.slice().sort((a,b)=>((func_lvl(clg.threads[b])|0) - (func_lvl(clg.threads[a])|0) || get_date(clg.threads[a][8])-get_date(clg.threads[b][8]))*polarity);
          var count = 0;
          for (var i=0;i<idxs_bulk.length;i++) if (idxs && idxs_bulk[i]!==idxs[i] && (!out_of_order || get_date(clg.threads[idxs_bulk[i]][8])!=get_date(clg.threads[idxs[i]][8])) ||
                                                           idxs_bulk[i]!==ref[i]) //   &&                   get_date(clg.threads[idxs_bulk[i]][8])!=get_date(clg.threads[ref[i]][8]))
            if (count++<10) console.log('ERROR: idx_reorder_exe: '+i+', bulk:'+idxs_bulk[i]+': '+get_date(clg.threads[idxs_bulk[i]][8])+
                                    (idxs && idxs_bulk[i]!==idxs[i]? ', single:'+idxs[i]+': '+get_date(clg.threads[idxs[i]][8]) : '')+
                                                                     ', ref:'+ref[i]+': '+get_date(clg.threads[ref[i]][8])) + ', '+idxs_bulk.length;
          if (count>10) console.log('Bailed out, too many errors. '+count+'/'+idxs_bulk.length);
          if (count>0) insert_bulk(names, {idx:idxs_before, __proto__:clg}, almost_sorted, from_merged, rm_dic,    true); // for debug diving
        }
        function dic_null(a,c){a[c]=null;return a;}
        return {
          idx_raise_odl: function(th){
            var i = this.idxs.indexOf(th.key);
            if (i!=-1) {
              var kwd_odldb = kwd_odl + th.domain + th.board;
              while (++i<this.idxs.length) {
                if (this.idxs[i].indexOf(kwd_odldb)===0) {
                  this.threads_odl[this.idxs[i]] = {'8':[ (th.time_posted || th.time_bumped), th.time_created, th.nof_posts, th.nof_files, (th.time_bumped || th.time_posted) ],
                                                 __proto__:threads_odl_default};
                  insert(this.idxs.splice(i,1)[0], this);
                  break;
            }}}
          },
          idx_delete_odl: function(keys){
            for (var i=0;i<keys.length;i++) delete this.threads_odl[kwd_odl+keys[i]];
          },
          idx_insert_odl: function(name){
            delete this.threads_odl[kwd_odl+name];
            this.idx_reorder(kwd_odl+name);
          },
//          clear: clear, // NEED TO BE IMPLEMENTED to clear 'threads_odl' to prevent memory from leaking.
          idx_re_sort: function(start_pos, small_diff){
            var odls = this.idxs.filter(function(v){return v[0]===':';});
//            if (pref.test_mode['169']) var idxs_bulk = this.idxs.slice();
            var idxs = this.idxs;
            this.idxs = [];
            var hide_tgts = {};
            var hide_unwatched = this.pref[this.mode].hide_unwatched
//            if (pref.test_mode['168'] || pref.test_mode['169']) {
//              if (small_diff) for (var i=idxs.length-1;i>=0;i--) {if (idxs[i][0]!==':') this.idx_re_sort_sub(hide_unwatched, idxs[i], hide_tgts);}
//              else for (var i in this.threads) this.idx_re_sort_sub(hide_unwatched, i, hide_tgts);
//            }
//            if (!pref.test_mode['168']) {
//              if (pref.test_mode['169']) idxs = idxs_bulk;
            if (!small_diff) idxs = Object.keys(this.threads);
            else filterInPlace(idxs, odls);
            if (hide_unwatched) {
              var hide_tgts = idxs.filter(function(v){return !this.threads[v][16].lth.watched_p;}, this);
              filterInPlace(idxs, hide_tgts);
            }
////              for (var i=idxs.length-1;i>=0;i--) {
////                var name = idxs[i];
////                if (name[0]===':') {odls[odls.length] = name; idxs.splice(i,1);}
////                else if (hide_unwatched && !this.threads[name][16].lth.watched_p) {hide_tgts[name] = this.threads[name]; idxs.splice(i,1);}
////              }
            if (idxs.length>0) this.idxs = insert_bulk(idxs, /*pref.test_mode['169']? {idxs:idxs_bulk, idxs_single:this.idxs, loose_check:true, __proto__:this} :*/
                                                             this, small_diff || pref.test_mode['173'] || !pref.test_mode['176'], null, true);
//            }
            if (hide_unwatched) this.func_hide_all(hide_tgts);
            this.idxs = this.idxs.concat(odls);
            this.show_catalog(start_pos || 0);
          },
//          idx_re_sort_sub: function(hide_unwatched, name, hide_tgts){ // working code
//            if (!hide_unwatched || this.threads[name][16].lth.watched_p) insert(name, this);
//            else hide_tgts[name] = this.threads[name];
//          },
          idx_rm_reorder_exe: function(){
//            if (pref.test_mode['168']) return;
            if (this.idxs_req.size===0) return; //  && !this.idxs_rm_queue) return;
            if (pref.test_mode['173']) var idxs_old = this.idxs; // .slice(0, this.drawn_idx===true? undefined : this.drawn_idx); // this.idxs is NOT changed, sort will be executed in a shallow copied array.
            var names_unique = [];
            var created_dic = {};
            var names_dic = Object.create(created_dic);
            var rm_dic = Object.create(pref.test_mode['173']? null : names_dic);
            var idxs_req = this.idxs_req;
            for (var i of idxs_req.keys()) { // too slow, overspec.
              var val = idxs_req.get(i);
              if (val>0) {
                names_unique[names_unique.length] = i;
                if (val>1) created_dic[i] = null;
                else names_dic[i] = null;
              } else rm_dic[i] = null;
            }
////            for (var i of this.idxs_req.keys()) names_unique[names_unique.length] = i;
////            var names_dic = names_unique.reduce(dic_null,{});
////            var names_unique = (this.dirty&0x08)? Object.keys(names_dic) : reqs; // this.idxs_req;
////            var rm_dic_base = Object.create(pref.test_mode['173']? null : names_dic);
////            var rm_dic = this.idxs_rm_queue? this.idxs_rm_queue.reduce(dic_null, rm_dic_base) : rm_dic_base;
//            if (pref.debug_mode['39']) {
//              var errors = [];
//              for (var i=0;i<this.idxs.length;i++) {
//                var name = this.idxs[i];
//                if (!(name in this.threads) && (!(name in rm_dic) || (name in names_dic))) errors.push(name);
//              }
//              if (errors.length>0) {
//                console.log('ERROR: idx_rm_reorder_exe: ',errors);
//                console.trace();
//                console.log(rm_dic, names_dic, names_unique, this.idxs_req);
//              }
//            }
            this.idxs_req.clear(); //  = [];
            if (names_unique.length===0) var idx_top = filterInPlace(/*pref.test_mode['169']? this.idxs_bulk :*/ this.idxs, null, rm_dic);
            else var idxs = insert_bulk(names_unique, /*pref.test_mode['169']? {idxs: this.idxs_bulk, idxs_single:this.idxs, __proto__:this} :*/ this, false, false, rm_dic);
////            this.idxs_rm_queue = null;
//            if (pref.test_mode['169']) this.idxs_bulk = null;
            if (names_unique.length===0) return [idx_top, null];
            /*if (!pref.test_mode['169'])*/ this.idxs = idxs;
            if (!pref.test_mode['173']) return [retval_first_idx, names_dic, created_dic];
            var old_clean = this.drawn_idx===true? idxs_old.length : this.drawn_idx;
            var has_posts = this.view==='page' || this.view==='thread';
            for (var i=0;i<old_clean;i++) if (idxs_old[i]!==idxs[i] || has_posts && (idxs[i] in rm_dic)) break;
            return [i, names_dic, created_dic];
////            if ((idx_new !== idx_old || (this.view==='page' || this.view==='thread')) // this line can't be implemented.
////              && (this.drawn_idx===true || idx_new < this.drawn_idx || idx_old!=-1 && idx_old < this.drawn_idx)) this.drawn_idx = 0;
          },
          idx_reorder: function(name, new_th, bulk){
            if (this.pref[this.mode].hide_unwatched && !this.threads[name][16].lth.watched_p) return;
//            if (!pref.test_mode['168']) {
//              if (pref.test_mode['169'] && !this.idxs_bulk) this.idxs_bulk = this.idxs.slice();
            var val_old = this.idxs_req.get(name);
            if (val_old!==undefined) this.idxs_req.delete(name); // keep order
            this.idxs_req.set(name, val_old | (new_th? 3:1));
////              if (this.idxs_req.has(name)) this.idxs_req.delete(name); // keep order
////              this.idxs_req.set(name, 1);
////              this.idxs_req[this.idxs_req.length] = name;
            this.dirty |= 0x02; // !bulk? 0x0a : 0x02;
//              if (!pref.test_mode['169'] /*|| this.idxs_rm_queue*/) return this.idxs.length; // dummy
//            }
//            if (pref.test_mode['168'] || pref.test_mode['169']) {
////              if (this.idxs_rm_queue && this.idxs_rm_queue.indexOf(name)!=-1) return this.idxs.length; // for safe, this line is not hit probably. // this doewn't work, deleted thread is scanned at inserting other live threads.
//              if (pref.test_mode['169'] && !bulk) var idxs_bulk = this.idxs.slice();
////              if (this.pref[this.mode].hide_unwatched && !this.threads[name][16].lth.watched) return this.idxs.length;
////              var idx_old = initial? -1 : idxs.indexOf(name); // causes multiple entries if idx_reorder is called from view_attr_set
//              var idx_old = this.idxs.indexOf(name);
//              if (idx_old!=-1) this.idxs.splice(idx_old,1);
//////              if (this.pref[this.mode].hide_unwatched && !this.threads[name][16].lth.watched) return this.idxs.length;
//              var idx_new = insert(name, this);
//              if ((idx_new !== idx_old || (this.view==='page' || this.view==='thread'))
//                && (this.drawn_idx===true || idx_new < this.drawn_idx || idx_old!=-1 && idx_old < this.drawn_idx)) this.drawn_idx = 0;
////                && (this.drawn_idx===true || idx_new < this.drawn_idx || idx_old < this.drawn_idx)) this.drawn_idx = 0;
//////              if ((idx_new !== idx_old || showing_posts) && (idx_new < this.drawn_idx || idx_old < this.drawn_idx)) this.drawn_idx = 0;
//////              else if (showing_posts && this.drawn_idx===true) this.drawn_idx = 0;
////  //            else if (this.mode==='page' && this.drawn_idx===true) this.drawn_idx = idx_new; // BUG??? this doesn't track drawn_y and ref_count.
////  //            if (idx_new !== idx_old && (idx_new < this.drawn_idx || idx_old < this.drawn_idx)) this.drawn_idx = 0; // BUG. doesn't redraw if its size is changed.
//            }
//            if (!pref.test_mode['168'] && !bulk && pref.test_mode['169']) {
//              var clg_tmp = {idxs:idxs_bulk, idxs_single:this.idxs, __proto__:this};
//              insert_bulk([name], clg_tmp);
//              if (retval_first_idx!==undefined && retval_first_idx!==idx_new && retval_first_idx!==idx_old && clg_tmp.idxs.indexOf(name)!==idx_new) console.log('ERROR: idx_reorder: bulk:'+retval_first_idx+', single:'+idx_new+', '+idx_old);
//            }
//            return idx_new;
////            return (idx_new !==idx_old || (this.threads[name] && this.threads[name][9][0]!=this.threads[name][1])); // returns need to redraw.
          },
          idx_remove: function(name){
//            if (pref.test_mode['169'] && !this.idxs_bulk) this.idxs_bulk = this.idxs.slice(); // working code
//            if (pref.test_mode['168'] || pref.test_mode['169'] || pref.test_mode['181']) {
//              var idx = this.idxs.indexOf(name);
//              if (idx>=0) this.idxs.splice(idx,1);
//              if (pref.test_mode['168'] || pref.test_mode['181']) return;
//            }
            if (this.idxs_req.has(name)) this.idxs_req.delete(name); // keep order, but redundant.
            this.idxs_req.set(name,0);
            this.dirty |= 0x04;
//            if (this.idxs_rm_queue) this.idxs_rm_queue.push(name);
//            else {
//              this.idxs_rm_queue = [name];
//              this.dirty |= 0x04;
//            }
          },
//          idx_remove_exe: function(){
//            var idx_top = filterInPlace(pref.test_mode['169']? this.idxs_bulk : this.idxs, this.idxs_rm_queue);
//            this.idxs_rm_queue = null;
//            if (pref.test_mode['169']) this.idxs_bulk = null;
//            return [idx_top, null];
//          },
          idx_refresh: function(clg){
            var start_pos = this.pref.INST.safety.hide? -1 : 0;
            if (this.order.ordering%6===5) {
              if (this.dirty&0x06) {this.idx_rm_reorder_exe(); this.dirty&=~0x06;} // this can be consolidated by using insert_sort, but insert_bulk_all is 5x faster than insert_sort, so this serial sorting may be faster than consolidated. // patch for clear_threads from embed_exe, the bug is hit when open other catalog(pref.catalog_board_list_sel!==0)
              this.idx_re_sort(start_pos, true); // causes extra redraw, consider how to reduce this....
            } else if (clg===this) if (start_pos) this.show_catalog(start_pos);
          },
          idx_idxMergeAll: 7, // /Creation
//          idx_insert: insert, // for merge
          idx_insert_bulk: insert_bulk, // for merge
        };
      })());
      

      function expand_shrink_thread(name){
        if (threads[name][0].style.width=='') {
          if (pref.catalog_expand_with_hr && embed_mode!=='catalog') show_catalog_hr(name,'shrink');
          threads[name][0].style.width = pref.float.format.fc.width + 'px';
          threads[name][0].style.height = pref.float.format.fc.height + 'px';
        } else {
          threads[name][0].style.width = '';
          threads[name][0].style.height = '';
          if (pref.catalog_expand_with_hr && embed_mode!=='catalog') show_catalog_hr(name,'add');
        }
      }

      var DIH = (function(){ // Dynamic Image Handler
        var hover_ex = null;
        var hover_pf = null;
        var hover_ex_mousemoved = false;
        var image_expanding = [];
        function hover_prep(tn, src, prefetch, e){
          var img_ex = clone_img(tn, src, 'hover', null, e);
          if (!img_ex) return;
          img_ex.style.position = 'fixed';
          img_ex.style.top = '0px';
          img_ex.style.right = '0px';
          if (prefetch) img_ex.style.display = 'none';
          else img_ex.style.zIndex = pref[embed_mode].thumbnail.hover.popup_zIndex;
          img_ex.style.pointerEvents = 'none';
  //        if (pref.test_mode['69']) archiver.test_dl(img_ex.src);
          var hover_obj = {pn:img_ex, tn:tn, loaded:false};
          img_ex.draggable = true;
          img_ex.ondragstart = zoom_dragstart;
//          img_ex.ondragend = zoom_dragend;
          img_ex.onclick = zoom_inout;
          img_ex.onmousemove = hover_ex_mousemove;
          var pf = pref[embed_mode].thumbnail.hover;
          if (pf.zoom_dblC) img_ex.ondblclick = image_hover_remove;
          if (pf.zoom_mW) img_ex['on'+brwsr.mousewheel] = cnst.div_scroll;
          img_ex.onload = hover_onload.bind(hover_obj); // for racing of onload
          img_ex.onerror = httpd.pause_cancel;
          httpd.pause_req();
          site.script_body.appendChild(img_ex);
          return hover_obj;
        }
        function hover_ex_mousemove(e){
          if (e.movementX!==0 || e.movementY!==0) hover_ex_mousemoved = true; // e.movement* for zoom-in which generates mousemove event always.
        }
        function hover_onload(e){
          httpd.pause_cancel();
          if (hover_ex && e.target===hover_ex.pn) format_hover_ex();
          else this.loaded = true;
        }
//        function get_mode(e){
//          return e.currentTarget===site.popup_body? 'page' : embed_mode;
//        }
        function image_hover_add(e,src){
if (pref.test_mode['163'] && pref[embed_mode].thumbnail.inline.stopHover && site.nickname==='4chan' && e.target.classList.contains('expanded-thumb')) return;
          var tn = e.target; //  : this; // e.currentTarget;
          if (pref[embed_mode].image_hover) {
            if (hover_ex && hover_ex.tn===tn && pref[embed_mode].thumbnail['hover'].zoom_over) return; // reentry from hover_zoom
            var hover_ex_old = hover_ex;
            if (hover_pf && hover_pf.tn===tn) { // snatches the prefetched one
              hover_ex = hover_pf;
              hover_ex.pn.style.display = '';
              hover_ex.pn.style.zIndex = pref[embed_mode].thumbnail.hover.popup_zIndex; // refrects the newest value
              if (hover_ex.loaded) format_hover_ex();
            } else {
              hover_ex = hover_prep(tn, src, null, e);
              if (!hover_ex) return;
              if (hover_pf) hover_pf.pn.remove();;
            }
            e.preventDefault();
            if (hover_ex_old) hover_ex_old.pn.remove(); // placed after show to reduce redraw.
            hover_ex_mousemoved = false;
          }
          if (pref[embed_mode].image_prefetch) {
            var img_next = site2[site.nickname].get_next_image(tn);
            hover_pf = img_next? hover_prep(img_next, null, true) : null;
          } else hover_pf = null;
        }
//        function image_hover_remove_tn(e){
//          hover_tn.removeEventListener('mouseout', image_hover_remove_tn, false);
//          image_hover_remove();
//        }
        function image_hover_remove(e){
          if (e.type!=='dblclick') if (e && hover_ex && (e.target===hover_ex.pn || e.target===hover_ex.tn)) return; // for zoom
          if (hover_ex) hover_ex.pn.remove();
          hover_ex = null;
        }
        function format_hover_ex(){ // must allow multiple entry for expanded image
          var pf = pref[embed_mode].thumbnail['hover'];
          var pn = hover_ex.pn;
          if (zoom_required(pn)) {
            pn.style.cursor = 'zoom-in';
            if (pref[embed_mode].thumbnail['hover'].zoom_over) pn.style.pointerEvents = null;
//            pn.onclick = image_hover_zoom_in;
//            if (pf.zoom_over || pf.zoom_click) pn.onmouseover = zoom_entered;
            var tn = hover_ex.tn;
            tn.style.cursor = 'zoom-in';
            tn.addEventListener('mouseout', hover_ex_tn_out, false);
//            tn.addEventListener('mousemove', tn_mousemove, false);
            tn.addEventListener('click', zoom_start, false);
//            zoom = {x:document.documentElement.clientWidth - img.clientWidth, y:img.clientHeight, state:null};
////            zoom = {x:document.documentElement.clientWidth - parseInt(s.maxWidth,10), y:parseInt(s.maxHeight,10), state:null};
          }
        }
        function zoom_required(img){
          var pf = pref[embed_mode].thumbnail['hover'];
//          if (check && !pf.zoom) return false;
          var s = img.style;
          return pf.limit_width && (img.naturalWidth > parseInt(s.maxWidth,10)) || pf.limit_height && (img.naturalHeight > parseInt(s.maxHeight,10));
        }
        function zoom_start(e){
          if (pref[embed_mode].thumbnail['hover'].zoom_click) {
            zoom_inout({target:hover_ex.pn, clientX:document.documentElement.clientWidth, clientY:0});
            hover_ex.pn.style.pointerEvents = null;
            e.preventDefault();
            e.stopPropagation();
          }
//          if (zoom_included(e)) {
//            e.stopPropagation();
//            e.preventDefault();
//            image_hover_zoom_end(e);
////            e.currentTarget.removeEventListener('mouseout', image_hover_remove_tn, false);
//            hover_ex.onmouseout = image_hover_remove;
//            image_hover_zoom_in({target:hover_ex, clientX:e.clientX, clientY:e.clientY});
//            image_hover_draggable(hover_ex);
//          }
        }
//        function zoom_included(e){
//          return e.clientX>zoom.x && e.clientY<zoom.y;
//        }
//        function image_hover_zoom_prep(e){
//          if (zoom_included(e)) {
//            if (!zoom.state) {
//              e.target.style.cursor = 'zoom-in';
//              zoom.state = true;
//            }
//          } else if (zoom.state) {
//            e.target.style.cursor = 'auto';
//            zoom.state = false;
//          }
//        }
        function hover_ex_tn_out(e){
          var tn = e.currentTarget;
          tn.removeEventListener('mouseout', hover_ex_tn_out, false);
//          tn.removeEventListener('mousemove', tn_mousemove, false);
          tn.removeEventListener('click', zoom_start, false);
          tn.style.cursor = null;
        }
//        function zoom_entered(e){
//          hover_ex.pn.onmouseover = null;
////          hover_ex.pn.onmouseout = zoom_left;
//          hover_ex.pn.draggable = true;
//          hover_ex.pn.ondragstart = zoom_dragstart;
//          hover_ex.pn.ondragend = zoom_dragend;
//        }
//        function zoom_left(e){ // recycling hover image here
//          if (e && e.relatedTarget===hover_zoom.tn && pref[embed_mode].thumbnail['hover'].zoom_over) {
//            hover_ex = hover_zoom;
//            if (hover_ex.loaded) format_hover_ex();
//          } else hover_zoom.pn.remove();
//          hover_zoom = null;
//        }
//        function image_hover_draggable(pn){
//          pn.style.pointerEvents = 'auto';
//          pn.draggable = true;
//          pn.ondragstart = cnst.div_dragstart;
//          pn['on'+brwsr.mousewheel] = cnst.div_scroll;
//        }
        function maxWidth(inline_or_hover){
          return document.documentElement.clientWidth  - pref[embed_mode].thumbnail[inline_or_hover].margin_width;
        }
        function maxHeight(inline_or_hover){
          return document.documentElement.clientHeight - pref[embed_mode].thumbnail[inline_or_hover].margin_height;
        }
        function zoom_factor(img){
          var fx = img.naturalWidth/(parseInt(img.style.maxWidth,10) || maxWidth('hover'));
          var fy = img.naturalHeight/(parseInt(img.style.maxHeight,10) || maxHeight('hover'));
          return (fx>fy)? fx : fy;
        }
        function zoom_in_place(e, s, f, force_zero){
          if (!s.top) {
            s = e.target.parentNode.parentNode.style;
            var oT = e.target.offsetTop;
            var oR = e.target.offsetLeft; // e.target.parentNode.parentNode.offsetWidth - e.target.offsetWidth - e.target.offsetLeft; // PATCH
          }
          s.top = (force_zero? 0 : parseInt(s.top,10)*f + ((oT||0)-e.clientY)*(f-1)) + 'px';
          s.right = (force_zero? 0 : parseInt(s.right,10)*f - (document.documentElement.clientWidth - (oR||0) - e.clientX) * (f-1)) + 'px';
        }
        function zoom_inout(e){
          var et = e.target;
          var s = et.style;
          if (s.cursor!=='zoom-in' && s.cursor!=='zoom-out') return;
          var zin = s.cursor==='zoom-in';
          zoom_in_place(e, s, zin? zoom_factor(et) : 1/zoom_factor(et), (hover_ex && hover_ex.pn===et)? !hover_ex_mousemoved : false);
          s.maxWidth  = zin? 'none' : maxWidth('hover')+'px';
          s.maxHeight = zin? 'none' : maxHeight('hover')+'px';
          s.cursor  = zin? 'zoom-out' : 'zoom-in';
        }
        function zoom_dragstart(e){
          gGEH.drag.started(e, null, 'move');
        }
//        function zoom_dragend(e, dx, dy){
//          var pn = e.target;
//          pn.style.right = (parseInt(pn.style.right,10) - dx) + 'px';
//          pn.style.top = (parseInt(pn.style.top,10) + dy) + 'px';
//          e.stopPropagation();
//        }
//        var drag_sx, drag_sy;
//        function zoom_dragstart(e){
//          drag_sx = e.screenX;
//          drag_sy = e.screenY;
//          site.script_body.addEventListener('dragover',zoom_dragover,false);
//        }
//        function zoom_dragover(e){ // http://www.html5rocks.com/ja/tutorials/dnd/basics/
//          e.preventDefault();
//          e.dataTransfer.dropEffect = 'move';
//        }
//        function zoom_dragend(e, sx, sy){
//          if (sx===undefined) site.script_body.removeEventListener('dragover',zoom_dragover,false);
//          var pn = e.target;
//          pn.style.right = parseInt(pn.style.right,10) - (sx!==undefined? sx : e.screenX - drag_sx) + 'px';
//          pn.style.top = parseInt(pn.style.top,10) + (sy!==undefined? sy : e.screenY - drag_sy) + 'px';
//          e.stopPropagation();
//        }
        function image_hover_snatch(e, dx, dy){
          if (!hover_ex || !pref[embed_mode].thumbnail.hover.dragfloat || e.target!==hover_ex.tn) return;
//          zoom_dragend(e, sx, sy);
          var obj = cnst.init3({
            func_str:'right:'+(-dx)+'px:top:'+dy+'px:ftb:overflow:hidden:Show'});
//            func_str:'right:'+right+'px:top:'+top+'px:tb:ftb:overflow:auto:Show'}).cn;
          var pn = obj.cn.appendChild(snatch_hover_ex()); // shatch at the last to reduce redraw
          if (pn.tagName==='VIDEO') pn.play(); // continue playing
////          hover_tn.removeEventListener('mouseout', image_hover_remove_tn, false);
//          if (zoom_required(hover_ex)) {
//            hover_ex.style.cursor = 'zoom-in';
//            hover_ex.onclick = image_hover_zoom_in;
//          }
//          return hover_ex;
//        }
//        function image_hover_snatch_end(){
          var pf = pref[cataLog.embed_mode].thumbnail.hover;
          if (pf.df_dblC) obj.cn.ondblclick = cnst.tb_funcs.evfunc_factory(obj, 'exit');
          if (pf.df_mW) obj.pn['on'+brwsr.mousewheel] = cnst.div_scroll;
        }
        function snatch_hover_ex(){
          var pn = hover_ex.pn;
          var s = pn.style;
          s.position = null;
          s.pointerEvents = null;
          s.zIndex = null;
          s.top = null;
          s.right = null;
          pn.onmouseover = null;
          pn.ondblclick = null;
          pn['on'+brwsr.mousewheel] = null;
          pn.ondragstart = null;
//          pn.ondragend = null;
          pn.onmousemove = null;
          hover_ex = null;
          return pn;
        }

        function clone_img_make_src(img, inline_or_hover, from_initial){
          var name = gGEH.get_key_recursive(img);
          if (!from_initial && pref[embed_mode].env.event_dynamic && inline_or_hover!==undefined &&
            ((inline_or_hover==='inline')? pref[embed_mode].env.expand_thumbnail_inline_native : pref[embed_mode].env.image_hover_native)) return;
  //        &&  comf.fullname2dbt(name)[0]===site.nickname) return;
          if (!name && pref.test_mode['106'] && pref.test_mode['107']) return img.src; // patch for expand all at initial in 4chan, BUT I DON'T KNOW WHY...
          var tgt_domain_html = (pref.catalog.mimic_base_site)? site.nickname : threads[name][16].domain_html;
  //        var tgt_th16 = threads[name][16];
  ////      var src = ((tgt_th16.type_html==='catalog')? tgt_th16.op_img_src_url : img.parentNode.href) || pn.src;
  //        return ((tgt_th16.type_html==='catalog')? // working code
  //          site2[tgt_th16.domain_html].parse_funcs.catalog_html.img2src && site2[tgt_th16.domain_html].parse_funcs.catalog_html.img2src(img) :
  //          site2[tgt_th16.domain_html].parse_funcs.post_html.img2src(img)) || img.src;
          var mode = site2[tgt_domain_html].general_event_handler[site.whereami].image_hover_check_mode(img);
          if (mode!=='catalog') return site2[tgt_domain_html].parse_funcs.post_html.img2src(img) || img.src; // working code, but cause an error at merging when the base is lost.
  //        if (embed_mode!=='catalog') return site2[tgt_th16.domain_html].parse_funcs.post_html.img2src(img) || img.src; // working code, but cause an error at merging when the base is lost.
          else if (site.nickname==='4chan') {
            var domain = site2['DEFAULT'].popups_posts.href2domain(img.src);
            return domain && site2[domain].parse_funcs[mode+'_html'].img2src(img) || img.src; // domain check for banners
          } else {
            if (!name) return;
            var lth = liveTag.mems.getFromName(name);
            return site2[lth.domain].parse_funcs[lth.th.type_parse].get_op_src(lth.th, img);
          }
        }
        function clone_img(img, src, inline_or_hover, from_odl, e){
          var pn;
          var pfih = pref[embed_mode].thumbnail[inline_or_hover];
  //        pn.src = (embed_mode==='page')? img.parentNode.href : pn.src; // TEMPORARILY!!!
          if (!src) src = clone_img_make_src(img, inline_or_hover, from_odl);
          if (!src) return; // return when native dynamic function works.
          var ext = (src.search(/^blob/)===0)? site2['DEFAULT'].parse_funcs.post_html.img2ext(img) : '';
          if (src.indexOf('data-ext')!=-1) { // PATCH for archive
            ext = src.slice(src.indexOf('data-ext')+10).replace(/".*/,'');
            src = src.replace(/".*/,'');
          }
          if (src.slice(-5)==='.webm' || ext==='.webm' || img.getAttribute('data-ext')==='.webm' ||
              src.slice(-4)==='.mp4'  || ext==='.mp4'  || img.getAttribute('data-ext')==='.mp4') {
            if (pfih.webm || pfih.webm_ctrl && e.ctrlKey) {
              if (hover_ex && hover_ex.pn.src===src) pn = snatch_hover_ex();
              else {
                pn = document.createElement('video');
                pn.controls = true;
                pn.autoplay = !from_odl;
                //  <video controls="" loop="" autoplay="" class="expandedWebm" src="xxx.webm" style="max-width: 957px; max-height: 587px;"></video> // 4chan
              }
              pn.loop  = pfih.webm_loop;
              pn.muted = pfih.webm_mute;
            } else return null;
          } else pn = img.cloneNode();
          pn.src = src;
          pn.style.width = 'auto';
          pn.style.height = 'auto';
          if (pfih.limit_width)  pn.style.maxWidth  = maxWidth(inline_or_hover) + 'px';
          if (pfih.limit_height) pn.style.maxHeight = maxHeight(inline_or_hover) + 'px';
          return pn;
        }
        function expand_thumbnail_inline(e, from_odl){
//          var mode = get_mode(e || {currentTarget:this});
          if (pref[embed_mode].expand_thumbnail_inline && image_expanding.indexOf(this)==-1) {
            var img_ex = clone_img(this, null, 'inline', from_odl);
            if (!img_ex) {expand_thumbnail_on_demand_set(this);return;}
  //          if (!img_ex) return;
            if (img_ex.tagName!=='VIDEO') {
              img_ex.style.display = 'none';
              img_ex.addEventListener('load',expand_thumbnail_inline_load, false);
              this.style.opacity = 0.5;
            }
            this.parentNode.insertBefore(img_ex,this.nextSibling);
  //          this.onclick = null; // can't track event accurately because 'on demand expand' mode adds click event to images which have a native expansion event.
            image_expanding.push(img_ex);
            
            if (e) e.preventDefault();
            if (img_ex.tagName==='VIDEO') {
              expand_thumbnail_inline_load.call(img_ex);
              if (!from_odl) img_ex.play(); // may be diverted from image_hover
            }
          }
        }
        function expand_thumbnail_inline_stopHover(e){
          e.stopPropagation();
        }
        function expand_thumbnail_inline_load(){
          this.previousSibling.style.display = 'none';
          this.style.display = '';
          this.previousSibling.style.opacity = '';
          this.removeEventListener('load',expand_thumbnail_inline_load, false);
          this.onclick = shrink_thumbnail_inline;
          if (pref[embed_mode].thumbnail.inline.stopHover) this.onmouseover = expand_thumbnail_inline_stopHover;
          if (site2[site.nickname].format_expanded_thumbnail) site2[site.nickname].format_expanded_thumbnail(this);
          var idx = image_expanding.indexOf(this);
          if (idx>=0) image_expanding.splice(idx,1);
  
          if (site.patch.expand_thumbnail_inline_load) site.patch.expand_thumbnail_inline_load.call(this);
          expand_thumbnail_on_demand_set(this);
        }
        function expand_thumbnail_on_demand_set(tn){
          if (pref[embed_mode].expand_thumbnail_inline && pref[embed_mode].expand_thumbnail_inline_all_after) {
//          if (pref[embed_mode].expand_thumbnail_inline && (pref[embed_mode].expand_thumbnail_initial || pref[embed_mode].expand_thumbnail_inline_all_after)) {
            var img_next = site2[site.nickname].get_next_image(tn,get_now_height()); // now_height for jump scroll
            if (img_next && img_next.style.display!=='none') expand_thumbnail_on_demand(img_next);
          }
        }
        var image_expand_on_demand_instance = null;
        function expand_thumbnail_on_demand(img){
          var ref_height = pClg.get_ref_height(pref[embed_mode].thumbnail.inline.ref_height/100);
          var offsetTop;
          if (Array.isArray(img)) {
            var tgt;
            var nowHeight = get_now_height();
            for (var i=0;i<img.length;i++) {
              var ot = img[i].offsetTop;
              if (ot>=nowHeight && ot<offsetTop || i===0) {
                tgt = img[i];
                offsetTop = ot;
              }
            }
            img = tgt;
          } else offsetTop = img.offsetTop;
          if (offsetTop<ref_height) { //  || !pref[embed_mode].thumbnail.inline.ondemand) {
            image_expand_on_demand_instance = null;
            expand_thumbnail_inline.call(img, null, true);
          } else image_expand_on_demand_instance = img;
        }
//        function expand_thumbnail_queue_add(img){
//          if (pref.test_mode['130']) {expand_thumbnail_on_demand(img); return;}
//          if (!image_expand_on_demand_instance) image_expand_on_demand_instance = img;
//          else if (Array.isArray(image_expand_on_demand_instance)) image_expand_on_demand_instance[image_expand_on_demand_instance.length] = img;
//          else image_expand_on_demand_instance = [image_expand_on_demand_instance, img];
//          show_catalog_cont(); // patch, delayed 200ms
//        }
        function shrink_thumbnail_inline(e){
  //        this.previousSibling.onclick = expand_thumbnail_inline;
          this.previousSibling.style.display = '';
          e.preventDefault();
          e.stopPropagation();
          if (pref[embed_mode].thumbnail.inline.ondemandStop) image_expand_on_demand_instance = null;
          
          if (this.getAttribute('data-originalWidth')) {
            var pnode = this.parentNode.parentNode;
            pnode.style.width = this.getAttribute('data-originalWidth');
          }
          this.parentNode.removeChild(this);
        }
        return {
//          get_mode: function(e){return e.currentTarget===site.popup_body? 'page' : embed_mode;},
          image_hover_add: image_hover_add,
          image_hover_remove: image_hover_remove,
          image_hover_reentry: function(img){
//            if (!mode) mode = embed_mode; // TEMPORAL PATCH. all reentry func in GIH must be revised.
            if (hover_ex && hover_ex.pn.src===img.src) hover_ex.pn.src = clone_img_make_src(img);
          },
          image_hover_snatch: image_hover_snatch,
//          image_hover_snatch_end: image_hover_snatch_end,
//          get hover_ex(){return hover_ex && hover_ex.pn;},
          expand_thumbnail_inline: expand_thumbnail_inline,
          expand_thumbnail_on_demand_kick: function(){
            if (image_expand_on_demand_instance) expand_thumbnail_on_demand(image_expand_on_demand_instance);
          },
//          expand_thumbnail_on_demand: expand_thumbnail_on_demand,
////          expand_thumbnail_queue_add: expand_thumbnail_queue_add,
          fetch_and_reentry: function(tgts, img){
            scan.scan_ui('image_hover', {tgts: tgts, options:{callback:function(){cataLog.DIH.image_hover_reentry(img);}, priority:6}});
          },
          expand_thumbnail_clicked_but_ineffective: function(tn){
            if (pref[embed_mode].expand_thumbnail_inline_all_after) {
              if (site.nickname==='4chan' && tn.classList.contains('expanded-thumb')) {
                if (pref[embed_mode].thumbnail.inline.ondemandStop) image_expand_on_demand_instance = null;
              } else expand_thumbnail_on_demand_set(tn);
            }
          },
//          expand_thumbnail_on_demand_set: expand_thumbnail_on_demand_set,
        };
      })();
//      function image_hover_add(e,src){
//        DIH.image_hover_add.call(this,e,src);
//      }
      cataLog.DIH = DIH;
//      cataLog.image_hover_add = image_hover_add;
      cataLog.image_hover_reentry = DIH.image_hover_reentry;

      cataLog.InfoPv = (function(){
        var pn = document.createElement('div');
        pn.setAttribute('class',pref.cpfx+'infoPv');
        pn.style.position = 'absolute';
        function fmt(val, str, check){
          var v = parseInt(val,10);
          return (!check || val!=='00')? (check? ' and ':'') + v + ' ' + str + (v!=1? 's':'') : '';
        }
        function rel_time(rt){
          var strs = Footer.format_relative_time(rt).split(/:|d/);
          var s0 = strs[0].slice(1);
          return (strs[0][0]=='+')? 'Just now' : (strs[0] = strs.slice(1), 
            (strs.length===1)? fmt(s0,'second')
            : (strs.length===2)? fmt(s0,'munute') // +fmt(strs[1],'second', true)
            : (strs.length===3)? fmt(s0,'hour')+fmt(strs[1],'minute', true)
            :                    fmt(s0,'day')+fmt(strs[1],'hour', true));
        }
        var shown = false;
        return {
          show: function(e){
            var name = gGEH.get_key_recursive(e.target);
            var rect = e.target.getBoundingClientRect();
            var cW = document.documentElement.clientWidth;
            var left = e.clientX - cW*0.5 <0; 
            pn.style.left  = (left)? rect.right + 5 + 'px' : null;
            pn.style.right = (left)? null : cW - rect.left + 5 + 'px';
            pn.style.top = rect.top + window.scrollY + (pref.test_mode['143']? 100 : 0) + 'px';
            var th = liveTag.mems.getFromName(name).th;
            var last_no = th.posts.length>=2 || th.last_no;
            var last_name = th.posts.length>=2 && th.posts[th.posts.length-1].name || th.last_name;
//          show: function(top, left, sub, name_op, time_created, page, name_posted, time_posted){
            var now = Date.now();
            pn.innerHTML = (th.sub? '<span class="post-subject">'+th.sub+'</span>' : 'Posted')+' by <span class="post-author">'+th.name+'</span> '+(th.country?'<div class="flag flag-'+th.country.toLowerCase()+'"></div> ':'')+'<span class="post-ago">'+rel_time(th.time_created - now)+' ago</span><span class="post-page">Page '+gClg.AllThreads[name][14]+'</span>'+(last_no?'<div class="post-last">Last reply by <span class="post-author">'+last_name+'</span> <span class="post-ago">'+rel_time(th.time_posted - now)+' ago</span></div>':'');
            if (!shown) site.script_body.appendChild(pn);
            shown = true;
          },
          hide: function(){
            if (!shown) return;
            site.script_body.removeChild(pn);
            shown = false;
          }
        };
      })();

//      function get_name_recursive(pn){
//        while (pn && !pn.name) pn = pn.parentNode;
//        return pn && pn.name;
//      }
//      function click_thread_tn(e){ // working code
//        if (pref[embed_mode].click==='none') return;
//        e.preventDefault();
//        if (pref[embed_mode].click_area==='entire') return;
//        click_thread(get_name_recursive(e.target), (e.target.classList.contains(pref.cpfx+'link'))? e.target.getAttribute('href') : null);
//      }
//      function click_thread_whole(){
//        if (pref[embed_mode].click_area==='entire') click_thread(this.name);
//      }
      Clg.prototype.click_thread = function(name, url){
        if (url) open_new_thread((url==='force')? null : url, name, this);
        else if (pref[this.mode].click==='open') open_new_thread(null, name, this);
        else if (threads[name][18]!=='catalog' && pref[embed_mode].click==='expand') expand_shrink_thread(name);
      }
      function click_link(e){
        e.preventDefault();
        if (e.target.href) open_new_thread(e.target.href, gGEH.get_key_recursive(e.target));
      }
//      function click_thread(name){
//        if (typeof(name)==='object') name= this.name;
//        var name = this.name;
//        if (pref.click=='expand') expand_shrink_thread(name);
//        else open_new_thread(threads[name][7], name);
//      }

      function add_open_new_thread_event(name,args){
        for (var i=0;i<args.length;i++) {
          var elem = args[i][0];
          var url  = args[i][1];
          var func = function(){open_new_thread(url, name);if (pref[embed_mode].click==='expand') expand_shrink_thread(name);};
          elem.addEventListener('click', func, false);
          args[i][1] = func;
        }
        return args;
      }
      function remove_open_new_thread_event(args){
        for (var i=0;i<args.length;i++) args[i][0].removeEventListener('click', args[i][1], false);
        return null;
      }
      Clg.prototype.get_mark_time = function(name, extra_format){
        var pf = this.pref.filter;
        var val = pref_func.merge_obj5(name,pf.watch_list_obj2,null);
        if (val && ('time' in val)) return (extra_format)? -val.time-1 : val.time;
        var val2 = pref_func.merge_obj5(name,pf.list_obj2,null);
        if (val2) return val2.time || 0; 
//          date_mark = pref_func.merge_obj5(name,pref.filter.list_obj2,{hit:false});
//          if ('time' in date_mark) return date_mark.time;
//          else if (date_mark.hit) return 0;
        return pf.time.w? pf.time.time_val : 0; // Date.parse(pf.time_str);
      };
      function open_new_thread(url, name, clg){
        if (!clg) clg = pClg;
        var threads = clg.threads;
        var dbt = comf.fullname2dbt(name);
        if (threads[name][16].archiveFile) url = site2[(pref.archive.open_local)? site.nickname : dbt[0]].make_url4([site.nickname, site.board, '0','page_html'])[0];
//        if (threads[name][16].archiveFile) url = site2[dbt[0]].make_url4(comf.name2dbt(name).slice(0,2).concat(['0','page_html']))[0];
        else {
          if (url===null) {
            url = site2[dbt[0]].make_url4(comf.name2dbt(name))[0]; // temporal
            if (pref.catalog_open_last50!=='no') {
              var short_link = document.createElement('div');
              short_link.innerHTML = site2[dbt[0]].short_link(name, threads[name][8][2], null, '', '');
              var as = short_link.getElementsByTagName('a');
              for (var i=0;i<as.length;i++) {
                if (pref.catalog_open_last50==='exist' || !(threads[name][19][0]&0x000c0000) || (parseInt(as[i].textContent,10)>(threads[name][19][1]&0x0000ffff))) {
                  url = as[i].getAttribute('href');
                  break;
          }}}}
        }
//////////        if (url===null) url = site2[threads[name][16].dbt[0]].make_url3(threads[name][16].dbt[1], threads[name][16].dbt[2], '0'); // temporal // working code.
////////        if (url===null) url = site2[threads[name][16].dbt[0]].make_url4(comf.name2dbt(name))[0]; // temporal
////////        if (typeof(url)==='string') url = [url];
////////        var idx = 0;
////////        var unread50 = (threads[name][19][0]!==0 && threads[name][19][2]<=50);
////////        if      (pref.catalog_open_last50==='no')    idx = 0;
////////        else if (pref.catalog_open_last50==='exist') idx = url.length-1;
////////        else if (pref.catalog_open_last50==='exist_watch') idx = (unread50)? url.length-1 : 0;
////////        else {
////////          var dbt = comf.fullname2dbt(name); // patch
////////if (dbt[0]==='8chan' || dbt[0]==='lain') { // patch.
////////          if (url.length==1 && threads[name][8][2]>100) url[1] = url[0].replace(/.html/,'+50.html');
////////          if (pref.catalog_open_last50==='speculative') idx = (threads[name][8][2]>100)? url.length-1 : 0;
////////          else if (pref.catalog_open_last50==='spec_watch') idx = (unread50)? url.length-1 : 0;
////////}
////////        }
////////        url = url[idx];

//console.log(url);
//        var cw = window.open(url,(pref.catalog_open_in_new_tab)? '_blank' : '_self');
        if (pref.tabID===0) pref.tabID = Date.now();
        var window_name = ((site.embed_frame_win)? site.embed_frame
                           : (pref.catalog_open_where==='named')? name + ((threads[name][16].archiveFile)? 'A' : '')
                           : (pref.catalog_open_where==='_blank')? name + '_' + Date.now() : pref.catalog_open_where) + (pref.catalog_open_where!=='_self'? '_'+pref.tabID : '');
//                        : (pref.catalog_open_in_new_tab)? ((pref.catalog_use_named_window)? name : '_blank') : '_self';

        var names = pref['thread'].merge_auto? clg.merge_bases.query_merged_keys(name) : [name];
        var time_marked = (pref[embed_mode].mark_new_posts)? names.map(function(name){return clg.get_watch_time_of_a_thread(name,threads[name][8][1], null);}).reduce(function(a,c){return c>a? c : a;}) : 0;
//        var time_marked = (pref[embed_mode].mark_new_posts)? clg.get_watch_time_of_a_thread(name,threads[name][8][1], null, true) : 0; // working code.
//        if (time_marked && pref['thread'].merge_auto) {
//          var merged_others = clg.merge_bases.query_others(name);
//          if (merged_others) for (var i=0;i<merged_others.length;i++) {
//            var time_marked_tmp = clg.get_watch_time_of_a_thread(merged_others[i],threads[merged_others[i]][8][1], null, true);
//            if (time_marked_tmp && time_marked<time_marked_tmp) time_marked = time_marked_tmp;
//          }
//        }
        var message = ['MARK',time_marked];
        if (!site2[site.nickname].historyAPI || !site2[dbt[0]].historyAPI || !pref.pref2.meguca.historyAPI) {
          var cw = window.open(url,window_name);
          send_message(window_name, message,cw);
          if (pref.uip_tracker.on) {
            var lth = liveTag.mems.getFromName(name);
            if (pref.uip_tracker.sage.annotate) {
              if (pref.uip_tracker.sage.annotate_page && lth.pgh) send_message(window_name, ['UIP', ['page',JSON.stringify(lth.pgh)]]);
              if (lth.sg && lth.sg.size) send_message(window_name, ['UIP', ['sage'].concat(Array.from(lth.sg))]);
            }
            if (pref.uip_tracker.annotate) if (lth.sID && lth.sID.h) send_message(window_name, ['UIP',lth.sID.h]);
          }
          if (threads[name][16].archiveFile) {
            var file = threads[name][16].archiveFile;
            send_message(window_name, ['ARCHIVER',['SUB_INIT',(file==='IDB')? {IDB:true, domain:dbt[0], board:(pref.test_mode['80'])? dbt[1].slice(0,-5)+'/' : dbt[1], no:dbt[2]} : // cut '_IDB'
                                                                              {name:file.name, size:file.size, lastModified:file.lastModified, files_sel:pref.archive.files_sel} ]]);
          }
        } else {
          if (site2[site.nickname].historyAPI_blocking) return; // patch for meguca, killing click, but this is dangerous.
          send_message_emu.push(message);
        }
        var pf_aw = pref.auto_watch;
        if (pf_aw.watch) clg.triage_event(name,'WATCH',''); // ,true);
        if (pf_aw.hide || pf_aw.style) clg.triage_event(name, pf_aw.hide? pf_aw.hide_com : 'ATTR', pf_aw.style? pf_aw.style_str : '');
        if (pf_aw.triage) {
          var triages = pf_aw.triage_str.split(',');
          while (triages.length>0) {
            if (triages[0]) clg.triage_event(name,triages[0], triages[2]||''); // , triages.length<4);
            triages.splice(0,3);
          }
        }
      }
      Clg.prototype.mark_read_thread = function(name,time, track){
        var lth = liveTag.mems.getFromName(name);
        if (time!==null) site2['DEFAULT'].check_reply.set_watched_to_last(lth.wt, time, name, track);
        else site2['DEFAULT'].check_reply.set_unwatch(lth.wt);
        liveTag.dirtify_ur(lth);
        if (pref.liveTag.style.use) liveTag.update_ur(lth,0,true);
//        site2[cnst.name2domainboardthread(name,true)[0]].insert_footer2(threads[name][0],threads[name][18],threads[name][19],threads[name][8]);
        this.footer.update_force(name, true);
//        gClg.footer.update_force(name, gClg.Clgs.length==1? pref.catalog.footer.flag : true);
//        if (reorder_thread_idx(name)) show_catalog(name); // called in triage_exe
        if (pref.notify.favicon) notifier.favicon.set(threads);
      };

//      var show_catalog_init_funcs;
//    Clg.prototype.show_catalog_scroll_lock_factory = function(clg){
//        var marks = null;
////        var set = Watchdog.prototype.restart.bind(new Watchdog(scroll_back, 5));
//        var wdg  = new Watchdog(scroll_back, 5);
//        var wdg2 = new Watchdog(tgt_lock_cancel, 100); // 500ms scroll lock and watchdog for oscillation.
//        var set = Watchdog.prototype.restart.bind(wdg);
////        var last_viewed = null;
//        function tgt_lock_cancel(){
//          wdg.cancel();
//          marks = null;
//        }
//        function scroll_back(){
//          var mark_top = marks[0].offsetTop || 0;
//          var abs_top = mark_top - marks[1] + marks[2];
//          if (abs_top<0) abs_top = 0;
//          if (!mark_top || clg.get_now_height()===abs_top) { // may cause oscillation
////            marks = null;
////             wdg2.stop(); // scroll lock
//            return;
//          }
//          window.scrollTo(0, abs_top);
//          set();
//        }
//        var last_viewed = null;
//        function start(pn){
//          if (marks===null || pn) {
////            if (marks && pn) scroll_back();
//            if (!pn) {
//              pn = (wdg2.id)? last_viewed : // scroll locked
//                              clg.GEH.get_last_viewed() || last_viewed; // return null when initial or deleted
//              if (!pn) return;
//            }
//            marks = [pn, pn.offsetTop, clg.get_now_height()];
//            last_viewed = pn;
//////            if (last_viewed) { // working code.
//////              marks = [last_viewed, last_viewed.offsetTop, get_now_height()];
//////              set();
//////            }
////            var now_height = get_now_height();
////            var last_top; // working code.
////            if (last_viewed) last_top = last_viewed.offsetTop;
////            if (last_viewed && last_top >= now_height && last_top <= now_height + window.innerHeight) marks = [last_viewed, last_top, now_height];
////            else { // scrolled
////              var posts = (embed_mode==='thread')? site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'thread_html', {thread:site.no})[0].posts :
////                                                   site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'page_html', {page:0})[0].posts;
////              var step = posts.length >> 1;
////              var i = 0;
////              while (step>0) {
////                if (posts[i+step].pn.offsetTop<now_height) i += step;
////                step >>= 1;
////              }
////              while (i<posts.length-1 && posts[i].pn.offsetTop<now_height) i++;
////              marks = [posts[i].pn, posts[i].pn.offsetTop, now_height];
////            }
//          }
//          wdg2.restart();
//          set();
//        }
//        return {
//          modified: function(tgt){
////            start((tgt.pn && marks[0]===tgt.pn)? site2[site.nickname].general_event_handler[site.whereami].get_prev_mark(tgt.pn) : null); // fail if get_prev_mark returns undefined;
//            if (tgt.pn && marks && marks[0]===tgt.pn) {
//              scroll_back();
//              marks = null; // get_prev_mark may return undefined
////              start(site2[site.nickname].general_event_handler[site.whereami].get_prev_mark(tgt.pn));
//              start(site2[site.nickname].general_event_handler[site.whereami].get_mark_from_height(tgt.pn.offsetTop));
//              this.GEH.clear_last_viewed();
//            } else start();
//          },
//          set: start,
////          viewed: function(tgt){if (!marks) last_viewed = tgt;},
////          viewed: function(tgt){
////            if (!wdg2.id)
////              last_viewed = tgt;}, // scroll lock
//        }
//      };
//      cataLog.show_catalog_scroll_lock = show_catalog_scroll_lock;
//      var drawn_idx = 0;
//      var show_catalog_cont = new DelayBuffer(show_catalog_delayed, 200).get_bound_func(); // working code
//      cataLog.show_catalog_cont = show_catalog_cont;
//      function show_catalog_delayed(){
//        ref_heights = null;
//        if (pClg.drawn_idx!==true) show_catalog();
//        else if (cataLog.catalog_obj2.lazy_draw) cataLog.catalog_obj2.lazy_draw();
//        cataLog.DIH.expand_thumbnail_on_demand();
//      }
//      var ref_heights = null;
//      function get_ref_height(threshold){
//        if (!threshold) threshold = 1.5;
////        return (embed_embed)? brwsr.document_body.scrollTop + window.innerHeight * threshold // slow.
////                            : triage_parent.scrollTop + triage_parent.clientHeight * threshold;
//        if (!ref_heights) ref_heights = {scrollTop: (scroll_event_src===window)? brwsr.document_body.scrollTop : scroll_event_src.scrollTop,
//                                         innerHeight: (scroll_event_src===window)? window.innerHeight : scroll_event_src.clientHeight}; // BUG, NEED TO ADD EVENTLISTENER TO TRIAGE_PARENT.'resize'.
//        return ref_heights.scrollTop + ref_heights.innerHeight * threshold;
//      }
//      cataLog.get_ref_height = get_ref_height;
      function get_now_height(){
        return (embed_embed)? brwsr.document_body.scrollTop : triage_parent.scrollTop;
      }
      function scopy_th(th){
         var dst = {posts:[], __proto__:th};
         for (var i=0;i<th.posts.length;i++) dst.posts[i] = {__proto__:th.posts[i]};
         return dst;
      }
var debug_str2;
//      function show_catalog(tgts_in,sound){
    var catalog_obj2_proto = {
      rolledup: 0,
      show_catalog: function(tgts_in, force_all_named, may_bump_down){ // , force_all_footer){
        var drawn_idx_old = this.drawn_idx;
var debug_count = 0;
        if (this.dirty) {
          if (this.dirty&0x06) {
            var check_perf = ['idx_rm_reorder_exe',performance.now()];
            var retvals_idx = this.idx_rm_reorder_exe(); // (this.dirty&0x02)? this.idx_rm_reorder_exe() : this.idx_remove_exe();
            if (pref.debug_mode['42']) comf.perf_out(check_perf);
            if (retvals_idx[0]<this.drawn_idx) this.func_track_reset();
            var tgts_from_idx = retvals_idx[1];
            var created_from_idx = this.dirty&0x10? null : retvals_idx[2] && Object.keys(retvals_idx[2]);
//            var created_from_idx = this.dirty&0x10? null : (cr_keys_from_idx = Object.keys(retvals_idx[2])).length==0? true : pref.test_mode['184']? cr_keys_from_idx : retvals_idx[2];
          }
          if (this.dirty&0x01) {
            this.footer.clean_idx = 0; // footer.draw checks footer.timestamp, I don't have to check them here.
//            this.footer.clean_idx = this.footer.seek_clean_idx(this.drawn_idx);
//            if (this.drawn_idx<this.footer.clean_idx) this.func_track_reset();
          }
          this.dirty &= ~0x0f; // 0x10 is kept as a flag for a case of being rolled up.
//          if (this.dirty&0x04) if (this.drawn_idx>this.idx_remove_exe()) this.func_track_reset();
//          if (this.dirty&0x02) if (this.drawn_idx>this.idx_reorder_exe()) this.func_track_reset();
        }
        if (this.rolledup) {this.rolledup = 2; return;}
//        force_all_named |= this.force_all_named;
        if (tgts_in===0 || tgts_in===-1) {var hide_tails = tgts_in===-1; this.drawn_idx = 0; tgts_in=undefined;}
//        if (this.masked) {this.updated_but_masked = true; return;} // test patch for meguca.
if (pref.debug_mode['11']) {
  var debug_str = threads_idx_debug(this.drawn_idx, this.drawn_y, this.ref_count, this.idxs);
}
if (pref.test_mode['23']) this.drawn_idx = 0;
        var draw_on_demand = pref[embed_mode].draw_on_demand && this.permit_draw_on_demand;
        if (draw_on_demand) {
          var ref_height = this.get_ref_height((pref[cataLog.embed_mode].merge)? 4 : (this.disp_th20)? 0.2 : (this.disp_th100)? 1 : 1.5);
          if (this.drawn_idx!==0 && this.drawn_y>=ref_height)
            if (this.ppn.offsetTop + this.ppn.offsetHeight<this.drawn_y) this.drawn_y = -1; // for expanding posts or images. // (this.ppn.offsetHeight||this.ppn.getBoundingClientRect().height) is required to scale <small> in 2ch.
//            if (this.ppn.offsetHeight<this.drawn_y) this.drawn_idx = 0; // for expanding posts or images.
            else if (tgts_in===undefined && created_from_idx===undefined) return true;
//            else if (tgts_in===undefined) return true;
//          if (drawn_idx!==0 && this.drawn_y>=ref_height) return;
        }
        var draw_adaptive = draw_on_demand && pref.network.adaptive;
        var draw_count = 0;
        if (pref[embed_mode].scroll_lock) this.show_catalog_scroll_lock.set();
//        if (common_obj.thread_reader && common_obj.thread_reader.observer_IDinfo) common_obj.thread_reader.observer_IDinfo.disconnect();
        if (this.observer_myself) this.observer_myself.disconnect(); 
        var tgts;
        if (typeof(tgts_in)==='string') {
          tgts = tgts_from_idx || {};
          tgts[tgts_in] = null;
//        } else if (Array.isArray(tgts_in)) {
//          tgts = {};
//          for (var i=0;i<tgts_in.length;i++) tgts[tgts_in[i]] = null;
        } else {
          tgts = tgts_from_idx || tgts_in;
          if (tgts_in && tgts_from_idx) for (var i in tgts_in) tgts[i] = tgts_in[i];
        }
        var tgts_count = (tgts)? Object.keys(tgts).length : -1; // may be called with tgts==={} (vacant object; tgts_count===0 from initial), see scan_boards_keyword_callback2
//        if (embed_catalog) {show_catalog_native();return;}
//        if (tgts) for (var name in tgts) if (threads[name] && threads[name][0].parentNode) threads[name][0].parentNode.removeChild(threads[name][0]); // this is required when the thread is moved to later. // THIS CAUSES A BLINK.
////////        catalog_triage_out();
//        var triage_thread = triage.get_triaged_thread_name();
//        pref_func.tooltips.hide();
        var catalog_expand_with_hr = (embed_mode==='float' && pref.catalog_expand_with_hr) || pref[embed_mode].env.disp_filler;
        if (tgts!==undefined) for (var i=0;i<this.drawn_idx;i++) if (this.idxs[i] in tgts) {this.drawn_idx = i; this.drawn_y = -1; break;}
        this.ref_count_init = pref[embed_mode].env.disp_offset;
        if (catalog_expand_with_hr) this.ref_count_init = show_catalog_skip_hs(this.ref_count_init);
        var mode_merge = this.func_lazy_draw;
        var mode_merge_tails = (mode_merge && this.pref[this.mode].merge_list && !this.pref[this.mode].merge)? null : false;
        var merge_tails = mode_merge_tails===null && (pref.test_mode['183'] || (this.dirty&0x10) /*|| tgts_count>=0*/ || may_bump_down);
        var try_all = tgts===undefined || !draw_on_demand || !pref.test_mode['132'] && (!pref.test_mode['151'] || tgts_count!==1); // test_mode['132'] causes problem at merge mode, see func_track_shown // test_mode['151'] doesn't work if redraw request comes from 'MERGE' triage.
// DIRTY CONTROL (UNDER CONSTRUCTION)
//   footer: this.footer.clean_idx
//   data: tgt_th[16].th
//   order: this.drawn_idx and this.drawn_y
// try_all WILL BE DEFAULT, bump down occurs usually when threads are sorted by /bumptime.
        if (tgts!==undefined && try_all || this.drawn_idx===0 || this.drawn_idx===true) {
          this.func_track_reset(); // called with drawn_idx===true from 'release_draw'.
          if (mode_merge) try_all = true; // tracking info about merge was cleared by func_track_reset
//          cataLog.Footer.timestamp_trial_prep();
        }
        var shown_count = 0;
        var load_tgt = '';
//        this.drawn_idx_mb = -1;
//        var appeared = [];
//var debug = '';
//for (var d=0;d<threads_idx.length;d++) if (threads_idx[d]!=='ODL:' && threads[threads_idx[d]][9][0]) debug += threads_idx[d] + ', ';
//console.log(debug);
        var i_start = this.drawn_idx;
        var lazy_step = this.view==='catalog'? pref.proto.lazyDraw.step : pref.proto.lazyDraw.merge_step; // pref[cataLog.embed_mode].lazyDraw.step;
        var lazy_next = Math.floor(ref_height*this.lazy_avgSizeInv) + lazy_step;
//        var lazy_check = lazy_step <= i_start;
        var footer_clean_idx = this.footer.clean_idx;
//        var i_start = (draw_on_demand && drawn_idx!==true)? drawn_idx : 0;
//        var retval = false;
        var view_page_thread = this.view==='page' || this.view==='thread';
        var i=-1;
        while (++i<this.idxs.length) {
//console.log(this.drawn_y+', '+triage_parent.scrollTop+', '+triage_parent.clientHeight*1.5+', '+ref_height);
          var name = this.idxs[i];
          var tgt_th = this.threads[name];
          if (name.substr(0,4)===':DL:') {
            if (load_tgt==='') {
              load_tgt = name;
              if (this.load_on_demand_try_load([load_tgt.substr(4)])) this.idxs.splice(i,1);
              break;
            }
          } else {
            if (draw_on_demand && this.drawn_y>=ref_height && (!force_all_named || tgts_count<=0)) {
              if (mode_merge_tails===false || !merge_tails) {
                if (!hide_tails) break; // {cataLog.Footer.timestamp_inc(); break;}
                else if (hide_tails===true) hide_tails = i + 1; // be true
                if (tgt_th[1]) if (this.func_hide(name)) tgt_th[1] = false;
                continue;
              }
              if (mode_merge_tails===null) {
//                if (!pref.test_mode['183'] && created_from_idx===true) break;
                if (!pref.test_mode['183'] && created_from_idx && created_from_idx.length>=0) {
//                if (!pref.test_mode['183'] && created_from_idx) {
//                  if (created_from_idx.length===0) break; // can't merge with threads in other boards
                  if (i + created_from_idx.length>=this.idxs.length) created_from_idx = null;
                  else {
                    var i_merge_tails = i;
                    var merge_tails_idx = created_from_idx.length;
                  }
                }
                mode_merge_tails = i + 1; // be true
                draw_adaptive = false;
              }
              if (!pref.test_mode['183'] && merge_tails_idx>=0) {
//              if (!pref.test_mode['183'] && created_from_idx) {
////                if (pref.test_mode['184']) {
//                if (merge_tails_idx<=0) break; // can't merge with threads in other boards
                i = i_merge_tails; // keep looping
                name = --merge_tails_idx>=0? created_from_idx[merge_tails_idx] : this.idxs[i];
//                } else if (!(name in created_from_idx)) {
//                  while (++i<this.idxs.length && !(this.idxs[i] in created_from_idx));
//                  name = this.idxs[i];
//                }
                tgt_th = this.threads[name];
              }
debug_count++;
              if (!this.merge_bases.isShownForTails(name)) {
                if (hide_tails && tgt_th[1]) if (this.func_hide(name)) tgt_th[1] = false;
                continue;
              }
            } // else if (tgts_count===0 && !force_all_footer) break; // can't keep valid area clean if the thread is chenged NOT to be shown using MERGE or KILL.
//            if (tgts_count===0 && mode_merge_tails===false) {retval = true; break;}
            if (i>=i_start) {
//              if (i%lazy_step===0) lazy_check = true;
              if (try_all || tgts_count===0 || (name in tgts) || mode_merge_tails) {
//              if (try_all || tgts_count!==0 && (name in tgts) || mode_merge_tails) {
                var to_show = this.filter_test(name, tgt_th); // use differentAPI===true if wrapped posts are required
                if (to_show && view_page_thread && !pref.test_mode['52']) to_show = format_html.replace_posts_by_search(tgt_th, this.pref); // may change tgt_th[9][0]
                if (to_show) {
                  if (!tgt_th[0] || tgt_th[16].th) {
                    this.insert_thread_prepare_html_lazy(tgt_th, !tgt_th[0], false, true, null);
                    if (draw_adaptive && (!force_all_named || tgts_count<=0)) {
                      if (++draw_count==pref.network.th100) {
                        ref_height = this.get_ref_height(1);
                        if (!this.disp_th100) {this.disp_th100 = setTimeout(function(){this.disp_th100 = null; this.show_catalog();}.bind(this), pref.network.th100_delay);}
                      } else if (draw_count===pref.network.th20) {
                        ref_height = this.get_ref_height(0.2);
                        if (!this.disp_th20) {
                          clearTimeout(this.disp_th100);
                          this.disp_th100 = null;
                          this.disp_th20 = setTimeout(function(){this.disp_th20 = null; this.show_catalog();}.bind(this), pref.network.th20_delay);}
                        draw_adaptive = false; // close
                      }
                    }
                  }
                  this.footer.draw(tgt_th, name);
                  if (mode_merge_tails && !this.func_check_overrated(name)) { // overrating is only happen with !try_all probably.
                    this.func_search_ref_idx(name,null,true); 
                    if (!tgt_th[1]) tgt_th[1] = true;
//                    if (!tgt_th[1]) if (this.func_show(name,null)) tgt_th[1] = true;
                    continue;
                  }
////                  if (tgts!==undefined) { // working code.
////                    ref_count = 0;
////                    for (var j=0;j<i;j++) if (threads_idx[j].substr(0,4)!=='ODL:' && threads[threads_idx[j]][1]) ref_count++;
////                  }
////                  if (catalog_expand_with_hr) {
////                    var j = 0;
////                    var j_max = triage_parent.childNodes.length;
////                    while (j<=ref_count && j<j_max) if (triage_parent.childNodes[j++].tagName!=='DIV') ref_count++;
////                  }
                  var ref;
                  if (tgts!==undefined || mode_merge) {
                    if (tgts && pref.test_mode['132'] && mode_merge) this.func_check_reorder(name,i);
                    var j = (mode_merge? this.func_search_ref_idx(name,i) : i) -1; // bump up here in mode_merge
                    while (j>=0 && (this.idxs[j].substr(0,4)===':DL:' || !this.threads[this.idxs[j]][1] || (mode_merge && this.func_search_ref(this.idxs[j])))) j--;
                    if (j>=0) {
                      ref = this.threads[this.idxs[j]][0].nextSibling;
                      if (catalog_expand_with_hr && ref) ref = ref.nextSibling;
                    } else ref = this.ppn.childNodes[this.ref_count_init];
//                    delete tgts[name];
//                    if (Object.keys(tgts).length===0) tgts='END';
                  } else ref = this.ppn.childNodes[this.ref_count];
                  if (ref!==tgt_th[0] || !tgt_th[1]) {
                    if (this.func_show(name,ref)) tgt_th[1] = true;
//                    if (pref.test_mode['127']) if (triage_thread) {triage.off_delay();triage_thread=null;}
                  }
//                  if (this.lazy_draw) this.lazy_draw(name);
//                  if (this.lazy_draw) if (this.lazy_draw(name, get_ref_height(4)) this.backup_mb_state(i);
                  if (tgt_th[1]) {
                    var update_y = draw_on_demand && try_all && (this.drawn_y<ref_height) && (shown_count>=lazy_next); // try_all should be removed???
                    var retvals_fts = this.func_track_shown(name, update_y, i);
                    merge_tails |= retvals_fts & 0x02;
                    if (update_y && !(retvals_fts&0x01)) { // check for merge
                      lazy_next += lazy_step;
                      if (try_all && !pref.test_mode['150']) {
                        this.lazy_avgSizeInv = this.drawn_y>0? shown_count/this.drawn_y : 0;
                        lazy_next = Math.max(lazy_next, Math.floor(ref_height*this.lazy_avgSizeInv)+lazy_step);
                      }
                    }
                  }
                  
//                  if (threads[name][1]) { // working code.
//                    if (draw_on_demand) drawn_y = threads[name][0].offsetTop; // for faster execution. DOM function is too heavy.
//                    ref_count += this.ref_step;
//                    if (catalog_expand_with_hr) ref_count = show_catalog_skip_hs(ref_count);
//                  }
                } else if (tgt_th[1]) {
//                  if (pop_up_status[name]) pop_down_event(name);
//                  threads[name][11] = remove_open_new_thread_event(threads[name][11]);
                  if (this.func_hide(name)) tgt_th[1] = false;
//                  if (pref.test_mode['127']) if (triage_thread) {triage.off_delay();triage_thread=null;}
                }
                if (tgts && (name in tgts)) tgts_count--;
              } else if (i>=footer_clean_idx && tgt_th[1]) this.footer.draw(tgt_th, name);
            } else if (i>=footer_clean_idx && tgt_th[1]) this.footer.draw(tgt_th, name); // always check, this is slow but better than others.
            if (tgt_th[1]) if (!mode_merge || view_page_thread || !this.func_search_ref(name)) shown_count += view_page_thread? tgt_th[16].posts.length : 1;
          }
//          if (triage_thread===name) triage_thread = null;
        }
        if (mode_merge_tails || hide_tails) i = (mode_merge_tails||hide_tails) -1;
//        if (mode_merge_tails) i = mode_merge_tails -1;
        if (try_all || tgts_count==0 && drawn_idx_old<i) this.drawn_idx = (!draw_on_demand || i==this.idxs.length)? true : (i<i_start)? i_start : i;
        this.footer.clean_idx = i;
        if (mode_merge) {
          if (pref.test_mode['214']) this.footer.draw_merge_consolidated_exe();
          this.func_lazy_draw();
        }
        if (this.observer_myself) this.observer_myself.observe(); 
//        if (common_obj.thread_reader && common_obj.thread_reader.observer_IDinfo) common_obj.thread_reader.observer_IDinfo.observe();
        this.dirty = 0;
        if (pref.test_mode['184']) console.log('show_catalog: '+debug_count+'/'+(created_from_idx && created_from_idx.length)+' at '+i_merge_tails+'/'+this.idxs.length)
//        this.force_all_named = false;
//        if (this.drawn_idx_mb!=-1) this.restore_mb_state();
//        if (appeared.length!=0 && sound) notifier.appeared(appeared,threads);
//var str_debug = '';
//for (var d=0;d<threads_idx.length;d++) if (threads[threads_idx[d]][1] && threads[threads_idx[d]][9][0]) str_debug += threads_idx[d] + threads[threads_idx[d]][8][pref.catalog.indexing] + ',';
//console.log(str_debug);
////        if (catalog_expand_with_hr)
////          for (var i=triage_parent.childNodes.length-2;i>=0;i--)
////            if (triage_parent.childNodes[i].tagName==='HR' && triage_parent.childNodes[i+1].tagName==='HR') triage_parent.removeChild(triage_parent.childNodes[i+1]);
if (pref.debug_mode['11'] && embed_mode!=='thread' && !pref[embed_mode].merge) {
        var flag = -1;
        var d_count = 0;
        for (var d=0;d<i;d++) {
          if (this.idxs[d][0]!=='O' && this.threads[this.idxs[d]][1]) {
            var dbt = comf.fullname2dbt(this.idxs[d]);
            var tgt,tgt_bt;
            if (embed_mode==='page' && site.nickname==='4chan') {
              tgt = this.ppn.getElementsByClassName('CatChan_thread')[d_count++];
              tgt_bt = site.board + tgt.id.substr(1);
            } else if (embed_mode==='page' && site.nickname==='8chan') {
              tgt = this.ppn.getElementsByClassName('CatChan_thread')[d_count++];
              tgt_bt = '/' + tgt.getAttribute('data-board') + '/' + tgt.id.substr(7);
            } else {
              tgt = this.ppn.childNodes[pref[embed_mode].env.disp_offset + d_count++];
              tgt_bt = (embed_mode==='page')? '/' + tgt.getAttribute('data-board') + '/' + tgt.id.substr(7) :
                                              tgt.getElementsByTagName('a')[0].getAttribute('href').replace('res/','').replace('.html','').replace(/.*lainchan.org/,'').replace(/.*8kun.top/,'').replace('thread/','').replace(/.*4chan.org/,'').replace(/.*meguca.org/,'').replace(/.*krautchan.net/,'').replace(/thread\-/,'');
              if (dbt[0]==='4chan' && embed_mode==='catalog') tgt_bt = tgt_bt.substr(0,tgt_bt.lastIndexOf('/'));
            }
            if (dbt[1]+dbt[2]!==tgt_bt) {
              var debug_str4='';
              for (var j=d+1;j<d+5;j++) debug_str4 += this.idxs[j]+', ';
              console.log('ERROR: '+d+', '+tgt_bt+', '+this.idxs[d]+': '+i+', '+debug_str4);
              flag = i;
            }
          }
        }
        var debug_str3 = threads_idx_debug(this.drawn_idx, this.drawn_y, this.ref_count, this.idxs);
        if (flag!=-1) {
          console.log('PREV: '+debug_str2);
          console.log('IN:   '+debug_str);
          for (var i=0;i<flag+5;i++) if (this.ppn.childNodes[i]) console.log(this.ppn.childNodes[i]);
          console.log('OUT:  '+debug_str3);
//          console.trace();
        } else {
//          console.log('OK: '+debug_str);
        }
        debug_str2 = debug_str3;
}
        if (pref.debug_mode['36']) {
          if (debugWindow && !this.debug) this.debug = debugWindow.connect();
          if (this.debug) {
            var i=-1;
            var i1=-1;
            var footer0 = null;
            var footer1 = null;
            while (++i<this.idxs.length) {
              if (this.threads[this.idxs[i]]) {
                if (footer0 = this.footer.test_dirty(this.idxs[i])) break;
                footer1 = this.threads[this.idxs[i]][24] || footer1;
                i1 = i;
              }
            }
            this.debug.innerHTML = this.name+':<br>'+
              'drawn_idx: '+this.drawn_idx+'<br>'+
              'drawn_y: '+this.drawn_y+'<br>'+
              'last_clean_footer: '+this.idxs[i1]+', offsetTop: '+(footer1 && footer1[0].parentNode.offsetTop)+'<br>'+
              'first_dirty_footer: '+this.idxs[i]+', offsetTop: '+(footer0 && footer0[0].parentNode.offsetTop);
          }
        }
//        return retval;
      },
      func_show: function(name,ref){
        this.ppn.insertBefore(this.threads[name][0],(ref && ref.parentNode===this.ppn)? ref : null); // if many threads are removed, ref refers horizontal splitter.
        return true;
      },
      func_hide: /*(site.whereami!=='archive')?*/ function(name){
        this.ppn.removeChild(this.threads[name][0]);
        return true;
//      } : function(name){ // patch
//        threads[name][0].parentNode.removeChild(threads[name][0]);
//        return true;
      },
      func_hide_all: function(tgts, prep_remake){
//        var catalog_expand_with_hr = (this.mode==='float' && pref.catalog_expand_with_hr) || pref[this.mode].env.disp_filler;
        if (Array.isArray(tgts)) for (var i=0;i<tgts.length;i++) this.func_hide_all_1(tgts[i], prep_remake);
        else for (var name in tgts) this.func_hide_all_1(name, prep_remake);
      },
      func_hide_all_1: function(name, prep_remake){
        var tgt_th = this.threads[name];
        if (tgt_th) {
          if (tgt_th[1]) if (this.func_hide(name)) tgt_th[1] = false;
          if (prep_remake) this.remake_html_prep(tgt_th);
        }
      },
      func_track_shown: function(name, update_drawn_y){
        if (update_drawn_y) this.drawn_y = this.threads[name][0].offsetTop; // - this.pppn.offsetTop; // for consistency of this.drawn_y, but slow, so this is handled in 'get_ref_height' // for faster execution. DOM function is too heavy.
        this.ref_count += this.ref_step;
//        return update_drawn_y;
      },
      func_track_reset: function(){
        this.ref_count = this.ref_count_init;
        this.drawn_y = -1;
        this.drawn_idx= 0;
//        cataLog.Footer.timestamp_inc();
      },
//      backup_mb_state: function(idx){
//        if (this.drawn_idx_mb==-1) {
//          this.drawn_idx_mb = idx;
//          this.ref_count_mb = this.ref_count;
//        }
//      },
//      restore_mb_state: function(){
//        drawn_idx = this.drawn_idx_mb;
//        this.ref_count = this.ref_count_mb;
//      },
      permit_draw_on_demand: true,
      ref_step: 1,
      drawn_y: -1, // for floating catalog
      ref_count: 0,
      ref_count_init: 0,
      dirty: 0x11, // [0]:updated(footer), [1]:reordered, [2]:removed, [3]:may contain duplicated entries, [4]: merge_list was changed.
      lazy_avgSizeInv: 0,
//      drawn_idx_mb: -1,
//      ref_count_mb: 0,
    };
    comf.Object_defProps(Clg.prototype, catalog_obj2_proto);

////      if (pref.test_mode['96'] && embed_mode==='catalog') { // test for faster execution, removes redundant queries of offsetTop // working code
////        catalog_obj2_proto = {
////          func_track_shown: (function(func_org){
////            return function(name, update_drawn_y){
////              var y_old = this.drawn_y;
////              var check_y = this.od_count%this.od_width==0;
////              var retval = func_org.call(this, name, update_drawn_y && check_y);
////              if (check_y && y_old==this.drawn_y) this.od_width++;
////              this.od_count++;
////              return retval;
////            }
////          })(catalog_obj2_proto.func_track_shown),
////          func_track_reset: (function(func_org){
////            return function(){
////              func_org.call(this);
////              this.od_count = 0;
////            };
////          })(catalog_obj2_proto.func_track_reset),
////          func_od_reset: function(){
////            this.od_width = 1;
////            this.show_catalog_cont();
////          },
////          od_width: 1, // on_demand_mode
////          od_count: 0,
////          __proto__: catalog_obj2_proto
////        };
////      }
//////      scroll_event_src.addEventListener('scroll', show_catalog_cont, false);
////      if (pref.test_mode['96'] && embed_mode==='catalog') window.addEventListener('resize', catalog_obj2_proto.func_od_reset.bind(catalog_obj2_proto), false);
//////      window.addEventListener('resize', (pref.test_mode['96'] && embed_mode==='catalog')? catalog_obj2_proto.func_od_reset.bind(catalog_obj2_proto) : show_catalog_cont, false);
      var catalog_obj2 = catalog_obj2_proto;
      if (embed_mode==='page' && site2[site.nickname].all_boards && site2[site.nickname].all_boards.indexOf(site.board)!=-1) { // PATCH
        catalog_obj2 = {
          func_show: function(name){
            var ch = this.threads[name][0];
            if (ch.previousSibling && ch.previousSibling.tagName==='H2') {
              ch.previousSibling.style.display = '';
              ch.style.display = '';
              return true;
            } else return false;
          },
          func_hide: function(name){
            var ch = this.threads[name][0];
            if (ch.previousSibling && ch.previousSibling.tagName==='H2') {
              ch.previousSibling.style.display = 'none';
              ch.style.display = 'none';
              return true;
            } else return false;
          },
          permit_draw_on_demand: false,
          __proto__: catalog_obj2_proto
        }
////////      } else if (pref[embed_mode].merge) { // TEST // working code, but very slow because of basesd on HTML.
////////        var base_name = site.nickname + site.board + site.no;
////////        catalog_obj2 = {
////////          func_show_hide: function(name, show_or_hide){
////////            if (embed_mode==='thread' && base_name===name) return true;
////////            var myself = site2[site.nickname].update_posts_prep_merge();
////////            var dst = myself.posts;
////////            var pnode = myself.pn;
////////            var dbt = name.split('/');
////////            var posts = threads[name][16].posts;
////////            var src = {domain:dbt[0], board:dbt[1], no:dbt[2], posts:posts};
////////            if (show_or_hide) {
//////////////////              var j = dst.posts.length-1;
//////////////////              var i = posts.length-1;
//////////////////              while (i>=0) {
//////////////////                while (j>0 && dst.posts[j].time>posts[i].time) j--;
//////////////////                if (dst.posts[j].com===posts[i].com) break;
//////////////////                else i--;
//////////////////              }
//////////////////              while (++i<posts.length) {
//////////////////                while (j<dst.posts.length && dst.posts[j].time<posts[i].time) j++;
//////////////////                site2[site.nickname].update_posts_insert(src, dst, i, j, pnode);
//////////////////              }
////////////              var time_merged = threads[name][16].time_merged || 0; // working code.
////////////              var i = 0;
////////////              var j = 0;
//////////////              var scroll_add = 0;
//////////////              var now_height = (pref[embed_mode].scroll_lock && !init)? get_now_height() : 0;
//////////////              var now_height_tmp = now_height;
////////////              if (time_merged!==0) {
////////////                i = posts.length;
////////////                while (i>0 && posts[i-1].time>time_merged) i--;
////////////                j = dst.posts.length;
////////////                while (j>0 && dst.posts[j-1].time>time_merged) j--;
////////////              }
////////////              while (i<posts.length) {
////////////                while (j<dst.posts.length && (dst.posts[j].time<posts[i].time || dst.posts[j].time==posts[i].time && dst.posts[j].no<=posts[i].no)) j++;
////////////                site2[site.nickname].update_posts_insert(src, dst, i++, j, pnode);
//////////////                var add_val = site2[site.nickname].update_posts_insert(src, dst, i++, j, pnode, now_height_tmp); // slow
//////////////                if (add_val!==null) scroll_add += add_val;
//////////////                else now_height_tmp = 0;
////////////                if (pref[embed_mode].scroll_lock) show_catalog_scroll_lock.modified(src.posts[i-1]);
////////////              }
////////////              threads[name][16].time_merged = posts[posts.length-1].time;
//////////////              if (scroll_add) window.scrollTo(0,now_height + scroll_add);
////////              site2[site.nickname].update_posts_insert_merge(src.posts,0);
////////            } else {
//////////              var back = 0;
//////////              var now_height;
//////////              if (pref[embed_mode].scroll_lock) now_height = get_now_height();
////////              for (var i=posts.length-1;i>=0;i--) {
//////////                if (pref[embed_mode].scroll_lock) if (src.posts[i].offsetTop<now_height) back += src.posts[i].offsetHeight; // TEST, SHOULD INCLUDE container.
////////                if (src.posts[i].pn) site2[site.nickname].update_posts_remove(src, i, pnode, true);
////////              }
//////////              if (back) window.scrollTo(0,now_height-back);
////////              threads[name][16].time_merged = 0;
////////            }
////////            return true;
////////          },
////////          func_show: function(name){if (!threads[name][1]) this.func_show_hide(name, true); return true;},
////////          func_hide: function(name){if ( threads[name][1]) this.func_show_hide(name, false);return true;},
////////          permit_draw_on_demand: false,
////////          __proto__: catalog_obj2_proto
////////        }
////////        if (embed_mode==='page') {
////////          var top = site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, 'page_html', {page:0})[0];
////////          catalog_obj2.base_name = site.nickname + site.board + top.no;
////////          site2['DEFAULT'].update_posts_merge_base = top.pn;
////////        }
//////////        if (pref[embed_mode].scroll_lock && site2[site.nickname].update_posts_remove_lock) {// TEST // TOO SLOW
//////////          site2[site.nickname].update_posts_remove = site2[site.nickname].update_posts_remove_lock;
//////////          site2[site.nickname].update_posts_insert = site2[site.nickname].update_posts_insert_lock;
//////////        }
////////        show_catalog_init_funcs = (embed_mode==='page')? function(){
////////          for (var name in threads)
////////          if (threads[name][0]!==site2['DEFAULT'].update_posts_merge_base && threads[name][0].parentNode===triage_parent) {
////////            if (threads[name][0].nextSibling.tagName==='HR') triage_parent.removeChild(threads[name][0].nextSibling);
////////            triage_parent.removeChild(threads[name][0]);
////////            catalog_obj2.func_show_hide(name, true);
////////          }
////////        } : null;
      } else if (pref[embed_mode].env.disp_filler) {
        catalog_obj2 = catalog_obj2_factory_filler(cnst.dom(pref[embed_mode].env.disp_filler));
      } else if (embed_mode==='float' && pref.catalog_expand_with_hr) {
        catalog_obj2 = {
          func_show: function(name,ref){
            if (this.threads[name][1]) show_catalog_hr(name,'remove');
            this.ppn.insertBefore(this.threads[name][0],(ref && ref.parentNode===this.ppn)? ref : null); // if many threads are removed, ref refers horizontal splitter.
            show_catalog_hr(name,'add');
            return true;
          },
          func_hide: function(name){
            show_catalog_hr(name,'remove');
            this.ppn.removeChild(this.threads[name][0]);
            return true;
          },
          func_track_shown: function(name, update_drawn_y){
            this.__proto__.func_track_shown.call(this, name, update_drawn_y);
            this.ref_count = show_catalog_skip_hs(this.ref_count);
//            return retval;
          },
          __proto__: catalog_obj2_proto
        }
//        var pctrls = triage_parent;
//        for (var i=pctrls.childNodes.length-1;i>=0;i--) if (pctrls.childNodes[i].tagName==='HR') pctrls.removeChild(pctrls.childNodes[i]);
//        pctrls.parentNode.insertBefore(document.createElement('hr'),pctrls.nextSibling);
      }
      function catalog_obj2_factory_filler(filler){
        return {
          func_show: function(name,ref){
            var hr = this.threads[name][1] && this.threads[name][0].nextSibling || filler.cloneNode(false); // for 4chan, lastChild.nextSibling === null.
//            var hr = (threads[name][1])? threads[name][0].nextSibling : filler.cloneNode(false);
            this.ppn.insertBefore(this.threads[name][0],ref);
            this.ppn.insertBefore(hr,ref);
            return true;
          },
          func_hide: function(name){
            this.ppn.removeChild(this.threads[name][0].nextSibling);
            this.ppn.removeChild(this.threads[name][0]);
            return true;
          },
          ref_step: 2,
          __proto__: catalog_obj2_proto
        };
      }
      comf.Object_defProps(pClg, catalog_obj2);
//      comf.Object_defProps(Clg.prototype, catalog_obj2);
      var show_catalog = pClg.show_catalog.bind(pClg);
//      var show_catalog = (catalog_obj2)? catalog_obj2.show_catalog.bind(catalog_obj2) :
//                                         catalog_obj2_proto.show_catalog.bind(catalog_obj2_proto);
      cataLog.show_catalog = show_catalog;
      cataLog.catalog_obj2 = pClg; // Clg.prototype;
//      cataLog.catalog_obj2 = catalog_obj2;
//      var show_catalog_db_th100 = new DelayBuffer(show_catalog, pref.network.th100_delay);
//      var show_catalog_db_th20 = new DelayBuffer(show_catalog, pref.network.th20_delay);

function threads_idx_debug(idx,y,count, threads_idx){
  var str = 'drawn_idx:'+idx+', drawn_y:'+y+', ref_count:'+count+' ';
  for (var i=0;i<threads_idx.length;i++) if (!threads[threads_idx[i]] || threads[threads_idx[i]][1]) str += threads_idx[i]+', ';
  str += '::: '+JSON.stringify(threads_idx);
  return str;
}
      Clg.prototype.load_on_demand_timer = null; // means mutex also
      Clg.prototype.load_on_demand_release = function(){
        if (this.load_on_demand_timer!==null) clearTimeout(this.load_on_demand_timer);
        this.load_on_demand_timer = null;
      };
      Clg.prototype.load_on_demand_try_load = function(tgts){
        if (this.load_on_demand_try_get_mutex()) {
          this.idx_delete_odl(tgts);
          scan_boards.scan_init('on_demand_load', tgts, {refresh:this, crawler_max:1, callback:this.load_on_demand_release_draw.bind(this)});
          return true;
        } else return false;
      };
      Clg.prototype.load_on_demand_release_draw = function(){
        this.load_on_demand_release();
        this.show_catalog();
      };
      Clg.prototype.load_on_demand_try_get_mutex = function(){
        if (this.load_on_demand_timer!==null) return false;
        this.load_on_demand_timer = setTimeout(this.load_on_demand_release.bind(this), pref.proto.load_on_demand_timeout*1000);
        return true;
      };
//      var load_on_demand = (function(){ // working code
//        var mutex = true;
//        var timer = null;
//        function release(){
//          if (timer) {
//            clearTimeout(timer);
//            timer = null;
//          }
//          mutex = true;
//        }
//        return {
//          call: function(clg,tgts){
//            if (this.get()) {
//              pClg.idx_delete_odl(tgts);
//              scan_boards.scan_init('on_demand_load', tgts, {refresh:clg, crawler_max:1, callback:load_on_demand.release_draw});
//              return true;
//            } else return false;
//          },
//          release: release,
//          release_draw: function(){release();show_catalog();},
//          get: function(){
//            var retval = mutex;
//            if (retval) timer = setTimeout(release, pref.proto.load_on_demand_timeout*1000);
//            mutex = false;
//            return retval;
//          }
//        }
//      })();

      function show_catalog_skip_hs(ref_count){
        var c_len = triage_parent.childNodes.length;
        var class_hs = pref.cpfx+'hs';
//        while (ref_count<c_len && triage_parent.childNodes[ref_count].className===class_hs) ref_count++;
        if (ref_count<c_len && triage_parent.childNodes[ref_count].className===class_hs) ref_count++;
        return ref_count;
      }
      function show_catalog_hr(name,func){
        if (embed_mode==='catalog') return;
        var pn = threads[name][0];
        var class_hs = pref.cpfx+'hs';
        if (func==='add' && pn.style.width==='' && pn.style.height==='') {
          if (pn.previousSibling && pn.previousSibling.className!==class_hs) pn.parentNode.insertBefore(site2[site.nickname].horizontal_separator_in_index(),pn);
          if (pn.nextSibling && pn.nextSibling.className!==class_hs) pn.parentNode.insertBefore(site2[site.nickname].horizontal_separator_in_index(),pn.nextSibling);
        }
        if (func==='shrink' || func==='remove' && pn.style.width==='' && pn.style.height==='' || func==='add' && (pn.style.width!=='' || pn.style.height!=='')) {
          var pn_test = (pn.previousSibling && pn.previousSibling.className===class_hs)? pn.previousSibling.previousSibling : null;
          if (pn_test && (pn_test.style.width!=='' || pn_test.style.height!=='')) pn.parentNode.removeChild(pn.previousSibling);
          var pn_test = (pn.nextSibling && pn.nextSibling.className===class_hs)? pn.nextSibling.nextSibling : null;
          if (pn_test && (pn_test.style.width!=='' || pn_test.style.height!=='')) pn.parentNode.removeChild(pn.nextSibling);
        }
        if (func==='remove') {
//          if (pn.previousSibling && pn.previousSibling.className===class_hs && pn.nextSibling && pn.nextSibling.className===class_hs) pn.parentNode.removeChild(pn.previousSibling); // BUG IN 8ch page mode, remoev first br and 'ref' becomes null.
          if (pn.previousSibling && pn.previousSibling.className===class_hs && pn.nextSibling && pn.nextSibling.className===class_hs) pn.parentNode.removeChild(pn.nextSibling);
//          if (pn.nextSibling && pn.nextSibling.className===class_hs && pn.nextSibling.nextSibling && pn.nextSibling.nextSibling.className===class_hs) pn.parentNode.removeChild(pn.nextSibling);
        }
      }

////      function show_catalog_hr(name,func){ // working code.
////        var pn = threads[name][0];
//////        var sibls = ['previousSibling', 'nextSibling'];
//////        if (pn.style.width==='' && pn.style.height==='') {
//////          for (var i=0;i<sibls.length;i++)
//////            while (pn[sibls[i]] && pn[sibls[i]].tagName==='HR' && pn[sibls[i]][sibls[i]].tagName==='HR') pn.parentNode.removeChild(pn[sibls[i]]);
////        if (func==='add' && pn.style.width==='' && pn.style.height==='') {
////          if (pn.previousSibling && pn.previousSibling.tagName!=='HR') pn.parentNode.insertBefore(document.createElement('hr'),pn);
////          if (pn.nextSibling && pn.nextSibling.tagName!=='HR') pn.parentNode.insertBefore(document.createElement('hr'),pn.nextSibling);
////        }
////        if (func==='shrink' || func==='remove' && pn.style.width==='' && pn.style.height==='' || func==='add' && (pn.style.width!=='' || pn.style.height!=='')) {
////          var pn_test = (pn.previousSibling && pn.previousSibling.tagName==='HR')? pn.previousSibling.previousSibling : null;
////          if (pn_test && (pn_test.style.width!=='' || pn_test.style.height!=='')) pn.parentNode.removeChild(pn.previousSibling);
////          var pn_test = (pn.nextSibling && pn.nextSibling.tagName==='HR')? pn.nextSibling.nextSibling : null;
////          if (pn_test && (pn_test.style.width!=='' || pn_test.style.height!=='')) pn.parentNode.removeChild(pn.nextSibling);
////        }
////        if (func==='remove') {
////          if (pn.previousSibling && pn.previousSibling.tagName==='HR' && pn.nextSibling && pn.nextSibling.tagName==='HR') pn.parentNode.removeChild(pn.previousSibling);
//////          for (var i=0;i<sibls.length;i++) {
//////            var pn_test = pn[sibls[i]][sibls[i]];
//////            if (pn_test.tagName==='DIV' && pn_test.style && (pn_test.style.width!=='' || pn_test.style.height!=='')) pn.parentNode.removeChild(pn[sibls[i]]);
//////          }
////        }
////      }

      Clg.prototype.view_attr_changed = function(){
        for (var name in this.threads) this.view_attr_set(name);
      };
//      Clg.prototype.view_attr_changed = function(){ // working code
//        var tgts = {};
//        var req_acc = 0;
//        for (var name in this.threads) {
//          var req = this.view_attr_set(name, null, true);
//          if (req) {
//            tgts[name] = null;
//            req_acc |= req;
//          }
//        }
//        this.show_catalog(tgts, req_acc&0x02, req_acc&0x04);
//      };
      var RollbackInfo = new WeakMap();
      Clg.prototype.view_attr_set = function(name, style_only, skip_showing, pn_in, force_unroll){
        var tgt_th = this.threads[name];
        var pf = this.pref.filter;
        var pn = pn_in || tgt_th[0];
        var val = null;
        if (pf.attr_list) {
          var wlc;
          if (pref.common.blur_404 && (wlc=pf.watch_list_obj2[name]) && (wlc=wlc.cmd) && wlc['PRUNED'] && this.mode!=='thread' && !this.noBlur) val = {style:{opacity:0.4}}; // view is one of catalog, headline and page, never 'thread' // CAUTION: obj2 is accessed directly.
          if (pref.catalog.style_general_list) val = pref_func.merge_obj5(name,pref.catalog.style_general_list_obj2,val);
          if (pf.attr_list) val = pref_func.merge_obj5(name,pf.attr_list_obj2,val);
        }
//        if (val && val.style) {
////          var styles = val.style.split(';');
////          for (var i=0;i<styles.length;i++) {
////            var stl = styles[i].split(':');
////            pn.style[stl[0]] = stl[1];
////          }
////console.log('catalog_attr_set :'+name+', '+val.toSource());
////          for (var stl in val)
////            if (stl.indexOf('style')==0) pn.style[stl.substr(6)] = val[stl];
////          for (var stl in val) {
////            if (stl.indexOf('style')==0 && stl.length>6) pn.style[stl.substr(6)] = val[stl];
////            if (stl.indexOf('style')==0 && stl.length>6) {
////              pn.setAttribute('style',pn.getAttribute('style')+stl.substr(6)+':'+val[stl]+';');
////              console.log('catalog_attr_set_1 :'+name+', '+pn.getAttribute('style')+';'+stl.substr(6)+val[stl]+':');
////            }
////          }
//          for (var stl in val.style) pn.style[stl] = val.style[stl];
        //        }
        if (pn) {
          var rollback_info = RollbackInfo.get(pn); // tgt_th[22];
          if (!force_unroll && val && val.style) {
            if (!rollback_info) rollback_info = {};
            for (var stl in val.style) {
              if (!(stl in rollback_info)) rollback_info[stl] = pn.style[stl];
              pn.style[stl] = val.style[stl];
            }
            RollbackInfo.set(pn,rollback_info); // tgt_th[22] = rollback_info;
          }
          if (rollback_info) for (var stl in rollback_info) {
            if (!val || !val.style || !(stl in val.style)) {
              if (rollback_info[stl]===undefined) delete pn.style[stl];
              else pn.style[stl] = rollback_info[stl];
              delete rollback_info[stl];
            }
          }
        }
        if (style_only) return;
//        var request_for_show = 0; // stores boolean values of arguments at calling show_catalog from LSB
        if (val && val.cmd || tgt_th[20] || tgt_th[16].showAlways || tgt_th[16].icon_sticky || tgt_th[16].icon_showAlways || tgt_th[16].systemSticky) {
//        if (val && val.cmd || tgt_th[16].icon_sticky || tgt_th[16].icon_showAlways || tgt_th[16].systemSticky) {
          var sticky = !!((val && val.cmd && val.cmd['STICKY']!==undefined)? val.cmd['STICKY'] : tgt_th[16].systemSticky); // !! for ^
          if (sticky ^ tgt_th[20]) {
            tgt_th[20] = sticky;
            this.idx_reorder(name);
            if (!skip_showing) this.show_catalog_buf(name, null, true); // too slow, issues multiple show_catalog
//            request_for_show |= 0x5;
//            if (show_catalog) this.show_catalog(name); // 'if (show_catalog)' is a patch.
//            if (show_catalog && tgt_th[24]) this.show_catalog(name); // 'if (show_catalog)' is a patch. // tgt[24] means completion of making html, 'auto_list' may call here before making html
//            if (show_catalog && tgt_th[0]) this.show_catalog(name); // 'if (show_catalog)' is a patch.
          }
          // FUCKING JAVASCRIPT!!!, false != null become true.
          var merged_headline = this.view==='headline' && this.merge_bases.bases[name];
          if (tgt_th[24] && sticky != !!tgt_th[16].icon_sticky || tgt_th[16].systemSticky) {
            if (merged_headline) site2[site.nickname].headline_re_icon_sticky(merged_headline, this);
            tgt_th[16].icon_sticky = site2[site.nickname].set_icon(tgt_th[merged_headline? 3 : 0], this.view, 'sticky', tgt_th[16], sticky); // pass always because of lazy draw, pn was not prepared at initial. // tgt_th[24] means completion of making html
          }

          var showAlways = !!(val && val.cmd && val.cmd['SHOW']);
          if (showAlways ^ tgt_th[16].showAlways) { // show
//            tgt_th[16].icon_showAlways = site2[site.nickname].set_icon(tgt_th[0],tgt_th[16].type_html, 'showAlways', tgt_th[16]);
            tgt_th[16].showAlways = showAlways; // for lazy pn generation.
            this.filter_dirtify(tgt_th);
            if (!skip_showing) this.show_catalog_buf(name, true); // too slow, issues multiple show_catalog
//            request_for_show |= 0x3;
//            if (show_catalog) this.show_catalog(name, true); // 'if (show_catalog)' is a patch.
//            if (show_catalog && tgt_th[24]) this.show_catalog(name); // 'if (show_catalog)' is a patch. // tgt[24] means completion of making html, 'auto_list' may call here before making html
//            if (show_catalog && tgt_th[0]) this.show_catalog(name); // 'if (show_catalog)' is a patch.
          }
          if (tgt_th[24] && showAlways != !!tgt_th[16].icon_showAlways) {
            if (merged_headline) site2[site.nickname].headline_re_icon_showAlways(merged_headline, this);
            tgt_th[16].icon_showAlways = site2[site.nickname].set_icon(tgt_th[merged_headline? 3 : 0], this.view, 'showAlways', tgt_th[16], showAlways); // tgt_th[24] means completion of making html
          }
        }
//        return request_for_show;
      }
      Clg.prototype.view_attr_get_str = function(name){
        var val = this.pref.filter.attr_list_obj2[name]; // CAUTION, obj2 are accessed directly.
        if (!val) return null;
        var str = '';
        if (val && val.style) for (var stl in val.style) str += stl + ':' + val.style[stl] + ';';
        var tgt_th = this.threads[name];
        var sticky = !!((val && val.cmd && val.cmd['STICKY']!==undefined)? val.cmd['STICKY'] : tgt_th && tgt_th[16].systemSticky);
        if (sticky) str += 'STICKY;';
        var showAlways = !!(val && val.cmd && val.cmd['SHOW']);
        if (sticky) str += 'SHOW;';
        return str? str : null;
      };
    
      var catalog_filter_query_keyword = (function(){
        function query_1p(kwd, post, domain, unmatch, any_or_all){ // for in-post strict mode, returns if meets THE criteria, including any or all
          for (var i=0;i<kwd.rexps.length;i++) if (any_or_all==(unmatch ^ query_11(kwd, kwd.rexps[i], post, domain))) return any_or_all; // must be == instead of ===
          return !any_or_all;
        }
        function query_1t(kwd, rexp, posts, domain, unmatch){ // for thread, returns if any post meets ANY PARTS of criteria, 'all' is treated as 'any'
          if (kwd.op) if (unmatch ^ query_11(kwd, rexp, posts[0], domain)) return true;
          if (kwd.post) for (var j=1;j<posts.length;j++) if (unmatch ^ query_11(kwd, rexp, posts[j], domain)) return true;
          return false;
        }
//        function query_1(kwd, rexp, posts, domain, th){ // working code.
//          var post_array = Array.isArray(posts);
//          var start = (!post_array || kwd.op)? 0 : 1; // temporarily
//          var end   = ( post_array && kwd.post)? posts.length : 1; // tamporarily
//          for (var j=start;j<end;j++) {
//            var pst = (post_array)? posts[j] : (j==0)? posts : null;
//            if (pst && query_11(kwd, rexp, pst, domain)) return true; // if posts are not exist, th.posts becomes 'undefined'.
//          }
//          return false;
//        }
////        var com2txt_flag = false; // SLOW.
////        var com2txt_rexp = /<[^>]*>|&([#\w\d]*);/g
////        var com2txt_subs = {
////          gt : '>',
////          lt : '<',
////          amp: '&',
////          hellip: '\u2026',
////          larr: '\u2190',
////          rarr: '\u2192',
////          mdash: '\u2014',
////          ndash: '\u2013',
////          qout:'"', // 4chan
////          '#039':"'", // 4chan
////          '#044':',', // 4chan
////          '#44' :',', // 4chan
////        };
////        function com2txt(match,p1,offset,string){
////          if (match[0]==='&') {
////            var retval = com2txt_subs[p1];
////            if (retval) return retval;
////            com2txt_flag = true;
////            if (pref.debug_mode['17']) console.log('not interpreted html: '+match+', '+string);
////          }
////          return ' '; // case of <[^>]*> or not interpreetd.
////        }
//        var pn_com = document.createElement('div');
//        var pn_com = document.createElement('textarea'); // preserve tags in HTML. // slower than div.
        function query_11(kwd, rexp, pst, domain){
          if (kwd.sub)  if (pst.sub  && rexp.test(pst.sub )) return true;
          if (kwd.name) if (pst.name && rexp.test(pst.name)) return true;
//          if (kwd.com)  if (pst.com) {
          if (kwd.com) {
            if (pst.parse_funcs && pst.parse_funcs.type_com==='txt') { //  || pref.test_mode['53']) {
              if (rexp.test(pst.body || pst.com)) return true; // fastest, speed ratio: part/total = 1/1.
            } else {
//              if (!pref.test_mode['54']) {
////                if (!pref.test_mode['57']) {
              var txt = site2[domain].post_com2txt(pst); // 9.4/1.99 for lainchan, 13.07/2.38 for 4chan.
              if (txt && rexp.test(txt)) return true;
//                var txt = site2[domain].post_com2txt(pst); // 9.4/1.99 for lainchan, 13.07/2.38 for 4chan.
//                if (txt) {
//                  if (txt.search(/&[#\w\d]+;/)!=-1 && !pref.test_mode['58']) {
//                    if (pref.debug_mode['17']) console.log('not interpreted html: '+txt);
//                    pn_com.innerHTML = pst.com; // giving com is faster than giving txt.
//                    txt = pn_com[brwsr.innerText];
//                  }
//                  if (rexp.test(txt)) return true;
//                }
//////                } else { // SLOW.
//////                  com2txt_flag = false;
////////                  var txt = pst.com.replace(com2txt_rexp, com2txt); // StringReplaceGlobalRegexpWithfunction is heavy. 19.76/3.33
//////                  var txt = pst.com.replace(/<[^>]*>|&([#\w\d]*);/g, com2txt); // This still outs StringReplaceGlobalRegexpWithfunction.
//////                  if (com2txt_flag) {
//////                    pn_com.innerHTML = pst.com;
//////                    txt = pn_com[brwsr.innerText];
//////                  }
//////                  if (rexp.test(txt)) return true;
//////                }
////              } else {
////                pn_com.innerHTML = (!pref.test_mode['55'])? pst.com :  // TOO SLOW!!!. 53.72/7.65
////                                                            pst.com.replace(/<[^>]*>/g,' ');  // TOO SLOW, FURTHER!!!. 60.51/8.21
////                if (rexp.test(pn_com[brwsr.innerText])) return true;
////              }
            }
          }
          if (kwd.trip) if (pst.trip && rexp.test(pst.trip)) return true;
          if (kwd.id) if (pst.id && rexp.test(pst.id)) return true;
          if (kwd.file) {
            if (pst.filename && rexp.test(pst.filename)) return true;
            if (pst.extra_files)
              for (var k=pst.extra_files.length-1;k>=0;k--)
                if (pst.extra_files[k].filename && rexp.test(pst.extra_files[k].filename)) return true;
          }
          if (kwd.meta) if (pst.no && rexp.test(pst.no)) return true;
          if (kwd.flag) if (pst.country && rexp.test(pst.country)) return true;
          if (kwd.id2) if (pst.id2 && rexp.test(pst.id2)) return true;
          return false;
        }
        return {
          kwd: function(kwd,posts, domain){ // for entire thread
            if (!kwd.use || !kwd.rexps) return true;
            var any_or_all = kwd.match%2===1; // undefined means all [false, true, false, true]
            var unmatch = kwd.match>=2;
            if (kwd.inPost) {
              if (kwd.op) if (query_1p(kwd, posts[0], domain, unmatch, any_or_all)) return true;
              if (kwd.post) for (var j=1;j<posts.length;j++) if (query_1p(kwd, posts[j], domain, unmatch, any_or_all)) return true;
              return false;
            } 
            for (var i=0;i<kwd.rexps.length;i++) if (any_or_all===query_1t(kwd, kwd.rexps[i], posts, domain, unmatch)) return any_or_all;
//            var testval = !!((kwd.match>=2) ^ any_or_all); //      [false, true,  true,false] // value for short-cut
//            for (var i=0;i<kwd.rexps.length;i++) if (testval===query_1(kwd, kwd.rexps[i], posts, domain, th)) return any_or_all;  // BUG, can't handle unmatch if posts search
            return !any_or_all;
//            var retval = kwd.match<2; // working code.
//            if (kwd.match%2===0) { // all
//              for (var i=0;i<kwd.rexps.length;i++) if (retval!==query_1()) return false;
//              return true;
//            } else {
//              for (var i=0;i<kwd.rexps.length;i++) if (retval===query_1()) return true;
//              return false;
//            }
          },
          kwd_1: function(kwd, post, domain){ // for single post
            var val_detect = (kwd.inPost)? kwd.match%2===1 | 0 : 1; // numerify for ===
            var unmatch = kwd.match>=2 |0;
            for (var i=0;i<kwd.rexps.length;i++) if (val_detect===(unmatch ^ query_11(kwd, kwd.rexps[i], post, domain))) return val_detect;
            return !val_detect;
//            var any_or_all = kwd.match%2===1 | 0; // numerify // working, but debugged not so long...
//            var unmatch = kwd.match>=2 | 0;
//            if (kwd.inPost) { // meets whole criteria in a post.
//              for (var i=0;i<kwd.rexps.length;i++) if (any_or_all===(unmatch ^ query_11(kwd, kwd.rexps[i], post, domain))) return any_or_all; // OK.
//              return !any_or_all;
//            } else { // meets ANY match/unmatch
//              for (var i=0;i<kwd.rexps.length;i++) if (              unmatch ^ query_11(kwd, kwd.rexps[i], post, domain)) return true; // OK.
//              return false;
//            }
          },
          kwd_make_result: function(posts, domain, kwd){
            if (!kwd) kwd = pref.filter.kwd;
            var result = [Object.create(posts[0])]; // not to change source for 'posts_search_miss'. // BUG. wrapper just add protptpye to itself, it doesn't take this into account, but patched.
//            var result = [posts[0]]; // can't update omitted info in page mode.
            result[0].search_result = this.kwd_1(kwd, posts[0], domain);
            for (var i=1;i<posts.length;i++) if (this.kwd_1(kwd, posts[i], domain)) result[result.length] = posts[i];
            return (result.length!=1 || result[0].search_result)? result : null;
          },
//          auto_list: function(th, obj, clg){
//            for (var i=0;i<obj.length;i++)
//              if (obj[i].use!==false && this.kwd({use:true, __proto__:obj[i]}, th.posts, th.domain, th)) {
//                var cmds = obj[i].cmds.split(',');
//                for (var j=0;j<cmds.length;j++) {
//                  var idx = cmds[j].indexOf(':');
//                  if (idx===-1) clg.triage_exe(th.key, cmds[j], '', false, null, false); // CAUTION: TRIAGE IS SLOW BECAUSE IT IS DESIGNED TO BE USED AS GUI, it redraws doms and remake objs always.
//                  else clg.triage_exe(th.key, cmds[j].slice(0,idx), cmds[j].slice(idx+1), false, null, false);
//                }
//              }
//          },
        };
      })();
      cataLog.catalog_filter_query_keyword = catalog_filter_query_keyword;
//        for (var i=0;i<kwd.kwds.length;i++) { // OR
//          for (var j=start;j<end;j++) {
//            var pst = (post_array)? posts[j] : (j==0)? posts : null;
//            if (pst!==null) {
//              if (kwd.sub)  if (pst.sub  && kwd.kwds[i].test(pst.sub )) return retval;
//              if (kwd.com)  if (pst.com  && kwd.kwds[i].test(pst.com )) return retval;
//              if (kwd.name) if (pst.name && kwd.kwds[i].test(pst.name)) return retval;
//              if (kwd.trip) if (pst.trip && kwd.kwds[i].test(pst.trip)) return retval;
//              if (kwd.file) {
//                if (pst.filename && kwd.kwds[i].test(pst.filename)) return retval;
//                if (pst.extra_files)
//                  for (var k=pst.extra_files.length-1;k>=0;k--) 
//                    if (pst.extra_files[k].filename && kwd.kwds[i].test(pst.extra_files[k].filename)) return retval;
//              }
//            }
//          }
//        }
//        return !retval;
//      function catalog_filter_query_keyword(str_in){
//        if (!pref.filter.kwd.use) return true;
//        var kwd = pref.filter.kwd.str;
//        if (kwd==='') return true;
//        var str = ((pref.filter.kwd.op       )? str_in[0]+'\n' : '')
//                + ((pref.filter.kwd.op_sub   )? str_in[1]+'\n' : '')
//                + ((pref.filter.kwd.op_name  )? str_in[2]+'\n' : '')
//                + ((pref.filter.kwd.op_file  )? str_in[3]+'\n' : '')
//                + ((pref.filter.kwd.post     )? str_in[4]+'\n' : '')
//                + ((pref.filter.kwd.post_sub )? str_in[5]+'\n' : '')
//                + ((pref.filter.kwd.post_name)? str_in[6]+'\n' : '')
//                + ((pref.filter.kwd.post_file)? str_in[7]+'\n' : '');
//        if (str==='') return true;
//
//        var flag = true;
//        var kwds = kwd.split(' ');
//        for (var i=0;i<kwds.length;i++) {
//          if (kwds[i]==='') continue;
//          kwd = kwds[i];
//          if (!pref.filter.kwd.re) kwd = kwd.replace(/[\.\(\)\[\]\+\?\^\$\{\}]/g,'\\$&').replace(/\*/g,'.*');
//          if (pref.filter.kwd.ci) kwd = new RegExp(kwd,'i');
//          var result = (str.search(kwd)!=-1);
//          if (pref.filter.kwd.match==='unmatch') result = !result;
//          flag = flag & result;
//        }
//        return flag;
//      }
////////      function catalog_filter_query_tag(tags){ // working code.
//////////        if (!pref.filter.tag || filter_tags.length==0) return true;
////////        if (!pref.filter.tag) return true;
////////if (pref.test_mode['22']) {
////////        if (filter_tags.length==0) return false;
////////        if (!tags) return false;
//////////console.log(tags);
////////        for (var i=0;i<tags.length;i++)
////////          for (var j=0;j<filter_tags.length;j++)
////////            if (tags[i].search(filter_tags[j])!=-1) return true;
////////        return false;
////////} else {
////////        return liveTag.search_by_tags(tags);
////////}
////////      }
      Clg.prototype.filter_dirtify = function(tgt_th, updated){
        tgt_th[9][0] = null;
        if (updated) tgt_th[9][2] = null;
      };
      Clg.prototype.filter_test = function(name, tgt_th){
        var retval = tgt_th[9][0];
        return (retval!==null)? retval : (tgt_th[9][0] = this.filter_query_1(name, tgt_th)); // for lazy query
//        return (retval!==null)? retval : this.filter_query(name, tgt_th); // for lazy query
      };
//      Clg.prototype.filter_query = function(name, tgt_th){ // working code
//        return (tgt_th[9][0] = this.filter_query_1(name, tgt_th));
//      };
      Clg.prototype.filter_query_1 = function(name, tgt_th){
        var pf = this.pref.filter; // this.pref && this.pref.catalog && this.pref.filter || pref.filter;
        if (tgt_th[16].showAlways && !pf.kwd.active) return true; // can't set 'tgt_th[16].icon_showAlways' if (tgt_th[0]===null)
//        if (this.pref[this.mode].hide_unwatched && !tgt_th[16].lth.watched) return false;
        if (pf.tag && (!pf.kwd.active || !pf.disTWKA)) if (!this.liveTag.search_by_tags(tgt_th[16].lth)) return false;
        if (pf.time.h) if ((pf.time.h===1? (tgt_th[8][4]||tgt_th[8][0]) : tgt_th[8][1])<=pf.time.time_val) return false;
//        if (pf.time)          if ((tgt_th[8][4]||tgt_th[8][0])<=pf.time_obj6) return false;
//        if (pf.time_creation) if ( tgt_th[8][1]               <=pf.time_obj6) return false;
////        if (pf.time) { // working code.
////          var time = Date.parse(pf.time_str);
//////          if (tgt_th[8][0]<=time && tgt_th[8][4]<=time) return [false];
////          if ((tgt_th[8][4]||tgt_th[8][0])<=time) {tgt_th[9][0] = false; return;}
////        }
        if (pf.list && (!pf.kwd.active || !pf.disLWKA)) { // use [1]
          var val = pref_func.merge_obj5(name, this.pref.filter.list_obj2,null);
          if (val!==null && (val.time===undefined || !(val.time<(val.time<0x10000? tgt_th[8][2] : (tgt_th[8][4]||tgt_th[8][0]))))) return false; // !(val.time is for compatibility for safe, but may be redundant.
//          if (val!==null && (val.time===undefined || !(val.time<(pref.test_mode['167']? tgt_th[8][2] : (tgt_th[8][4]||tgt_th[8][0]))))) return false; // !(val.time is for compatibility for safe, but may be redundant.
//          var retval = this.filter_list(name, tgt_th);
//          if (retval[0]===false) {tgt_th[9] = retval; return tgt_th[9][0];}
        }
//        else if (pf.list_mark_time) [true, get_mark_time(name)];
        if (pf.kwd.active) { // use [2],[3]
          var tgt_posts = tgt_th[16].recent_posts((pf.kwd.post)? (pref[embed_mode].sourceOfSP==='auto'? -1 : null) : 0);
if (pref.test_mode['59']) { // BUG. THIS DOESN'T WORK WITH OTHER FILTERS, NEEDS TO BE SPLITTED. 
          if (!catalog_filter_query_keyword.kwd(pf.kwd, tgt_posts, comf.fullname2dbt(name)[0])) return false;
} else {
          if (!((pf.kwd.fail_list[tgt_th[9][3]] && tgt_th[9][2]===false) ||
                (pf.kwd.suc_list[tgt_th[9][3]]  && tgt_th[9][2]===true ))) {
            tgt_th[9][3] = pf.kwd.timestamp;
            tgt_th[9][2] = catalog_filter_query_keyword.kwd(pf.kwd, tgt_posts, comf.fullname2dbt(name)[0]);
          } else {
            if (pref.debug_mode['18'] && tgt_th[9][2]!==catalog_filter_query_keyword.kwd(pf.kwd, tgt_posts, comf.fullname2dbt(name)[0])) console.log('BUG, test59, '+name);
          }
          if (!tgt_th[9][2]) return false;
}
//          else if (pref[embed_mode].popup2==='sr' || pref[embed_mode].popup2==='srpv') tgt_th[10] = catalog_filter_query_keyword.kwd_make_result(tgt_posts);
        }
        return true;
      }

////      var pop_up_delay_id = {}; // working code
////      function pop_up_delay(e,name){
////        if (pref[embed_mode].popup2==='no' || pref[embed_mode].popup2==='sr' && !pref.filter.kwd.active) return; 
//////        if (threads[name][0].style.width=='' && threads[name][0].style.height=='' && pref.catalog_no_popup_at_expanded) return;
////        if (pref.catalog_popdown=='imm' || pop_up_status[name]) pop_up_op(e,name); // patch
////        else {
////          if (pop_up_delay_id[name]) clearTimeout(pop_up_delay_id[name]);
////          else { // init
////            threads[name][0].addEventListener('mousemove' , threads[name][2][1]);
////            threads[name][6] = function(){clearTimeout(pop_up_delay_id[name]);};
////            threads[name][0].addEventListener('mouseout'  , threads[name][6]);
////          }
////          pop_up_delay_id[name] = setTimeout(function(){pop_up_op(e,name);},pref.catalog_popup_delay);
////        } 
////      }
////      function pop_up_op(e,name){
////        if (pop_up_status[name]) {
////          pop_keep_event(name);
////          return;
////        }
////        var ch = threads[name][0];
////        if (pop_up_delay_id[name]) {
////          ch.removeEventListener('mousemove' , threads[name][2][1]);
////          ch.removeEventListener('mouseout'  , threads[name][6]);
////          delete pop_up_delay_id[name];
////        }
////        var pn = pop_up_set_contents(null, pref[embed_mode].popup2, name);
////        if (pn) {
////          site2['DEFAULT'].popups_posts.set_pos(pn,e);
////          site.popup_body.appendChild(pn);
////        }
////        if (pref.catalog_popup_size_fix) { 
////          pn.style.width  = pn.offsetWidth + 'px';
////          pn.style.height = pn.offsetHeight + 'px';
////        }
////        pop_up_status[name] = [null, function(){pop_down_event(name);}, function(){pop_keep_event(name);}, ch, pn];
////        if (pref[embed_mode].popup2==='chart') {
////          if (chart_obj && pref3.stats.use) {
////            pop_up_status[name][5] = new chart_obj.PostChart(pn,[name]); // must be after appendChild to draw. (just to call destroy?)
////          } else pn.innerHTML = 'Statistics needs to be activated.';
////        }
////        if (pref.catalog_popdown=='imm') {
////          if (document.documentElement.clientHeight/2-e.clientY>0) {
////            if (parseInt(pn.style.top.replace(/px/,''),10) + pn.offsetHeight > document.documentElement.clientHeight) {
////              if (pn.offsetHeight > document.documentElement.clientHeight) pn.style.top = '0px';
////              else {pn.style.top = null; pn.style.bottom = '0px';}
////            }
////          } else if (parseInt(pn.style.bottom.replace(/px/,''),10) + pn.offsetHeight > document.documentElement.clientHeight) {pn.style.bottom = ''; pn.style.top = '0px';}
////        } else {
////          pn.addEventListener('mouseover', pop_up_status[name][2], false);
////          pn.addEventListener('mouseout', pop_up_status[name][1], false);
////        }
////        ch.addEventListener('mouseout', pop_up_status[name][1], false);
////      }
////      function pop_keep_event(name){
////        if (pop_up_status[name][0]) {clearTimeout(pop_up_status[name][0]);pop_up_status[name][0]=null;}
////      }
////      function pop_down_event(name){
////        if (!pop_up_status[name][0]) pop_up_status[name][0] = setTimeout(function(){pop_down_op(name);},(pref.catalog_popdown=='imm')? 0 : pref.catalog_popdown_delay);
////      }
////      function pop_down_op(name){
////        if (pop_up_status[name][5]) pop_up_status[name][5].destroy();
//////        if (pop_up_status[name]==undefined) return;
////        var ch = pop_up_status[name][3];
////        var pn = pop_up_status[name][4];
////        ch.removeEventListener('mouseout' , pop_up_status[name][1], false);
////        if (pref.catalog_popdown!='imm') {
////          pn.removeEventListener('mouseover', pop_up_status[name][2], false);
////          pn.removeEventListener('mouseout' , pop_up_status[name][1], false);
////        }
////        if (threads[name][12]) threads[name][12] = remove_open_new_thread_event(threads[name][12]);
////        pn = cnst.div_destroy(pn,true);
////        delete pop_up_status[name]; //prevent memory leak.
////      }
      Clg.prototype.pop_up_set_contents = function(pn, kind, name, kwd){
        var flag_search = kind==='sr' || kind==='srpv';
        var posts_active = kwd || pref.filter.kwd.posts_active;
        var tgt_th = this.merge_bases.bases[name] || this.threads[name];
        var posts = tgt_th[16].recent_posts(flag_search && posts_active && pref[embed_mode].sourceOfSP==='auto'? -1 : null);
        var th = {posts: posts, __proto__:posts[0]};
//        var th = {posts: threads[name][16].recent_posts(flag_search && posts_active && pref[embed_mode].sourceOfSP==='auto'? -1 : null),
////          type_data: 'json', // BUG, lth.ta.posts may contain different type of data, see archiver.store_th_to_mem and archiver.check_deleted_posts. // NEED TO BE MODIFIED
//          __proto__:threads[name][7] || threads[name][16]};
////        var th = threads[name][7] || threads[name][16];
////        var th = threads[name][7];
if (!pref.test_mode['49'] && posts_active && th) {
        format_html.prepare_html_prep_posts(th); // REDUNDANT for 2nd times or later.
        var search_result = (flag_search)? catalog_filter_query_keyword.kwd_make_result(th.posts, th.domain, kwd) : null; // lazy
}
        if (kind==='sr' && !search_result) return;
        if ((this.view==='page' || this.view==='thread') && (kind==='pv' || kind==='srpv' && !search_result)) return;
//        if (!pn) pn = cnst.init('pop:border:1px solid blue');
//        if (kind!=='chart') {
//////if (!pref.test_mode['49']) {
////          if (th) {
            var lth = th.lth;
            th = (kind==='dp')? {posts:[{search_result:false, __proto__:th.posts[0]}].concat(lth && lth.pd || []), __proto__:th} :
                 (kind!=='pv' && search_result && {posts:search_result, __proto__:th}) || (th.posts = th.posts.slice(), th); // slice for wrapping; th contains lth.ta.posts itself when posts are less than required.
            if (kind==='dp' || !(kind!=='pv' && search_result)) format_html.prepare_html_prep_posts(th); // REDUNDANT for 2nd times or later.
//            insert_thread_prepare_html_lazy(threads[name], !threads[name][0], false, true); // is this the better? not debugged yet.
//            for (var i=0;i<th.posts.length;i++) th.posts[i] = Object.create(lth.q && lth.q[th.posts[i].no] || th.posts[i]); // wrap for re-assining pn, otherwise this causes a problem when popupX in embed_mode===page. // Backlink tracking system tries to add backlinks to lth.q[no].pn instead of th.posts[i].pn. lth.q stores oldest shown pn, while th.posts stores newest loaded posts. This might ought to be consolidated, but I haven't resolved how it should be handled. // BUG, THIS DOESN'T WORK ALSO, BECAUSE th.posts[i] is wrapped but the function popups_add tries to add backlinks to thq[no] directly.
            site2[th.domain].popups_prep(lth); // lth instead of th, but ok
            for (var i=0;i<th.posts.length;i++) th.posts[i] = Object.create(th.posts[i]); // wrap for re-assining pn, otherwise this causes a problem when popupX in embed_mode===page.
            var pftnp = this.pref.INST.page.thumb;
            var pn_result = site2[site.nickname].page_json2html3(th, kwd, {}, null, kwd, pftnp[pftnp.size]);
            site2['DEFAULT'].check_reply.set_own_posts(th); // must before add_you in prepare_html_post
            for (var i=0;i<th.posts.length;i++) cataLog.format_html.prepare_html_post(th, th.posts[i], lth.q);
//            if (!pref.test_mode['98'] && pref[embed_mode].popup) format_html.prep_anchor_links(pn_result, th);
//            if (!pref.test_mode['98'] && pref[embed_mode].popup) site2[th.domain].popups_add(th.posts, th, !kwd); // !kwd for test_mode['121'] // make backlink data beforehand. This prepares html for thq[no] in vain, but acceptable. This is required after prep_anchor_links and before format_pn, because internal function popups_href2dbtp requires thread.no in href, and the function backlink() in format_pn will add backlinks according to the data which is generated by popups_add.
//            if ((search_result || kind==='dp') && th.posts[0].search_result===false) site2[th.domain_html].update_posts0_class(th.posts[0].pn, th.posts[0].search_result);
//            for (var i=0;i<th.posts.length;i++) site2[th.domain_html].format_pn(th.posts[i].pn, lth && lth.q && lth.q[th.posts[i].no], null, th.posts[i], th); // SHOULD CHANGE TO USE prepare_html_post to mark editing and consolidate control flow.
            if (pref[embed_mode].mark_new_posts) this.view_update_draw_merged(th);
//            if (!pref.test_mode['98'] && pref[embed_mode].popup) site2[th.domain].popups_add(th.posts, th, false, false, !kwd); // !kwd for test_mode['121']
            if (kwd && !th.posts[0].search_result) Array.prototype.forEach.call(pn_result.getElementsByClassName('CatChan_search_miss'), function(v){v.style.display = 'none';});
////            pn.name = th.key; // obsolete
//            gGEH.pns_all_keys.set(pn,th.key);
////          } else pn_result = document.createTextNode('You must be set to store posts at least 1, see \'Catalog\' tab in settings.');
          pn.appendChild(pn_result);
          var clg = {threads:{}, pref:pref, noBlur:true, view:'page', __proto__:Clg.prototype};
          clg.image_resize1_onload_bound = clg.image_resize1_onload.bind(clg);
          clg.image_resize2_onload_bound = clg.image_resize2_onload.bind(clg);
          var tgt_th = clg.threads[name] = {0:pn_result}; // , 22:null};
          Clg.prototype.insert_thread_format_th_pn.call(clg, tgt_th, {pn:pn_result, key:name, __proto__:th}, true); // key for merged thread, whose key is merge.ths[0].key
//          Clg.prototype.view_attr_set.call(obj, name, true);
////          catalog_attr_set(name,pn.childNodes[0]);
////        }
        return pn;
      }

//      var page_delim = [];
//      for (var i=0;i<site.max_page;i++) page_delim[i] = null;
//      var page_delim_idx = [];

      var load_list = {refresh: {key:'',    idx:0, tgts:null, mutex:true, use_cache:false, from_auto:false, page_check:true },
                       ondemand:{key:'odl', idx:0, tgts:null, mutex:true, use_cache:false, from_auto:false, page_check:false},
                       tag:     {key:'tag', idx:0, tgts:null, mutex:true, use_cache:true,  from_auto:false, page_check:false}};
      var filter_tags_refresh_mem = {};
      var refresh_use_cache = false;
//      Clg.prototype.prep_reserved_tags = function(set,idx, pickup_only){ // working code
//        var tags = pref.catalog_board_list_obj[idx] && pref.catalog_board_list_obj[idx][0].tags || null;
////        var keys = pref.catalog_board_list_obj[idx];
////        for (var i=1;i<keys.length;i++) {
////          var dbt = cnst.name2domainboardthread(keys[i].key,true);
////          if (!dbt[2] && site2[dbt[0]].nativeVirtualBoard) {
////            if (!tags) tags = [];
////            tags[tags.length] = '#'+dbt[1].slice(1,-1);
////          }
////        }
//        if (tags && pickup_only) tags = tags.filter(function(v){return v[0]==='#' && v[1]==='#';});
////        if (tags && pickup_only) for (var i=tags.length-1;i>=0;i--) if (tags[i][1]!=='#') tags.splice(i,1);
//        return (tags)? this.liveTag.tags_reserved_init(tags,set) : null;
//      };
////      function prep_reserved_tags(set,idx){ // working code.
////        var tags = pref.catalog_board_list_obj[idx][0].tags || null;
////        if (tags) {
////          tags = tags.slice();
////          liveTag.set_reserved_tags(tags,set);
////          if (set) liveTag.reserved = (tags.length!==0)? tags : null;
////        }
////      }
      Clg.prototype.make_refresh_list_from_watch_list = function(){
        var tgts = []
        var obj2 = this.pref.filter.watch_list_obj2;
        var obj = obj2[':REV'];
        for (var db in obj) {
          var tgts_db = [];
          for (var t in obj[db]) if (obj2[db+t].time>0) {
            if (obj2[db+t].cmd && obj2[db+t].cmd['PRUNED'] && this.threads[db+t]===undefined) {
              var idx = db.indexOf('/');
              var domain = db.slice(0,idx);
              var board = db.slice(idx);
              this.insert_thread_dummy(domain, board, t);
              if (pref.archive.IDB.auto_restore_watch) restore_th_from_IDB(domain, board, t, this);
            }
            else tgts_db[tgts_db.length] = db+t;
          }
          if (tgts_db.length>=pref[this.mode].refresh_src_th) tgts[tgts.length] = db;
          else tgts = tgts.concat(tgts_db);
        }
//        for (var db in obj) if (Object.keys(obj[db]).length>=pref.float.refresh_src_th) tgts[tgts.length] = db;
//        else for (var t in obj[db]) tgts[tgts.length] = db+t;
        return {tgts:tgts};
//        var obj = this.pref.filter.watch_list_obj2;
//        for (var name in obj) if (name!==':REV') tgts[tgts.length] = name;
//        return {tgts:tgts};
      };
      Clg.prototype.make_refresh_list = function(sel,bookmark_list_str, refresh){ // , pickup_only){
        var tgts = [];
        var tgt_domains = {};
        if (sel==0 && site2[site.nickname].catalog_board_list_obj_dynamic) site2[site.nickname].catalog_board_list_obj_dynamic();
        var blist = pref.catalog_board_list_obj[sel].slice();
        var domains = blist[0].domains_for_all_boards;
        if (domains) {
          for (var i=0;i<domains.length;i++) {
            var domain = domains[i];
            if (!site3[domain].boards) return function(callback){ // singlelined because this may cause infinite loop if reading boards_json is failed.
              site2[domain].get_boards_json('refresh_boards_json',callback,false,health_indicator);};
//            if (!site3[domain].boards) {
//              site2[domain].get_boards_json('refresh_boards_json',reentry_func,false,health_indicator);
//              return tgts; // singlelined because this may cause infinite loop if reading boards_json is failed.
//            }
          }
          for (var i=0;i<domains.length;i++)
            if (refresh && site2[domains[i]].utilize_boards_json && pref.pref2[domains[i]].utilize_boards_json && pref.pref2[domains[i]].utilize_boards_json_domain)
              tgt_domains[domains[i]] = null;
            else for (var j in liveTag.mems[domains[i]]) blist[blist.length] = liveTag.mems[domains[i]][j];
        }
        var bds_by_tags = this.liveTag.boards_picked_up_by_tags(this,sel);
        for (var bd in bds_by_tags) {
          for (var j=1;j<blist.length;j++) if (blist[j].key===bd) break;
          if (j==blist.length) blist[blist.length] = liveTag.mems.getFromName(bd); // scan.add_board(bd);
        }
        if (bookmark_list_str) tgts = bookmark_list_str.replace(/\s*\/\/.*$/mg,',').replace(/\n/g,',').replace(/,,+/g,',').replace(/^,/,'').replace(/,$/,'').split(',');
        if (tgts[0]==='') tgts = [];
//        var page_str = (pref.catalog.design==='page')? 'p' : 'c';
        var j = 0;
        while (blist.length>1) {
          for (var i=1;i<blist.length;i++) {
            var key = blist[i].key.replace(/\s/g,''); // replace... is redundant
            var dbt = cnst.name2domainboardthread(key,true);
            var max_page_bd = pref.catalog_max_page_select==='auto' && liveTag.mems[dbt[0]] && liveTag.mems[dbt[0]][dbt[1]] && liveTag.mems[dbt[0]][dbt[1]].pgs || pref.catalog_max_page;
            if (dbt[2]) {
              if (j==0) tgts.push(key);
              else blist.splice(i--,1)
            } else if ((!blist[i].max_page || j<blist[i].max_page) && j<max_page_bd) tgts.push(key+ ((pref.catalog.catalog_json)? 'q':'p') +j);
            else blist.splice(i--,1)
          }
          j++;
        }

////        for (var j=0;j<pref.catalog_max_page;j++) // working code.
////          for (var i=1;i<blist.length;i++) {
//////            if (blist[i]['key'].search(/[^\/]*\/[^\/]*\/[0-9]+/)!=-1) {if (j==0) tgts.push(blist[i]['key']);} // working code.
////            var key = blist[i].key.replace(/\s/g,''); // replace... is redundant
////            if (comf.fullname2dbt(key)[2]) {if (j==0) tgts.push(key);}
////            else if (!blist[i].max_page || j<blist[i].max_page) tgts.push(key+ ((pref.catalog.catalog_json)? 'q':'p') +j);
////          }
//////        if (remove_attr) for (var i=0;i<tgts.length;i++) tgts[i] = tgts[i].replace(/!.*/,'');
//////        for (var i=tgts.length-1;i>=0;i--) { // working code
//////          var dbt = cnst.name2domainboardthread(tgts[i],true);
//////          if (tgts[i].indexOf(dbt[1])==-1) tgts[i] = dbt[0] + dbt[1] + tgts[i];
//////          else if (tgts[i].indexOf(dbt[0])==-1) tgts[i] = dbt[0] + tgts[i];
//////        }
        return {tgts:tgts, tgt_domains:tgt_domains};
      };
      function trim_list(tgts,embed_init, mode){
        if (pref.catalog.board.ex_list) {
          for (var i=tgts.length-1;i>=0;i--) {
            var val = pref_func.merge_obj5(tgts[i],pref.catalog.board.ex_list_obj2,null);
            if (val) tgts.splice(i,1);
          }
        }
        if (pref.catalog.design==='catalog' || (pref.catalog.design==='auto' && mode==='catalog') || mode==='chart') {
          for (var i=tgts.length-1;i>=0;i--) {
            var dbt = cnst.name2domainboardthread(tgts[i],true);
            if ((site2[dbt[0]].trim_list!=='no' || mode==='chart') && tgts[i].search(/\/[pq][0-9]*/)!=-1) {
//            if ((dbt[0]==='8chan' ||dbt[0]==='4chan') && tgts[i].search(/\/p[0-9]*/)!=-1) {
//            if (dbt[0]==='8chan' && tgts[i].search(/\/p[0-9]*/)!=-1) {
//              if (tgts[i].search(/\/p0$/)!=-1 && (!embed_init || dbt[1]!=site.board)) tgts[i] = tgts[i].replace(/p0/,(pref.catalog.catalog_json)? 'j0' : 'c0');
              if (tgts[i].search(/\/[pq]0$/)!=-1 && (!embed_init || dbt[1]!=site.board || dbt[0]!=site.nickname || (site2[dbt[0]].trim_list==='force_init' || pref3.stats.use)))
                tgts[i] = tgts[i].replace(/[pq]0$/,(pref.catalog.catalog_json || site2[dbt[0]].trim_list==='force_init')? 'j0' : 'c0');
              else if (tgts[i].search(/\/[pq]\d+$/)!=-1) tgts.splice(i,1); // ratain thread
            }
          }
        }
        if (mode==='page' && embed_init) { // patch
          if (pref.liveTag.use) {
            var bds = {};
            for (var i=0;i<tgts.length;i++) {
              var dbt = cnst.name2domainboardthread(tgts[i],true);
              bds[dbt[0]+dbt[1]+((pref.catalog.catalog_json)? 'j0':'c0')] = null; // add a catalog for scan all thread.
            }
            for (var i in bds) tgts[tgts.length] = i;
//            cataLog.scan_init('init_tag',bds, {tag_only:true});
          }
//          if (pref.liveTag.use) { // working code.
//            for (var i=0;i<tgts.length;i++) {
//              var dbt = cnst.name2domainboardthread(tgts[i],true);
//              scan.list_nup.add_board(dbt[0]+dbt[1]);
//            }
//          }
          if (tgts[0] && tgts[0].search(site.nickname+site.board+'[pq]0')===0) tgts.splice(0,1);
////          var site_db = site.nickname+site.board;
////          for (var i=0;i<tgts.length;i++) { // working code, but obsolete.
////            if (tgts[i].indexOf(site_db)===0) {
////              if (tgts[i].search(site_db+'[pq]0')===0) tgts.splice(i--,1)[0];
////              else if (pref[mode].load_on_demand) threads_idx[threads_idx.length] = ':DL:'+tgts.splice(i--,1)[0];
////            }
////          }
          if (pref[mode].load_on_demand) pClg.drawn_idx = 0;
        }
//        if (mode==='thread') { // patch
//          if (board_sel.selectedIndex===0) {
//            if (embed_init) tgts.splice(0,1);
//            else tgts = [site.nickname + site.board + site.no];
//          }
//        }
        for (var i=tgts.length-1;i>=0;i--) {
          var dbt = comf.name2dbt(tgts[i]);
          var url = site2[dbt[0]] && site2[dbt[0]].make_url4(dbt, pref.catalog.design!=='page' && (pref.catalog.order.ordering!==0 || pref.liveTag.from!=='none')); // trim for 4chan. // !=='none' IS ALWAYS TRUE, WHY... // site2[dbt[0]] check for testing new domains
          if (!url) tgts.splice(i,1);
          else if (url[2] && url[2]!==tgts[i]) {
            if (tgts.indexOf(url[2])!=-1) tgts.splice(i,1);
            else tgts[i] = url[2];
          }
//          if (!site2[dbt[0]].make_url4(dbt)) tgts.splice(i,1);
        }
        return tgts;
      }


//      function remove_threads_events(name){
//        if (pref.test_mode['127']) if (threads[name][0]) threads[name][0].removeEventListener('mouseover', threads[name][2][0], false);
//        if (pref.test_mode['104']) comf.dom_removeEventListener(threads[name][5]);
//        if (threads[name][11]) threads[name][11] = remove_open_new_thread_event(threads[name][11]);
//      }
//      function remove_thread(name, pn_only){
//        var ref = threads_idx.indexOf(name);
//        if (name.substr(0,4)!==':DL:') {
//          if (!threads[name]) return;
//          var dbt = comf.fullname2dbt(name);
//          var lth = liveTag.mems.getFromName(name);
//          if (!pn_only) if (lth.q) site2[dbt[0]].popups_release_all(lth, {}); // remove cross thread/board links.
////          if (!pn_only) if (threads[name][16].popups) for (var i in threads[name][16].popups) site2[dbt[0]].popups_release(lth, i); // remove cross thread/board links.
//          remove_threads_events(name);
//          if (threads[name][1]) {
//            if (pop_up_status[name]) pop_down_op(name);
////            if (threads[name][0].parentNode===triage_parent) { // working code.
//////              if (pref.catalog_expand_with_hr && !embed_catalog) show_catalog_hr(name,'remove');
//////              triage_parent.removeChild(threads[name][0]); // for 4chan's native
////              catalog_obj2.func_hide(name);
////            }
//            if (threads[name][0] && threads[name][0].parentNode===triage_parent) pClg.func_hide(name); // parent check is for on_demand_draw
//            site2['DEFAULT'].update_posts_merge_bases.remove_th(threads[name][16], true);
////            if (threads[name][0] && threads[name][0].parentNode===triage_parent || pref[embed_mode].merge) // parent check is for on_demand_draw and merging threads.
////              catalog_obj2.func_hide(name);
//            if (ref<pClg.drawn_idx) pClg.drawn_idx = 0;
//          }
////          if (threads[16].archiveFile) {
////            if (threads[16].archiveFile==='IDB') {
////            } else {
////              var files = threads[16].archiveFile;
////              while (files) {
////                for (var i in files) if (files[i].url) window.URL.revokeObjectURL(files[i].url);
////                files = Object.getPrototypeOf(files);
////              }
////            }
////          }
////          if (lth) {
////            delete lth.th;
////            delete lth.ta;
////            delete lth.pd;
////          }
//        }
//        if (pn_only) {
//          threads[name][0] = false;
//          threads[name][1] = false;
//          threads[name][16].th = threads[name][7];
//          threads[name][16].posts = null;
////          if (!threads[name][16].th) threads[name][16].th = threads[name][16];
//          if (threads[name][24]) threads[name][24][0] = null;
//          return;
//        }
//        delete threads[name]; // remove ':DL:' also
//if (pref.debug_mode['2']) console.log('removed: '+name);
////        for (var i=threads_idx.length-1;i>=0;i--) if (threads_idx[i]===name) {threads_idx.splice(i,1);break;}
//        if (ref>=0) threads_idx.splice(ref,1);
//if (pref.test_mode['95'] && site.nickname==='dist') site2[site.nickname].testPoster(name);
//      }
//      cataLog.remove_thread = remove_thread;
//////      function remove_thread(name){ // working code.
//////        if (name.substr(0,4)!==':DL:' && threads[name]) { // BUG. SHOULD WORK WITHOUT CHECKING threads[name]
//////          var dbt = comf.fullname2dbt(name);
//////          if (threads[name][16].popups) for (var i=0;i<threads[name][16].popups.length;i++) site2[dbt[0]].popups_release(threads[name][16].popups[i]);
////////        if (name.substr(0,4)!==':DL:') {
////////          if (threads[name][16].th_destroy) threads[name][16].th_destroy(threads[name][0], threads[name][16].parse_funcs);
//////          threads[name][0].removeEventListener('mouseover', threads[name][2][0], false);
////////          threads[name][0].removeEventListener('click', threads[name][5], false);
////////          for (var i=0;i<threads[name][5].length;i++) threads[name][5][i].removeEventListener('click', click_thread, false);
//////          comf.dom_removeEventListener(threads[name][5]);
//////          if (threads[name][11]) remove_open_new_thread_event(threads[name][11]);
//////          if (threads[name][1]) {
////////            if (threads[name][12]) remove_open_new_thread_event(threads[name][12]);
//////            if (pop_up_status[name]) pop_down_op(name);
////////            triage_parent.removeChild(threads[name][0]);
//////            if (threads[name][0].parentNode===triage_parent) triage_parent.removeChild(threads[name][0]); // for 4chan's native
//////          }
////////          if (threads[name][17][0][0].length!=0) liveTag.remove_tags_in_th(threads[name][17][0][0], name);
////////          if (threads[name][17][1][0].length!=0) liveTag.remove_tags_in_th(threads[name][17][1][0], name);
////////          liveTag.remove_tags_in_th(name);
//////        }
//////        delete threads[name]; // remove ':DL:' also
//////if (pref.debug_mode['2']) console.log('removed: '+name);
//////        for (var i=threads_idx.length-1;i>=0;i--) if (threads_idx[i]===name) {threads_idx.splice(i,1);break;}
//////      }
////////      function remake_boards(){
////////        boards = {};
////////        for (var name in threads) {
////////          var dbt = cnst.name2domainboardthread(name,true);
////////          boards[dbt[0]+dbt[1]] = null;
////////        }
////////      }
      function catalog_refresh(refresh, embed_init, from_auto, from_switch) {
        return pClg.refresh(refresh, embed_init, from_auto, from_switch);
      }
      Clg.prototype.refresh = function(refresh, embed_init, from_auto, from_switch) {
        pref4.refresh.count++;
        Footer.refresh_start();
        for (var j=0;j<gClg.Clgs.length;j++) gClg.Clgs[j].footer.timestamp_set();
//        this.footer.timestamp_set();
        for (var j=0;j<gClg.Clgs.length;j++) gClg.Clgs[j].idx_refresh(this); // update timestamps in footer
//        this.idx_refresh(this);
//        gClg.footer.refresh_start();
        if (!pref.test_mode['67'] && !embed_init) archiver.refresh_start();
        if (!from_switch && !embed_init && pref.catalog.auto_save_filter_at_refresh) this.onchange_funcs['save']();
        var result = this.refresh_1(embed_mode, refresh && this, embed_init, from_auto, 'refresh_'+this.serialNo, this.pref[this.mode].board_list_sel, this.pref.filter.bookmark_list_str, false, undefined, from_switch);
        if (typeof(result)==='function') result(function(){catalog_refresh(refresh, embed_init, from_auto);});
        else this.refresh_tgts = result;
      }
      Clg.prototype.refresh_1 = function(mode, refresh, embed_init, from_auto, scan_name, sel, bookmark_list_str, get_board_list, callback, from_switch) {
        var tgts_bg = typeof(refresh)!=='object' || refresh.pref[refresh.mode].refresh_src.slice(0,2)==='bg'? this.make_refresh_list(sel, bookmark_list_str, refresh /*, !embed_init && !from_switch*/) : null;
        var tgts_wl = typeof(refresh)==='object' && refresh.pref[refresh.mode].refresh_src.slice(-2)==='wl'? this.make_refresh_list_from_watch_list() : null;
        var tgts_all = tgts_bg && tgts_wl? (tgts_bg.tgts = tgts_bg.tgts.concat(tgts_wl.tgts), tgts_bg) : tgts_bg || tgts_wl;
//        var tgts_all = (typeof(refresh)==='object' && refresh.pref[refresh.mode].refresh_by_watchlist)? this.make_refresh_list_from_watch_list()
//          : make_refresh_list(sel, bookmark_list_str, refresh, !embed_init && !from_switch);
        if (typeof(tgts_all)==='function') return tgts_all; // must be reentried.
        var tgts = tgts_all.tgts;
        if (get_board_list) {
          var bds = {};
          for (var i=0;i<tgts.length;i++) {
            var dbt = comf.name2domainboardthread(tgts[i],true);
            if (dbt[2][0].search(/^[pqcj]/)!=-1) bds[dbt[0]+dbt[1]] = null;
            else bds[dbt[0]+dbt[1]+dbt[2].replace(/^t/,'')] = null; // patch
          }
        }
        if (scan_name && pref3.stats.use && pref.stats.auto_acquisition_all) stats.register_auto_acquisition(sel);
        tgts = trim_list(tgts,embed_init, mode);
        var priority = (!refresh)? 0 : (from_auto)? 2:4;
        var hi = refresh && refresh.health_indicator || health_indicator;
        var done_list = new scan.DoneList(tgts, tgts_all.tgt_domains);
        if (tgts.length===0) {if (refresh) scan.scan_refresh(hi, done_list, priority); return tgts;}
        if (refresh) if (this.pref.INST.safety.remove && !embed_init) this.clear_threads(pref4.scan.max_threads_at_refresh(this), true);
////////        load_on_demand.release(); // prevent from hanging up.
        if (scan_name) scan_boards.scan_init(scan_name, tgts,
                                             {refresh:refresh, indicator: hi, done_list:done_list,
                                              callback: function(){scan.scan_refresh(hi, done_list, priority);
                                                                   if (callback) callback();},
                                              from_auto:from_auto, load_on_demand:pref[mode].load_on_demand, priority:priority});
        return (get_board_list)? Object.keys(bds) : tgts;
      };
      
////      function catalog_refresh(refresh, embed_init, from_auto, indicator) { // working code.
//////if (pref.debug_mode['0']) console.log(new Date().toLocaleTimeString() + ', refresh: start: ');
//////        set_auto_update();
////        if (pref.filter.time_ago_str_sync_at_refresh) ago_clicked();
////        load_list.refresh.use_cache = !refresh;
////        load_list.refresh.idx = 0;
////        load_list.refresh.mutex = true;
////        load_list.refresh.from_auto = from_auto;
////        load_list.refresh.tgts = trim_list(make_refresh_list(board_sel,pref.filter.bookmark_list),embed_init);
////        if (load_list.refresh.tgts.length===0) return;
////        if (threads_candidates_of_deletion!==null) {
////          for (var name in threads_candidates_of_deletion)
////           if (threads[name] && threads_candidates_of_deletion[name]===((threads[name][8][0]>threads[name][8][4])? threads[name][8][0] : (threads[name][8][4] || threads[name][8][0]))) remove_thread(name);
////          threads_candidates_of_deletion = null;
////////////          remake_boards();
////        }
////        if (refresh && pref.catalog_refresh_clear && !embed_init) catalog_clear_threads(pref.catalog.max_threads_at_refresh, true);
//////        for (var i=0;i<load_list.refresh.tgts.length;i++) load_list.refresh.tgts[i] = [load_list.refresh.tgts[i], from_auto];
////////////        load_on_demand.release(); // prevent from hanging up.
////        if (load_list.refresh.idx<load_list.refresh.tgts.length) {
//////if (!pref.test_mode['15']) scan_boards.scan_init('refresh', load_list.refresh.tgts, {refresh:true, crawler_max:1, indicator:indicator, callback:catalog_refresh_watch});
//////if (!pref.test_mode['15']) scan_boards.scan_init('refresh', load_list.refresh.tgts, {refresh:true, indicator:indicator, callback:catalog_refresh_watch, from_auto:from_auto, load_on_demand:pref[embed_mode].load_on_demand});
////if (!pref.test_mode['15']) scan_boards.scan_init('refresh', load_list.refresh.tgts, {refresh:true, callback:catalog_refresh_watch, from_auto:from_auto, load_on_demand:pref[embed_mode].load_on_demand, priority:(from_auto)?2:4});
////else {
//////          health_indicator.shift('limegreen','0');
////          load_list.refresh.indicator = indicator;
////          get_page(load_list.refresh);
////}
////        } else catalog_refresh_watch();
//////        scan_boards.scan_init('refresh_tag',filter_tags_refresh_mem,(refresh)? 0 : pref.scan.lifetime*60, catalog_refresh_watch);
////if (pref.test_mode['22'])
////        scan_boards.scan_init('refresh_tag', filter_tags_refresh_mem, {lifetime:((refresh)? 0 : pref.scan.lifetime*60), cache_write:true});
////      }
////      
////////      function catalog_refresh_watch() { // working code.
//////////console.log('test');
////////        var tgts = {};
////////        for (var name in threads) if (threads[name][21]) tgts[name] = null;
////////        scan_boards.scan_init('refresh_watch', tgts, {callback:catalog_refresh_gather_info, refresh:true});
//////////console.time('refresh_watch');
//////////        scan_boards.scan_init('refresh_watch', tgts, {callback:catalog_refresh_gather_info, force_json:pref.catalog.order.find_sage_in_8chan});
////////      }
////////      cataLog.catalog_refresh_watch = catalog_refresh_watch;
////      function catalog_refresh_watch() { // working code, @safe1080
////        catalog_refresh_gather_info();
////      }
////      var missing_info = {};
////      function catalog_refresh_gather_info() {
////        var tgts = missing_info;
////        missing_info = {};
//////////////console.timeEnd('refresh_watch');
////////////        var tgts = {};
////////////        for (var name in threads) {
////////////          if (!pref.filter.time && !pref.filter.list && !threads[name][9][0]) continue;
//////////////          var dbt = comf.name2domainboardthread(name);
//////////////          if (dbt[0]==='8chan' && pref.catalog.indexing==4 && threads[name][23]) tgts[name] = true; // get time of sage post in 8chan from json.
//////////////          if (pref.catalog.indexing==4 && threads[name][23] && threads[name][23].time_posted===null) tgts[name] = true; // get time_posted
////////////          if (pref.catalog.indexing==4 && threads[name][8][4]===undefined) tgts[name] = true; // get time_posted
//////////////          if (!threads[name][9][0]) continue;
//////////////          if (dbt[0]==='8chan' && pref.catalog.order.sticky!=='dont_care' && threads[name][20]===null) tgts[dbt[0]+dbt[1]] = true; // get sticky in 8chan from json.
////////////        }
//////////////console.log(tgts);
////        scan_boards.scan_init('refresh_watch', tgts, {tgt_raw: true,
////                                                      callback: function(){scan.scan_refresh(health_indicator);}});
//////                                                      callback: (pref.liveTag.utilize_boards_json)? catalog_refresh_boards : catalog_liveTag_scan_boards,
//////                                                      callback_args: (pref.liveTag.utilize_boards_json)? 'refresh_watch' : null});
//////        if (pref.liveTag.utilize_boards_json && embed_mode==='catalog') catalog_liveTag_scan_threads(); // patch for 8chan, boards_json is too heavy.
////      }
////
////      function catalog_refresh_boards() { // patch for 8chan // working code, @safe1069
//////        if (liveTag.mems['8chan']) {
//////          var tgts = scan.list_nup.get_list_board('8chan',true);
////////          if (tgts.length!=0) http_req.get('refresh_watch','8chan,boards_json,boards_json,boards_json',site2['8chan'].url_boards_json(),catalog_refresh_boards_callback,0,true,catalog_liveTag_scan_boards);
//////          if (tgts.length!=0) site2['8chan'].get_boards_json('refresh_watch',catalog_liveTag_scan_boards,true,health_indicator);
//////          else catalog_liveTag_scan_boards();
//////        } else catalog_liveTag_scan_boards();
////        if (scan.list_nup.query_list_board('8chan')) {
////          site2['8chan'].get_boards_json('refresh_watch',catalog_liveTag_scan_boards,true,health_indicator);
////          return;
////        }
////        catalog_liveTag_scan_boards();
////      }
////////////      function catalog_refresh_boards_callback(key,value,callback){
//////////////if (pref.debug_mode['7']) console.log('boards_json:');
////////////        if (value.status==200) {
////////////          var dbt = key.split(',');
////////////          site2[dbt[0]].postprocess_board(value.response);
////////////////          site3[dbt[0]].boards = value.response.boards || value.response; // patch for 8chan. WHY DO THEY CHANGE THE SPEC REPEATEDLY WITHOUT A PARTICULAR REASON??? // working code.
////////////////          site3[dbt[0]].boards_to_scan = null;
////////////////          if (site2[dbt[0]].make_site3_bds) site2[dbt[0]].make_site3_bds();
////////////        }
////////////        if (callback) callback();
////////////      }
////
////      var mutex_wd_liveTag_scan_boards = new MutexWatchdog('scan');
////      function catalog_liveTag_scan_boards(domain) {
////        if (pref.liveTag.use) {
////          if (!mutex_wd_liveTag_scan_boards.get()) {catalog_refresh_end(); return;}
////          if (pref.debug_mode['7']) var d_str = '';
////          var tgts = scan.list_nup.get_list_board(domain);
////////          var tgts = []; // working code.
////////          var time_th = Date.now()-pref.liveTag.pickup_interval*1000;
////////          for (var i in scan.list_nup_boards) {
////////            var dbt = comf.fullname2dbt(i);
////////            if (scan.list_nup_boards[i].time<time_th && (!scan.list_nup_boards[i].max || scan.list_nup_boards[i].max<liveTag.mems[dbt[0]][dbt[1]].max)) {
////////              tgts[tgts.length] = i;
////////              if (pref.debug_mode['7']) d_str += i + ':' +scan.list_nup_boards[i].max+'/'+liveTag.mems[dbt[0]][dbt[1]].max+', ';
////////            }
////////          }
////          if (pref.debug_mode['7']) for (var i=0;i<tgts.length;i++) d_str += tgts[i].key + ':' +tgts[i].read_max+'/'+tgts[i].max+', ';
////          if (Object.keys(tgts).length!=0) {
////            scan_boards.scan_init('scan', tgts, {callback: catalog_liveTag_scan_boards_cont,
////                                                 watchdog: mutex_wd_liveTag_scan_boards.restart.bind(mutex_wd_liveTag_scan_boards),
////////////                                                 crawler_watchdog: true
//////                                                 priority: 1,
////                                                });
////            if (pref.debug_mode['7']) console.log('catalog_liveTag_scan_boards: '+tgts.length+', '+d_str);
////          } else {
////            mutex_wd_liveTag_scan_boards.stop();
////            catalog_liveTag_scan_threads();
////          }
////        } else catalog_liveTag_scan_threads();
////      }
////      function catalog_liveTag_scan_boards_cont() {
////        mutex_wd_liveTag_scan_boards.stop();
////        if (mutex_wd_liveTag_scan_boards.query_req()) catalog_liveTag_scan_boards();
////        else catalog_liveTag_scan_threads();
////      }
////      function catalog_liveTag_scan_cancel() {
////        mutex_wd_liveTag_scan_boards.abort();
////        scan_boards.scan_abort('scan');
////        mutex_wd_liveTag_scan_threads.abort();
////        scan_boards.scan_abort('scan_threads');
////      }
////
////      var catalog_liveTag_scan_threads_delayed_do = DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(catalog_liveTag_scan_threads, 200));
////      var mutex_wd_liveTag_scan_threads = new MutexWatchdog('scan_threads');
////      function catalog_liveTag_scan_threads() {
////        if (!mutex_wd_liveTag_scan_threads.get()) {catalog_refresh_end();return;} // multi entry.
////////        var tgts = []; // working code.
////////        for (var i in scan.list_nup)
////////////          if (scan.list_nup[i]!==null) {
////////////            scan.list_nup[i] = null;
////////////            tgts[tgts.length] = i;
////////////          } else delete scan.list_nup[i];
//////////          if (--scan.list_nup[i]>=0) tgts[tgts.length] = i; // leave nodes of <0 as black listed, patch for 8chan.
////////          if (scan.list_nup[i]>0) tgts[tgts.length] = i;
////        var tgts = scan.list_nup.get_list_thread();
////        if (Object.keys(tgts).length!=0) scan_boards.scan_init('scan_threads', tgts, {callback: catalog_liveTag_scan_threads_cont,
////                                                                                      watchdog: mutex_wd_liveTag_scan_threads.restart.bind(mutex_wd_liveTag_scan_threads),
////////////                                                                                      crawler_watchdog: true
////                                                                                     });
////        else {
////          mutex_wd_liveTag_scan_threads.stop();
////          catalog_refresh_end();
////        }
////      }
////      function catalog_liveTag_scan_threads_cont() {
////        mutex_wd_liveTag_scan_threads.stop();
////        if (mutex_wd_liveTag_scan_threads.query_req()) catalog_liveTag_scan_threads();
////        else catalog_refresh_end();
////      }
////      cataLog.catalog_liveTag_scan_threads = catalog_liveTag_scan_threads;
////      cataLog.catalog_liveTag_scan_threads_delayed_do = catalog_liveTag_scan_threads_delayed_do;

      function catalog_refresh_end(){
        if (pref.notify.favicon || pref.notify.title.notify) notifier.favicon.set(threads);
//        if (pref.liveTag.style) liveTag.refresh_end_proc();
//if (pref.debug_mode['9']) for (var i in scan.list_nup) if (scan.list_nup[i]<=0) console.log('list_nup: '+i+', '+scan.list_nup[i]);
        if (pref.debug_mode['3']) {
          var th_count = 0;
          for (var d in liveTag.mems) for (var b in liveTag.mems[d]) for (var t in liveTag.mems[d][b]) th_count++;
          var ths = {};
          for (var i in liveTag.tags_ci) for (var j of liveTag.tags_ci[i].mems.keys()) ths[j.key] = null;
          console.log('refresh_end: tags_ci+tags: '+Object.keys(liveTag.tags_ci).length+'+'+Object.keys(liveTag.tags).length+', threads:'+th_count+', '+Object.keys(ths).length);
        }
        if (pref[embed_mode].load_on_demand) show_catalog();
      }
      cataLog.catalog_refresh_end = catalog_refresh_end;

////      var mutex_wd_liveTag_scan_ui = new MutexWatchdog('scan_ui'); // working code, @safe1069
////      var liveTag_scan_ui_queue = {};
////      function catalog_liveTag_scan_ui(key,arg) { // arg is {tgts:, options:}
////        if (arg) {
////          if (!liveTag_scan_ui_queue[key]) liveTag_scan_ui_queue[key] = {queue:[], mutex: new MutexWatchdog(key)};
////          liveTag_scan_ui_queue[key].queue.push(arg);
////        }
////        var mutex = liveTag_scan_ui_queue[key].mutex;
////        var queue = liveTag_scan_ui_queue[key].queue;
////        if (!mutex.get()) return; // multi entry.
////        if (queue.length>0) {
////          var tgt = queue.shift();
////          var scan_obj = {
////            callback: catalog_liveTag_scan_ui_cont,
////            callback_args: key,
////            watchdog: mutex.restart.bind(mutex),
////////////            crawler_watchdog: true,
////            __proto__: tgt.options};
////          scan_boards.scan_init(key, tgt.tgts, scan_obj);
////        } else mutex.stop();
////      }
////      function catalog_liveTag_scan_ui_cont(key) {
////        liveTag_scan_ui_queue[key].mutex.stop();
////        catalog_liveTag_scan_ui(key);
////      }

//      function catalog_refresh_gather_info() {
//        if (pref.catalog.order.sticky!=='dont_care') {
//          var tgts = {};
//          for (var name in threads) if (threads[name][20]===null) {
//            var dbt = comf.name2domainboardthread(name);
//            tgts[dbt[0]+dbt[1]] = null;
//          }
//          if (Object.keys(tgts).length!=0) scan_boards.scan_init('refresh_watch', tgts, {force_json:true, callback:re_sort_thread});
//        }
//      }
//      var flag_initial_refresh = (site.whereami==='boards') && pref.catalog.refresh.except_bt;

      if (embed_frame) site2[site.nickname].catalog_frame_prep(pn12);
//      else if (embed_mode==='float') pn12_0.getElementsByTagName('button')[pref.catalog.appearance.initial.state].onclick();
//      else if (embed_mode==='float') {
//        if (pref.catalog.appearance.initial.state==='maximized') pn12_0.childNodes[1].childNodes[3].onclick();
//        else if (pref.catalog.appearance.initial.state==='top') pn12_0.childNodes[1].childNodes[0].onclick();
//        else if (pref.catalog.appearance.initial.state==='bottom') pn12_0.childNodes[1].childNodes[1].onclick();
///      }
      var exe_embed = function(){
//          cnst.tb_prep_for_embed(pn12_0);
          pn12_0.appendChild(pn12_0_4); // for simplest catalog_native_prep
if (!pref.test_mode['160']) site2[site.nickname].catalog_native_prep(pn12_0_4,pn12_0, pClg.health_indicator.pn2, pClg);
        var ths = insert_myself(null,null, true);
        if (pClg.pref.filter.kwd.post) {pref4.search_posts_active_once = false; pClg.pref.filter.kwd.posts_active = false; pClg.filter_kwd_active({target:{name:'filter.kwd.post'}});} // patch for posts scan
//          if (site.whereami==='catalog') site2[site.nickname].clean_up_LS(ths);
          if ((embed_mode==='page' && site2[site.nickname].all_boards && site2[site.nickname].all_boards.indexOf(site.board)!=-1) ||
              (embed_mode==='thread' && pref.thread.env.auto_update_native)) { // BUG(,but patched), this cause conflicts, because insert_myself doesn't take merge into account.
            var observer = new MutationObserver(insert_myself);
//            var observer = new MutationObserver(function(){setTimeout(insert_myself,10);}); // wait native script for 4chan, but doesn't work.
//            observer.observe(triage_parent, {childList: true});
            var observe_tgt = (embed_mode==='page')? ths[0].pn.parentNode : ths[0].pn;
            pClg.observer_myself = { // for merge
              observe: function(){observer.observe(observe_tgt, {childList: true});},
              disconnect: function(){observer.disconnect();}
            };
            pClg.observer_myself.observe();
            if (pref.test_mode['137']) triage.change_mode();
          }
//          if (show_catalog_init_funcs) show_catalog_init_funcs();
          if (pref.filter.kwd.active && (embed_mode==='page' || embed_mode==='catalog')) show_catalog();
          if (pref.catalog_board_list_sel!==0 && !(pref.catalog.board.all_boards && pref.catalog_board_list_sel===pref.catalog_board_list_obj.length-1)) {
            var tgts = pClg.refresh_1(embed_mode, false, false, false, false, pref.catalog_board_list_sel, false, false, null, true);
            var key_me = site.nickname+site.board+((embed_mode==='catalog')? 'c0' : (embed_mode==='page')? 'p0' : site.no);
            var key_me2= site.nickname+site.board+((embed_mode==='catalog')? 'j0' : (embed_mode==='page')? 'q0' : 't'+site.no);
            if (Array.isArray(tgts) && pref.catalog.refresh.at_switch && tgts.indexOf(key_me)===-1 && tgts.indexOf(key_me2)===-1) pClg.clear_threads(0);
          }
//          if (pref.catalog_board_list_sel!==0 && pref.catalog.refresh.at_switch) catalog_clear_threads(0);
          if (!pref.test_mode['212'] && pref[embed_mode].merge_auto) site2['DEFAULT'].update_posts_merge_bases.auto_merge_list(site.key, pClg);
      };
      if (embed_embed) {  // for native catalog
        var pfunc = site2[site.nickname].parse_funcs[site.whereami+'_html'];
        if (pfunc.ths_hook) pfunc.ths_hook(exe_embed); else setTimeout(exe_embed, 0); // required for 4chan, causes too many deleted posts sometimes
      }
      function insert_myself(e,observer,init,force_annotate){
//          var ths = scan_boards.scan_boards_keyword_callback2(site.nickname+','+site.board+','+site.no+','+((embed_catalog)?'catalog':'page')+'_html',
        var new_posts = [];
//        if (!pref.test_mode['122'] || init) {
//          if (!init && (pref[embed_mode].merge || pref[embed_mode].merge_list)) return; // patch
          if (e) {
            var pfunc = site2[site.nickname].parse_funcs['post_html'].nativeUpdate2pn;
            var posts_updated = {};
            for (var i=0;i<e.length;i++) if (e[i].addedNodes) for (var j=0;j<e[i].addedNodes.length;j++) {
              var key = (site.whereami==='thread')? site.key : gGEH.get_key_recursive(e[i].addedNodes[j], document);
              var post = {pn:pfunc(e[i].addedNodes[j])};
              if (!posts_updated[key]) posts_updated[key] = [post];
              else posts_updated[key][posts_updated[key].length] = post;
            }
            for (var key in posts_updated) posts_updated[key].sort(function(a,b){return a.no - b.no;});
          }
          var insert_thread_from_native = {init:init, posts_updated:init? null : posts_updated};
          var site_no = site.whereami==='thread'? site.no : site.page;
          var parse_options = {}; // {native_prep:true};
          parse_options[site.whereami==='thread'? 'thread' : 'page'] = site_no; // emulation of callback2
          var ths = site2[site.nickname].wrap_to_parse.get(document, site.nickname, site.board, site.whereami+'_html', parse_options);
          for (var i=0;i<ths.length;i++) ths[i].insert_thread_from_native = insert_thread_from_native_prep(ths[i], liveTag.mems.init(ths[i]), insert_thread_from_native);
          scan_boards.scan_boards_keyword_callback2(site.nickname+','+site.board+','+site_no+','+site.whereami+'_html',
                                                              {date:Date.now(), status:200, response:document},
                                                              ['native_prep',{ext_posts:new_posts, force_annotate:force_annotate, refresh:pClg,
                                                                              __proto__:cataLog.scan_boards_keyword_callback2_default_args}], ths);
          if (site.whereami==='thread') Object.defineProperty(site,'myself',{value:ths[0], writable:true, enumerable:true, configurable:true});
          if (init) pClg.show_catalog();
          if (pref.test_mode['155']) pref.test_mode['136'] = true;
          if (pref.debug_mode['38']) {
            var arr = Array.prototype.slice.call(document.querySelectorAll('.thread>.postContainer>.post')).map(function(v){return v.id.substr(1);}); // test in 4chan
            var nos = {};
            arr.forEach(function(v){nos[v] = (nos[v]||0)+1;});
            if (Object.keys(nos).length!==arr.length) console.log('ERROR: doubles: '+arr.filter(function(v){return nos[v]!=1 && nos[v]--;}));
          }
//        } else { // TEST PATCH
//          // DATA PROCESSING IS REQUIRED HERE, tag extraction, favicon and archiving.
//          var pfunc = site2[site.nickname].parse_funcs['post_html'].nativeUpdate2pn;
//          if (e) for (var i=0;i<e.length;i++) if (e[i].addedNodes) for (var j=0;j<e[i].addedNodes.length;j++) new_posts[new_posts.length] = {pn:pfunc(e[i].addedNodes[j])};
//          site2[site.myself.domain].wrap_to_parse.posts({posts:new_posts, __proto__:site.myself});
//          for (var i=0;i<new_posts.length;i++) format_html.prepare_html_post(site.myself, new_posts[i]);
//          for (var i=0;i<new_posts.length;i++) site.myself.posts[site.myself.posts.length] = new_posts[i];
//          insert_thread_format_html_add_events(site.myself, new_posts, true);
//          threads[site.myself.key][7] = site.myself;
//          threads[site.myself.key][16].posts = site.myself.posts;
//        }
//        if (common_obj.thread_reader && new_posts.length!==0) common_obj.thread_reader.updated(new_posts,init);
        return ths;
      }
      cataLog.insert_myself = insert_myself;
        
      function insert_thread_from_native_prep(th, lth, obj){
        var posts_updated = obj.posts_updated && obj.posts_updated[th.key];
        if (posts_updated) site2[th.domain].wrap_to_parse.posts({posts:posts_updated, __proto__:th});
        var merge = pClg.mode==='thread' && pClg.merge_bases.bases[th.key];
        if (merge) {
          var posts_others = {};
          for (var i=0;i<merge.lths.length;i++) if (lth!==merge.lths[i]) {
            var posts = merge.tgt_ths[i][16].posts;
            for (var j=0;j<posts.length;j++) posts_others[posts[j].key] = null;
          }
          th.posts = th.posts.filter(function(v){return posts_others[v.key]===undefined;});
        }
        var retval = {init: obj.init, posts_shown:lth.pd? th.posts.slice():th.posts, posts_updated: posts_updated}; // posts_shown===th.posts is for tgt_th[16].posts===th.posts in update_posts_in_page, requests of merging deleted posts.
        var merge_dp = pref[pClg.mode].deleted_posts.merge;
        if (lth.pd && merge_dp && !obj.init) {
          var recovered = 0;
          if (site.myself && posts_updated && site.myself.posts.length + posts_updated.length == th.posts.length) th.posts = site.myself.posts.concat(posts_updated); // prevent from re-parsing in thread.
          else {
            var nos_updated = {};
            if (pref.test_mode['157']) for (var i=0;i<posts_updated.length;i++) nos_updated[posts_updated[i].no] = null;
            var i=1; // doesn't contain OP
            var j=0;
            while (i<th.posts.length && j<lth.pd.length) { // lower posts may be parsed already.
              if (th.posts[i].no==lth.pd[j].no) {if (pref.test_mode['157'] && (lth.pd[j].no in nos_updated)) {recovered++;i++;} else th.posts.splice(i,1); j++;}
              else if (th.posts[i].no>lth.pd[j].no) j++;
              else i++; // for broken data, i++ for default is the robustest.
            }
          }
          th.nof_posts -= lth.pd.length - recovered;
          th.nof_files -= lth.pd.map(function(v){return v.tn_imgs.length;}).reduce(function(a,v){return a+v;});
        }
        return retval;
      }
////////      if (embed_catalog || embed_page) {  // for native catalog // working code.
////////        pn12.style.display = 'none';
////////        setTimeout(function(){ // patch for liveTag.
////////          var date = Date.now();
////////          site2[site.nickname].catalog_native_prep(date,pn12_0_4,pn12_0, embed_catalog);
////////          insert_thread_from_native = true;
//////////          var ths = scan_boards.scan_boards_keyword_callback2(site.nickname+','+site.board+','+site.no+','+((embed_catalog)?'catalog':'page')+'_html',
////////          var ths = scan_boards.scan_boards_keyword_callback2(site.nickname+','+site.board+',0,'+((embed_catalog)?'catalog':'page')+'_html',
////////                                                              {date:date, status:200, response:document},
////////                                                              ['native_prep',{native_prep:true, found_threads: 0, max_threads:500, found_board:0, scanned:0, refresh:true}]);
////////          insert_thread_from_native = false;
////////          if (pref.thread_reader.own_posts_tracker && pref.thread_reader.clean_up_own_posts && site.whereami==='catalog') site2[site.nickname].clean_up_own_posts(ths,site.board);
////////        },0);
////////      }

//    if (embed_mode!=='thread') setTimeout(function(){ // patch for liveTag.
      if ((!window.opener || !window.name || window.name.slice(-1)!=='A') && site.whereami!=='archive') setTimeout(function(){ // patch for liveTag and archive.
//    if (!window.opener) setTimeout(function(){ // patch for liveTag and archive. // THIS CAUSE WRONG BEHAVIOR when the page was load by clicking [catalog]
        if (pref.catalog_board_list_sel!=0 || pref[embed_mode].env.refresh_initial) catalog_refresh(site.whereami!=='boards', embed_embed, false);
      },1);
//      if (pref[embed_mode].merge || pref[embed_mode].merge_list)
//        setTimeout(pref_func.settings.onchange_funcs['*w/.merge'+(pref[embed_mode].merge?'':'_list')],2);

      function catalog_insert(key) {
        var dbt = key.split(',');
        if (pref.catalog_promiscuous || (dbt[0]+dbt[1] in boards)) scan_boards.scan_init('snoop', [key], {lifetime:3600, crawler_max:1, tgt_raw:true});
      }
      function catalog_insert_snoop(key,value) {
        catalog_insert2(key,value,true,true);
      }
      function catalog_insert2(key,value,snoop,from_auto) {
//var check_perf = ['catalog_refresh :', performance.now()];
        var dbt = key.split(',');
        var nickname = dbt[0];
        var board = dbt[1];
        var read_type = dbt[3];
        var page_no = dbt[2];
        var thread = dbt[2];
////////        var dbt = cnst.name2domainboardthread(key,true);
////////        var nickname = dbt[0];
////////        var board = dbt[1];
////////        var read_type = (dbt[2][0]==='p')? 'page_html' : ((dbt[2][0]==='c')? 'catalog_html' : ((dbt[2][0]==='j')? 'catalog_json' : 'thread_html'));
////////        var page_no = (read_type==='thread_html')? '?' : dbt[2].substr(1);
////////        var thread = dbt[2];
        if (snoop && !pref.catalog_promiscuous && read_type==='thread_html' && !threads[name]) {
          var hit = false;
          for (var i=0;i<load_list.refresh.tgts.length;i++) if (load_list.refresh.tgts[i][0].indexOf(name)!=-1) {hit=true;break;}
          if (!hit) return 0; // return if no interest.
        }
if (pref.test_mode['3']) return;
        if (!('response' in value)) value.response = (read_type==='catalog_json')? JSON.parse(value.responseText)
                                                                                 : new DOMParser().parseFromString(value.responseText, 'text/html'); // cause memory leak at KC/int/.
if (pref.test_mode['2']) return;
//check_perf.push(performance.now());
        var tgts_show = {};
//        catalog_clear_threads(pref.catalog.max_threads);
////////        if ((read_type==='page_html' || read_type==='thread_html') && nickname==='NONE') { // for debug
//////////        if ((read_type==='page_html' || read_type==='thread_html') && nickname==='KC') { // for debug
//////////        if ((read_type==='page_html' || read_type==='thread_html') && nickname!=='8chan') { // for debug
//////////        if ((read_type==='page_html' && nickname!=='8chan') || read_type==='thread_html') { // for debug
//////////        if (read_type==='page_html' || read_type==='thread_html') {
////////          var name = nickname + board + thread;
////////          if (snoop && !pref.catalog_promiscuous && read_type==='thread_html' && !threads[name]) {
////////            var hit = false;
////////            for (var i=0;i<load_list.refresh.tgts.length;i++) if (load_list.refresh.tgts[i][0].indexOf(name)!=-1) {hit=true;break;}
////////            if (!hit) return 0; // return if no interest.
////////          }
//////////          value.responseText = site2[nickname].preprocess_html(value.responseText,read_type==='page_html'); // cause memory leak.
//////////          var doc = ('response' in value)? value.response : new DOMParser().parseFromString(value.responseText, 'text/html');
////////          var doc = value.response;
////////          site2[nickname].preprocess_doc(doc);
////////          var nof_posts = 0;
////////          var nof_files = 0;
////////          if (read_type==='thread_html') {
////////            var nof_pi = site2[nickname].thread2headline(doc);
////////            nof_posts  = nof_pi[0];
////////            nof_files = nof_pi[1];
////////            nof_pi = null; // for test
////////            site2[nickname].add_thread_link(doc,site2[nickname].make_url3(board,thread));
////////          }
////////          if (site.nickname!=nickname || site.board!=board) site2[nickname].absolute_link(doc,board);
////////          var threads_in_page = site2[nickname].catalog_threads_in_page(doc);
////////          var th_no = site2[nickname].get_ops(doc);
////////
////////          for (var i=0;i<threads_in_page.length;i++) {
////////            var p_node = threads_in_page[i].parentNode;
////////            insert_thread_from_page(threads_in_page[i], nickname, board, th_no[i], page_no+((read_type==='page_html' && pref.show_page_fraction)? '.'+i : ''), (i==0)?nof_posts : 0, (i==0)?nof_files : 0, snoop, value.date);
//////////          if (threads_in_page[i].parentNode==p_node) threads_in_page[i].parentNode.removeChild(threads_in_page[i]);
////////            tgts_show[nickname+board+th_no[i]]=true;
////////          }
////////        } else {
//          if (read_type==='thread_html') site2[nickname].add_thread_link(value.response,site2[nickname].make_url3(board,thread));
//          if (read_type==='page_html' || read_type==='thread_html') site2[nickname].preprocess_doc(value.response);
          var ths;
if (pref.test_mode['0']) {
          ths = {domain:nickname, board:board, page:page_no};
          if (read_type==='catalog_json') ths.obj = value.response;
          else ths.pn = value.response;
          site2[ths.domain].parse_funcs[read_type].entry(ths,site2[ths.domain].parse_funcs[read_type]['before_test']);
          ths = ths.ths;
} else {
//////          ths = Object.create({domain:nickname, board:board, page:page_no, parse_funcs:site2[nickname].parse_funcs[read_type], __proto__:site4.parse_funcs_on_demand});
//////          if (read_type==='catalog_json') ths.obj = value.response;
//////          else Object.defineProperty(ths,'pn',{value:value.response, enumerable:true, configurable:true, writable:true});
////          var parse_obj = Object.create({domain:nickname, board:board, page:page_no, parse_funcs:site2[nickname].parse_funcs[read_type], __proto__:site4.parse_funcs_on_demand});
////          ths = (read_type==='catalog_json')? {obj:value.response, __proto__:parse_obj} : {pn:value.response, __proto__:parse_obj};
////          ths = ths.ths;
          ths = site2[nickname].wrap_to_parse.get(value.response, nickname, board, read_type, {page:page_no});
}
          if (read_type==='catalog_json' || read_type==='catalog_html') rm_items_404_check(nickname,board,ths); // consumes 15-40 ms, too slow.
          if (snoop && !pref.catalog_promiscuous) for (var i=ths.length-1;i>=0;i--) if (!(ths[i].key in threads)) ths.splice(i,1);
//check_perf.push(performance.now());
          for (var i=0;i<ths.length;i++) {
//            if (read_type!=='catalog_json' && ths[i].pn.parentNode) ths[i].pn.parentNode.removeChild(ths[i].pn); // patch for memory leak issue, but probably fixed.
            if (insert_thread_with_test(ths[i], read_type, value.date)) tgts_show[ths[i].key]=true;
          }
////////        }
//check_perf.push(performance.now());
        if (Object.keys(tgts_show).length!=0) {
          show_catalog(tgts_show,from_auto);
//          if (pref.filter.tag_scan_auto) scan_tags();
        }
//check_perf.push(performance.now());
//check_perf.push('num: '+Object.keys(tgts_show).length);
//comf.perf_out(check_perf);
      }

//      var threads_delayed_pruning = Object.create(null);
      function restore_bd_from_IDB(domain, board){
//        if (!threads_delayed_pruning[domain]) threads_delayed_pruning[domain] = {};
//        if (!threads_delayed_pruning[domain][board]) threads_delayed_pruning[domain][board] = {};
        IDB.req(domain, board, null, null, restore_bd_from_IDB_1, 'list_os');
      }
      function restore_bd_from_IDB_1(domain, board, nos){
        for (var i=0;i<nos.length;i++) if (pref.test_mode['80'] || !threads[domain+board+nos[i]]) restore_th_from_IDB(domain, board, nos[i]);
      }
      function restore_th_from_IDB(domain, board, no, tgt){
        IDB.req(domain, board, no, null, function(d,b,n,r){archiver.event_funcs['restore3'](d,b,n,r,tgt);}, 'get_all');
//        IDB.req(domain, board, no, null, archiver.event_funcs['restore3'].bind(archiver.event_funcs), 'get_all');
//        if (!threads_delayed_pruning[domain]) threads_delayed_pruning[domain] = {}; // BUG, if a 404 thread is loaded before catalog, since this is used as a flag in above 'restore_bd_from_IDB' function.
//        if (!threads_delayed_pruning[domain][board]) threads_delayed_pruning[domain][board] = {};
//        threads_delayed_pruning[domain][board][no] = true;
      }
      cataLog.restore_th_from_IDB = restore_th_from_IDB;
      function rm_items_404_check(domain, board, ths, nos){
        if (pref.test_mode['136'] && embed_mode==='thread') return;
        if (pref.patch.rm_404_blacklist.indexOf(domain)!=-1) return; // PATCH for 8chan, 8chan sends corrupted data.
////        if (pref.catalog.bookmark_list_rm404) {
////          var val = catalog_obj_merge(db,pref.filter.list_obj3,null);
////          val = catalog_obj_merge(db,pref.filter.attr_list_obj3,val);
////          if (val.hit) return true;
////        }
////        return false;
////        return pref.catalog.bookmark_list_rm404
////            && ((db in pref.filter.list_obj3) || (db in pref.filter.attr_list_obj3) || (db in pref.filter.watch_list_obj3));
//        var nos = Object.create(null);
////        var nos = Object.create(pref.archive.IDB.auto_restore && threads_delayed_pruning[domain] && threads_delayed_pruning[domain][board] || null);
//        for (var i=0;i<ths.length;i++) nos[ths[i].no] = null;
        var tgts = liveTag.rm_404_list(domain, board, nos);
        var nos_recovered = rm_items_404_0(domain, board, nos, tgts);
//        var nos_recovered = Object.create(nos);
//        for (var no in tgts) if (gClg.Remove_thread_refresh(domain + board + no, tgts[no])) nos_recovered[no] = null;
//        for (var i=0;i<gClg.Clgs.length;i++) rm_items_404_1(domain, board, nos, nos_recovered, gClg.Clgs[i]);
//        for (var no in tgts) liveTag.rm_404_1(domain, board, no, (no in nos_recovered));
////        var db = domain + board;
////        var flag_item = (pref.catalog.bookmark_list_rm404
////            && ((db in pref.filter.list_obj3) || (db in pref.filter.attr_list_obj3) || (db in pref.filter.watch_list_obj3)));
////        if (flag_item) rm_items_404(db,nos);
////        if (!pref.test_mode['67']) archiver.clean_list_all(domain, board, nos); // 'archiver.clean_list' is called in liveTag.rm_404_1
        var lbd = liveTag.mems[domain][board];
        if (!lbd.LS_synced) {
          site2[domain].clean_up_LS(domain, board, nos_recovered);
          lbd.LS_synced = 1;
        }
        if (pref.features.IDB && !lbd.IDB_synced && pref.archive.IDB.auto_clean_init) IDB.clean_up(domain, board, nos_recovered); // BUG, thread in IDB is removed even nos_recovered is used instead of nos, since 'prunied_time' is always recorded in liveTag.rm_404_1.
//        var keys = Object.keys(nos_recovered);
//        for (var j=0;j<keys.length;j++) {
//          var name = nos_recovered[keys[j]];
//          if (name) for (var i=0;i<gClg.Clgs.length;i++) if (gClg.Clgs[i].threads[name]) gClg.Clgs[i].view_attr_set(name);
//        }
        if (ths[0] && ths[0].parse_funcs.clean_collisions) ths[0].parse_funcs.clean_collisions(ths, nos_recovered);
      }
      Clg.prototype.Remove_thread_refresh = function(name, lth, force, recovered){
        if (gClg.AllThreads[name]) {
          var pf = pref.liveTag.rm_404;
          var keep = recovered || pf==='no' || pf!=='imm' && lth && lth.watched && (pf==='watched' || lth.nr);
          for (var i=0;i<gClg.Clgs.length;i++) {
            var tgt_th = gClg.Clgs[i].threads[name];
            if (!tgt_th) continue;
            var from_archive = tgt_th[14]==='IDB' || tgt_th[14]==='FILE';
            if (keep || !force && from_archive) {
              if (!from_archive) if (tgt_th[14]!=='x') {
                tgt_th[14]='x';
                gClg.Clgs[i].footer.update_force(name);
              }
            } else gClg.Clgs[i].remove_thread(name, false, true);
          }
          if (!keep) delete gClg.AllThreads[name]; // gClg.Maintain_AllThreads(name);
        }
        return keep;
      }
      Clg.prototype.rm_items_404_clearPRUNEDs = function(){
        var nos = Object.create(null);
        var nos_recovered = Object.create(nos);
        var gpf = {common:{watch_list_rm404:'imm'}, liveTag:{rm_404:'imm'}};
        var pf = this.pref.filter;
        var obj2s = [pf.watch_list_obj2, pf.attr_list_obj2, pf.list_obj2];
        for (var i=0;i<obj2s.length;i++) {
          var obj2 = obj2s[i];
          for (var name in obj2) if (obj2[name].cmd && obj2[name].cmd['PRUNED']) {
            var dbt = comf.fullname2dbt(name);
            rm_items_404_1(dbt[0], dbt[1], nos, nos_recovered, this, dbt[2], gpf);
          }
        }
        if (pref.common.consolidated_watch_list && pref.common.sync_watch_list) gClg.Sync_watch_save([null, 'PRUNED', pf.watch_list_str]); // be here to prevent from sending multiple requests in 'rm_items_404_1'
      };
      function rm_items_404_th(domain, board, no){
        var lth = (lth = liveTag.mems[domain]) && (lth = lth[board]) && lth[no]; // don't use getFromName, lth may not exist.
        if (lth) rm_items_404_0(domain, board, {}, {no:lth}, no);
//        if (!lth) return;
//        var nos = {};
//        var nos_recovered = Object.create(nos);
//        if (gClg.Remove_thread_refresh(domain + board + no, lth)) nos_recovered[no] = null;
//        for (var i=0;i<gClg.Clgs.length;i++) rm_items_404_1(domain, board, nos, nos_recovered, gClg.Clgs[i], no);
//        liveTag.rm_404_1(domain, board, no, (no in nos_recovered));
      }
      function rm_items_404_0(domain, board, nos, tgts, no_in){
        var nos_recovered = Object.create(nos);
        for (var i=0;i<gClg.Clgs.length;i++) rm_items_404_1(domain, board, nos, nos_recovered, gClg.Clgs[i], no_in);
        for (var no in tgts) if (gClg.Remove_thread_refresh(domain + board + no, tgts[no], null, (no in nos_recovered)) && !(no in nos_recovered)) nos_recovered[no] = null;
        for (var no in tgts) liveTag.rm_404_1(domain, board, no, (no in nos_recovered));
        if (pref.common.merge_list_rm404!=='no' && (pref.common.merge_list_rm404!=='dead' || no_in!==undefined)) {
          var str;
          if ((str = rm_items_404_2(domain, board, nos, nos_recovered, no_in, pref3.proto.merge_list_obj2, pref.proto.merge_list_str, null))!==null) pref3.proto.merge_list_obj2._str = str;
          if ((str = rm_items_404_2(domain, board, nos, nos_recovered, no_in, pref3.proto.merge_lv_obj2,   pref.proto.merge_lv_str,   null))!==null) pref3.proto.merge_lv_obj2._str   = str;
        }
        var keys = Object.keys(nos_recovered); // .map(function(k){nos_recovered[k];}).filter(function(v){return v;});
        for (var j=0;j<keys.length;j++) {
          var name = nos_recovered[keys[j]];
          if (name) for (var i=0;i<gClg.Clgs.length;i++) if (gClg.Clgs[i].threads[name]) gClg.Clgs[i].view_attr_set(name, true);
        }
//        if (keys.filter(function(v){return nos_recovered[v];}).length>0 && pref.common.consolidated_watch_list && pref.common.sync_watch_list) gClg.Sync_watch_save([null, 'PRUNED', pClg.pref.filter.watch_list_str]);
//        if (keys.length>0 && pref.common.consolidated_watch_list && pref.common.sync_watch_list) gClg.Sync_watch_save([keys, 'PRUNED', keys.map(function(k){nos_recovered[k];})]);
        return nos_recovered;
      }
      function rm_items_404_1(domain, board, nos, nos_recovered, clg, no, gpf){
        if (!gpf) gpf = pref;
        var pf_merged = (gpf.common.watch_list_rm404==='no' || gpf.liveTag.rm_404==='no')? 'no'
                      : (gpf.common.watch_list_rm404==='unread' || gpf.liveTag.rm_404==='unread')? 'unread'
                      : gpf.liveTag.rm_404;
        var pf = clg.pref.filter;
        var str;
        if ((str = rm_items_404_2(domain, board, nos, nos_recovered, no, pf.watch_list_obj2, pf.watch_list_str, pf_merged, clg))!==null) {
          clg.components.watch_list.value = pf.watch_list_str = str; // must be first to get priority.
          if (clg===pClg && gpf.common.consolidated_watch_list && gpf.common.sync_watch_list) gClg.Sync_watch_save([null, 'PRUNED', str]);
//          if (pref.common.consolidated_watch_list) gClg.Sync_watch_save(null); // may be issued multitimes, but OK.
        }
        if ((str = rm_items_404_2(domain, board, nos, nos_recovered, no, pf.attr_list_obj2, pf.attr_list_str, gpf.liveTag.rm_404, clg))!==null) clg.components.attr_list.value = pf.attr_list_str  = str;
        if ((str = rm_items_404_2(domain, board, nos, nos_recovered, no, pf.list_obj2,      pf.list_str,      gpf.liveTag.rm_404, clg))!==null) clg.components.ex_list.value   = pf.list_str       = str;
      }
      function rm_items_404_2(domain, board, nos, nos_recovered, no, obj2, str_in, pf, clg){
        var func = pf? rm_items_404_delete : rm_items_404_delete_merge;
        var db = domain + board;
        var str = null;
        var obj_r = obj2[':REV'] && obj2[':REV'][db];
        if (obj_r)
          if (no===undefined) {for (var n in obj_r) if (!(n in nos)) str = func(domain, board, db+n,  nos_recovered, n,  obj2, obj_r, str, str_in, pf, clg, true);}
          else if (nos[no]===undefined)             if (no in obj_r) str = func(domain, board, db+no, nos_recovered, no, obj2, obj_r, str, str_in, pf, clg);
        return str && pref_func.fmt4str2_2(str);
      }
      function rm_items_404_delete(domain, board, name, nos_recovered, no, obj2, obj_r, str, str_in, pf, clg, from404){
        var recovered = (no in nos_recovered) || pref.test_mode['213'] && clg.keepMergedThread(name, from404);
        var lth = (!recovered && pf==='imm')? null : ((lth = liveTag.mems[domain]) && (lth = lth[board]) && lth[no]); // don't use getFromName since watchlist may contain threads which don't exist in liveTag.mems
        var keep = recovered || pf==='no' || pf!=='imm' && (lth && lth.watched && (pf==='watched' || lth.nr)); // can't catch unread posts during offline
        if (keep) {
          nos_recovered[no] = null; // prior results are accumulated here
          var nr = lth && (lth.nrtm +'/'+lth.nr) || '?/?';
          if (!obj2[name].cmd) obj2[name].cmd = {'PRUNED':nr};
          else if (obj2[name].cmd['PRUNED']===undefined) obj2[name].cmd['PRUNED'] = nr;
          else return str; // keep str value since pruned already
          nos_recovered[no] = name; // overwritten for change view
        } else {
          delete obj2[name];
          delete obj_r[no];
        }
        if (pref.debug_mode['2']) console.log('rm_items_404_delete: ' + name);
        var key = new RegExp('(^|,)'+name.replace(/\+/,'\\+')+'([\\^@;][^,\n]*)*(,|\n|$)','mg'); // see 'triage_exe'
        return (str||str_in).replace(key,!keep?'':'$1'+name+'$2;PRUNED:'+nr+'$3');
      }
      function rm_items_404_delete_merge(domain, board, name, nos_recovered, no, obj2, obj_r, str, str_in, pf, clg, from404){
        if (no in nos_recovered) return str;
        else if (pref.test_mode['213'] && gClg.keepMergedThread(name, from404)) { // CAUTION, gClg is used here temporarily.
          nos_recovered[no] = name;
          return str;
        }
        if (pref.debug_mode['2']) console.log('rm_items_404_delete_merge: ' + name);
        return obj2._rm(name,obj_r,no,(str||str_in));
//        var tgt = obj2[name];
//        if (tgt.length>1) tgt.splice(tgt.indexOf(name),1);
//        delete obj2[name];
//        delete obj_r[no];
//        var key = name.replace(/\+/,'\\+')+'([\\^@;:][^,\n]*)*'; // see 'triage_exe' // moved to str2obj2, working code
//        return str.replace(new RegExp('(^|,)'+key+'([,\n]+\\s*\\+*|$|\\+)','mg'),'$1').replace(new RegExp('\\+\\s*'+key+'(,|\n|$|\\s*\\+)','mg'),'$2');
      }
      Clg.prototype.keepMergedThread = function(name, from404){
        var pf = pref.common.merge_list_rm404;
        // IMPLEMENT pf==='UI' case here
        return pf!=='imm' && !from404 && this.threads[name] && this.merge_bases.base_arr[name]; // merge_basea.bases can't be used because it is null when it is NOT displayed.
      };
//      function rm_items_404_1(domain, board, nos, clg, func){ // working code
//        var db = domain + board;
//        var pf = clg.pref.filter;
//        var result = {};
//        if (!pref.test_mode['134']) {
//          func(result, db, nos, pf.list_obj2);
//          func(result, db, nos, pf.attr_list_obj2);
//          func(result, db, nos, pf.watch_list_obj2);
//        } else {
//          if (db in pf.list_obj3)       rm_items_404_11(result, db, nos, pf.list_obj2);
//          if (db in pf.attr_list_obj3)  rm_items_404_11(result, db, nos, pf.attr_list_obj2);
//          if (db in pf.watch_list_obj3) rm_items_404_11(result, db, nos, pf.watch_list_obj2);
//        }
//        var list = Object.keys(result);
//        if (list.length>0) clg.triage_exe(list[0],'DELETE',list.slice(1),false); // don't broadcast
//        if (pref.debug_mode['2']) if (list.length>0) console.log('rm_items_404: ' + list);
//      }
//      function rm_items_404_1_bd(result, db, nos, obj2){
//        var obj_r = obj2[':REV'] && obj2[':REV'][db];
//        if (obj_r) for (var no in obj_r) if (!(no in nos)) {
//          result[db+no] = null;
//          delete obj2[db+no];
//          delete obj_r[no];
//        }
//      }
//      function rm_items_404_1_th(result, db, no, obj2){
//        var obj_r = obj2[':REV'] && obj2[':REV'][db];
//        if (obj_r && obj_r[no]) {
//          result[db+no] = null;
//          delete obj2[db+no];
//          delete obj_r[no];
//        }
//      }
//      function rm_items_404_11(result, db, nos, obj2){
//        var db_len = db.length;
//        for (var name in obj2)
//          if (name.length>db_len && name.indexOf(db)===0 && !(name.substr(db_len) in nos)) result[name] = null;
//      }
      
//      function rm_items_404(db,nos){ // working code
////console.log('rrr');
//        var tgts = [[pref.filter.list_str,      pref.filter.list_obj2,      search_ex_list],
//                    [pref.filter.attr_list_str, pref.filter.attr_list_obj2, attr_list],
//                    [pref.filter.watch_list_str, pref.filter.watch_list_obj2, watch_list]];
//        var db_len = db.length;
//        for (var i=0;i<tgts.length;i++) {
////          var changed = false;
//////          for (var name in tgts[i][1]) { // too slow
//////            var dbt = comf.name2domainboardthread(name,true);
////////            if (dbt[0]===nickname && dbt[1]===board && dbt[2]!=='') {
////////              var flag = false;
////////              for (var j=0;j<ths.length;j++) if (dbt[2]==ths[j].no) {flag=true;break;}
////////              if (!flag) {
//////            if (dbt[0]+dbt[1]===db && dbt[2]!=='') {
//////              if (nos[dbt[2]]===undefined) {
//////                if (pref.debug_mode['0']) console.log(name);
//////                triage_exe(name,'DELETE','',false);
//////                changed = true;
//////                if (threads[name]) remove_thread(name);
//////              }
//////            }
//////          }
////          for (var name in tgts[i][1]) {
//////            if (!(name in nos) && name.indexOf(db)==0) {
////            if (name.indexOf(db)==0 && name.length!=db.length && !(name in nos)) {
////              if (pref.debug_mode['2']) console.log('rm_items_404: ' + name);
////              triage_exe(name,'DELETE','',false); // BUG, this changes tgts[i][1], so inconsistency occurs, and requires redundant sequences.
////              changed = true;
////              if (threads[name]) remove_thread(name);
////            }
////          }
////          if (changed) { // seems to be redundant.
////            tgts[0][2].value = tgts[0][2].value.replace(/\n\n+/g,'\n'); // triage_exe executes both.
////            tgts[1][2].value = tgts[1][2].value.replace(/\n\n+/g,'\n');
////            tgts[2][2].value = tgts[2][2].value.replace(/\n\n+/g,'\n');
////            pref_func.apply_prep(tgts[i][2],true);
////            pref_func.apply_prep(tgts[i][2],false);
////          }
//          var list = [];
//          for (var name in tgts[i][1]) {
//            if (name.length>db_len && name.indexOf(db)===0 && !(name.substr(db_len) in nos)) {
//              if (pref.debug_mode['2']) console.log('rm_items_404: ' + name);
//              list[list.length] = name;
//            }
//          }
//          if (list.length>0) pClg.triage_exe(list[0],'DELETE',list.slice(1),false); // BUG, this changes tgts[i][1], so inconsistency occurs, and requires redundant sequences.
//        }
//      }

//      function req_events(key,value,list) { // working code.
//        list.mutex = true;
////if (pref.debug_mode['0']) console.log(new Date().toLocaleTimeString() + ', refresh: callback: '+load_list.refresh.tgts[load_list.refresh.idx]+', '+load_list.refresh.idx);
//        if (value.status==200 && (value.responseText || value.response)) catalog_insert2(key,value,false,list.from_auto);
//        else {
//          if (value.status==404) comment_out_bookmark(key);
//          list.indicator.set('orange');
//        }
//        if (list.idx<list.tgts.length && value.status<500) {
//          if (!pref[embed_mode].load_on_demand) get_page(list);
//        } else {
//          if (list.idx==1 && value.status!=200) list.indicator.set('red','X');
//          else if (list.idx==list.tgts.length) list.indicator.set(null,'\u25cf');
//          else list.indicator.set(null,'\u25b2');
//          if (list.check_page) {
//            refresh_idx_page = 0;
//            page_check_entry();
//          } else http_req.close('catalog'+list.key);
//          catalog_refresh_watch();
//        }
//      }
//      function get_page(list){
////if (pref.debug_mode['0']) console.log(new Date().toLocaleTimeString() + ', refresh: get: '+list.tgts[list.idx]+', '+list.idx);
//        if (pref[embed_mode].load_on_demand && list.idx==0) 
//          for (var i=list.tgts.length-1;i>=1;i--) {
//            var name = ':DL:'+list.tgts[i];
//            if (threads[name]) remove_thread(name);
//            threads_idx.unshift(name);
//          }
////        if (tgt===undefined) tgt = list.tgts[list.idx];
////        else tgt = [tgt,false];
//        if (list.idx==0 && !list.indicator) list.indicator = health_indicator.shift('limegreen','0');
//        list.indicator.set(null,(list.idx+1)+'/'+list.tgts.length);
//        http_req.get('catalog'+list.key,list.tgts[list.idx].replace(/!.*/,''),'',req_events,list.use_cache,true,list);
//        list.idx++;
//        list.mutex = false;
//      }
//
////      function req_events(key,value,args) {
//////if (pref.debug_mode['0']) console.log(new Date().toLocaleTimeString() + ', refresh: callback: '+refresh_tgts[refresh_idx][0]+', '+refresh_idx);
////        var inserted_idx = 0;
//////        var key = refresh_tgts[refresh_idx][0].replace(/!.*/,'');
//////        var key = args[0].replace(/!.*/,'');
////        if (value.status==200 && value.responseText) {
////          inserted_idx = catalog_insert2(key,value,false,args[1]);
////        } else {
////          if (value.status==404) comment_out_bookmark(key);
////          health_indicator.set('orange');
////        }
////        refresh_idx++;
////        if (refresh_idx<refresh_tgts.length && value.status<500) {
////          if (!pref[embed_mode].load_on_demand) get_page();
////          else {
////            if (threads_idx.length==inserted_idx+1) threads_idx.push('url');
////            else threads_idx.splice(inserted_idx+1,0,'url');
//////            threads_idx.splice(page_delim_idx[refresh_idx],0,'url'); // THIS MAKES SUBTLE BUG, insert point slides from here at snoop refresh, but ok to use, so I'll omit.
////            show_catalog();
////          }
////        } else {
////          if (refresh_idx==1 && value.status!=200) health_indicator.set('red','X');
////          else if (refresh_idx==refresh_tgts.length) health_indicator.set(null,'\u25cf');
////          else health_indicator.set(null,'\u25b2');
////          refresh_idx_page = 0;
////          page_check_entry();
////        }
////      }
////      function get_page(){
//////if (pref.debug_mode['0']) console.log(new Date().toLocaleTimeString() + ', refresh: get: '+refresh_tgts[refresh_idx][0]+', '+refresh_idx);
////        health_indicator.set(null,(refresh_idx+1)+'/'+refresh_tgts.length);
////        http_req.get('catalog',refresh_tgts[refresh_idx][0].replace(/!.*/,''),'',req_events,refresh_use_cache,true,refresh_tgts[refresh_idx]);
////      }

      var refresh_idx_page;
      function page_check_entry(){
        while (refresh_idx_page<load_list.refresh.tgts.length) {
          if (load_list.refresh.tgts[refresh_idx_page][0].search(/!page/)==-1) refresh_idx_page++;
          else {
            var name = load_list.refresh.tgts[refresh_idx_page++][0].replace(/!.*/,'');
            if (threads[name]) {
              if (threads[name][14].toString()[0]=='?') {
                var page_no_old = parseInt(threads[name][15].toString().replace(/\..*/,''),10);
                if (isNaN(page_no_old)) page_no_old = 0;
                page_check(name,page_no_old,page_check_callback);
                break;
              }
            }
          }
        }
      }
      function page_check_callback(name,str,date){
//console.log('Callback : '+name+', '+str);
        update_page_in_footer(name,str,date); // to show Dead.
        page_check_entry();
      }
      function update_page_in_footer(name,str,date){
//        if (threads[name]) {
        if (threads[name] && threads[name][0].getElementsByTagName('div')['catalog_footer']) { // patch for threads, this will be removed.
          var pn = threads[name][0].getElementsByTagName('div')['catalog_footer'].childNodes[0];
          if (pn) {
//            var str = str + '@' + new Date(date).toLocaleTimeString();
            pn.innerHTML = pn.innerHTML.replace(/[^\/]*$/,str);
          }
          threads[name][13]=date;
          threads[name][15]=threads[name][14];
          threads[name][14]=str;
        }
      }
 
      function page_check(name,page_ini,callback){
        var dbt = cnst.name2domainboardthread(name,true);
        var domain = dbt[0];
        var board = dbt[1];
        var thread = dbt[2];
        var page_idx = [];
        var max_page = site2[domain].max_page(dbt[1]);
        page_idx[0] = page_ini;
        page_idx[1] = (page_ini+1)%(max_page+1);
        for (var i=0;i<=max_page;i++) if (i!=page_idx[0] && i!=page_idx[1] && (!refresh_use_cache || i!=max_page)) page_idx.push(i);
//        if (!refresh_use_cache) for (var i=0;i<5;i++) page_idx.push(page_idx[i]); // for retry, but live cache working...
        var page_no;
        var key;
      
        function page_check(){
          page_no = page_idx.shift();
          key = (page_no==max_page)? domain+board+thread : domain+board+'p'+page_no;
//          http_req.get('catalog',key,url,req_events_page_check,refresh_use_cache);
          http_req.get('catalog',key,'',req_events_page_check,refresh_use_cache,true);
        }
        page_check();
        function req_events_page_check(key,value) {
//console.log('Read : '+name+', '+key+', '+value.status);
          var callback_str = null;
          if (value.status!=200) {
            if (page_no==max_page && value.status==404) callback_str = 'Dead';
            else callback_str = 'HTTP'+value.status;
          } else {
            var doc = ('response' in value)? value.response : new DOMParser().parseFromString(value.responseText, 'text/html');
            var ops = site2[domain].get_ops(doc);
            var hit = false;
            if (page_no!=max_page) for (var i=0;i<ops.length;i++) if (thread==ops[i]) {hit=true;callback_str = page_no+((pref.show_page_fraction)? '.'+i : '');}
            if (!hit) {
              if (value.responseText != null && page_idx.length!=0) setTimeout(page_check, 0); // live cache make racing condition with this. I don't know why.
              else callback_str = '?' + ((!refresh_use_cache)? '(missing)' : '');
              if (site2[domain].check_thread_archived(doc)) callback_str = 'Archived';
            }
          }
          if (value.status==200) catalog_insert2(key,value,true); // update
          if (callback_str!==null) {
            if (callback_str==='Dead' || callback_str==='Archived') {
              comment_out_bookmark(name);
//              var tgt = pn12_0_4.getElementsByTagName('textarea')['filter.bookmark_list_str'];
//              tgt.value = comment_out_key(name,tgt.value);
//              pref_func.apply_prep(tgt,true);
            }
            callback(name,callback_str,value.date);
          }
//console.log('Out : '+name+', '+key+', '+value.status);
        }
      }
      function comment_out_bookmark(name){
        var tgt = pn12_0_4.getElementsByTagName('textarea')['filter.bookmark_list_str'];
        tgt.value = comment_out_key(name,tgt.value);
        pref_func.apply_prep(tgt,true);
      }
      function comment_out_key(key,str){
////        var comments = [];
////        var str_ret = '';
////        var comment = new RegExp('//.*$','m');
////        while (1) {
////          var idx = str.search(comment);
////          if (idx!=-1) comments.push([idx,str.match(comment)[0]]);
////          else break;
////        }
//        return str.replace(key,'//$&');
        var rep_str = (pref.catalog.bookmark_list_rm404)? '' : '//$&';
////        return str.replace(new RegExp(key+'(!page)*(,|\n|$)','g'),rep_str); // working code.
        var dbt = key.split(',');
        return str.replace(new RegExp(dbt[0]+dbt[1]+dbt[2]+'(!page)*(,|\n|$)','g'),rep_str);
      }

      pref_func.mirror_targets.pn12_0_2 = pn12_0_2;
      pref_func.mirror_targets.pn12_0_4 = pn12_0_4;

      var GEH = function(clg){ // GeneralEventHandler
        this.ppn = clg.ppn;
        this.subscribers = [];
        this.valid = null;
        this.last_viewed = null;
        this.last_Y = 0;
        this.init2(clg);
      };
      GEH.prototype = {
        setup: function(reset){
          if (pref[embed_mode].scroll_lock && !this.valid && !reset){
            this.valid = this.mouseover_lock.bind(this);
            this.ppn.addEventListener('mouseover', this.valid, true);
          } else if (this.valid) {
            this.ppn.removeEventListener('mouseover', this.valid, true);
            this.valid = null;
          }
        },
//        var geh = site2[site.nickname].general_event_handler[site.whereami];
        mouseover_lock: function(e){
//if (pref.debug_mode['14']) if (last_viewed) site2[site.nickname].general_event_handler[site.whereami].get_mark(last_viewed, get_now_height()+last_Y).style.border = 'none';
          this.last_viewed = e.target;
          this.last_Y = e.clientY;
////          if (geh.last_viewed.call(this,e)) show_catalog_scroll_lock.viewed(e.target);
//          //          e.stopPropagation();
//if (pref.debug_mode['14']) {
//  var pn = site2[site.nickname].general_event_handler[site.whereami].get_mark(e.target, get_now_height()+last_Y);
//  if (!pn.classList.contains('post')) console.log(e.target, pn)
//  pn.style.border = '1px solid blue';
//}
        },
        change: (function(){
          var class_expander = pref.cpfx+'expander';
          return function(e){
            if (e.target.className===class_expander) {
              var clg = this['.'];
              var name = e.target.getAttribute('data-key');
              clg.threads[name][16].expand_event(e.target.selectedIndex, clg.threads[name][10]);
//              threads[name][16].t2h_sel = format_html.get_t2h_from_index(e.target.selectedIndex);
//              threads[name][16].expand_posts = true;
////            drawn_idx = 0; // patched in 'show_catalog', because this is required when images are expanded.
//              scan.scan_ui('expand_page', {tgts:[name], options:{refresh:true}});
              e.target.blur();
            }
          };
        })(),
        blacklist_click: new WeakSet(),
        geh: site2[site.nickname].general_event_handler[site.whereami],
        click_bl: function(e){
          var et = e.target;
          if (e.target.className===pref.cpfx+'tag') liveTag.boardlist_click_entry(e);
        },
        click: function(e){
          var et = e.target;
          var clg = this['.'] || pClg;
          var etp;
          if (e.target.className===pref.cpfx+'tag') {
            clg.liveTag.tag_node_onclick(e);
//          } else if (e.target.className===pref.cpfx+'footerMenu') { // working code
//            site2['DEFAULT'].popups_posts.menu_click.call(this,e,e.target);
////            var geh = GEH.prototype.geh;
////            var tgt = geh.recSearch_thread(e.target, e.currentTarget) || geh.recSearch_thread(e.target, e.currentTarget, pref.cpfx+'headline'); // copied from popups_posts.triage
////            var name = gGEH.pns_all_keys.get(tgt) || gGEH.pns_all_keys.get(tgt.parentNode); // tgt.parentNode for vichan catalog
////            if (name) return cataLog.triage.thread_in(e, name, tgt, clg.ppn, clg, clg.mode==='float', e.target);
          } else if (e.target.className===pref.cpfx+'button') {
            if (e.target.dataset.type==='triage') triage.embedded_onclick(e,clg);
            else {
              var name = e.target.getAttribute('name');
              var key = gGEH.get_key_recursive(e.target, e.currentTarget);
              var onchange_funcs = this['.'].onchange_funcs;
              if (onchange_funcs[name]) onchange_funcs[name].call(onchange_funcs, e, name, key);
            }
//            else if (e.target.dataset.type==='triage') {
//              var key = gGEH.get_key_recursive(e.target, e.currentTarget);
//              clg.triage_event(key, e.target.dataset.cmd, e.target.dataset.attr || '', null, e.target);
//            }
            e.stopPropagation();
//          } else if (pref.test_mode['194'] && (etp = et.tagName==='svg'? et : et.parentNode, etp && etp.tagName==='svg') && etp.classList.contains(pref.cpfx+'embeddedTriage')) {
//            triage.embedded_onclick(e,clg, etp.classList.contains('sticky')? {cmd:'UNSTICKY',attr:''}:null);
//            return stop_prevent(e);
          } else if (et.classList.contains(pref.cpfx+'embeddedTriage')) {
            triage.embedded_onclick(e,clg, et.classList.contains('sticky')? {cmd:'UNSTICKY',attr:''}:null); // svg can have dataset >chrome55
            return stop_prevent(e);
////            var key = gGEH.get_key_recursive(e.target, e.currentTarget);
////            Triage.prototype.prep_func_onclick(function(cmd,attr){
////              if (cmd==='MENU') {if (key) triage.make_menu(e,key,clg);}
////              else clg.triage_event(key,cmd,attr);})(e);
//          } else if (e.target.className===pref.cpfx+'UImerge') {
//            var key = gGEH.get_key_recursive(e.target, e.currentTarget);
//            var href = e.target.previousSibling.previousSibling.getAttribute('href');
//            var dbt = site2[site.nickname].popups_href2dbtp(href);
//            clg.merge_bases.add_to_list_fromUI(key, dbt.slice(0,3).join(''), clg);
          } else if (!pref.test_mode['104']) {
            var isThumbnail = GEH.prototype.geh.isThumbnail(e);
            if (clg.view==='catalog' && (isThumbnail || pref[clg.view].click_area==='entire') || clg.view==='headline' && et.className!==pref.cpfx+'tag') { // this['.'] check for popup
              e.preventDefault();
              if (clg.view==='headline' && !window.getSelection().isCollapsed) return;
              var key = gGEH.get_key_recursive(e.target, e.currentTarget);
//              if (typeof(key)!=='string') key = key.lths[0].key;
              if (key) clg.click_thread(key, null);
//              var tgt = GEH.prototype.geh.recSearch_thread(e.target, e.currentTarget);
//              if (tgt) click_thread(tgt.name);
            } else if (isThumbnail) {
              var domain = site2['DEFAULT'].popups_posts.href2domain(et.getAttribute('src'));
              if (!GEH.prototype.blacklist_click.has(et) && (!pref[embed_mode].env.expand_thumbnail_inline_native || !(pref[embed_mode].env.event_dynamic && domain===site.nickname)))
                DIH.expand_thumbnail_inline.call(e.target, e);
              else if (pref.test_mode['163']) DIH.expand_thumbnail_clicked_but_ineffective(e.target);
            }
//          if (!pref.test_mode['104'] && GEH.prototype.geh.isThumbnail(e)) {
//            if (cataLog.embed_mode==='catalog') click_thread_tn(e);
//            else {
//              var domain = site2['DEFAULT'].popups_posts.href2domain(et.getAttribute('src'));
//              if (!GEH.prototype.blacklist_click.has(et) && (!pref[embed_mode].env.expand_thumbnail_inline_native || !(pref[embed_mode].env.event_dynamic && domain===site.nickname)))
//                DIH.expand_thumbnail_inline.call(e.target, e);
            //            }
            triage.off();
          }
          if (et.tagName==='A') {
//            if (pref.test_mode['147'] && clg.view==='catalog') { // DnD UI is far better
//              var key = gGEH.get_key_recursive(e.target, e.currentTarget);
//              var href = e.target.getAttribute('href');
//              var domain = site2['DEFAULT'].popups_posts.href2domain(href);
//              var dbtp = site2[domain].popups_href2dbtp(href);
//              var tgt = dbtp.slice(0,3).join('');
//              if (key && tgt && dbtp[2] && key!==tgt) clg.triage_event(key, 'MERGEC', tgt);
//              if (!clg.threads[tgt]) scan.scan_ui('mergeClicked', {tgts:[tgt], options:{refresh:clg}});
//              return stop_prevent(e);
//            }
            if (pref[clg.mode].inline_post!=='no' && site2['DEFAULT'].popups_link_regex.test(et.textContent)) {
              site2['DEFAULT'].popups_posts.inline_post(e, clg);
              return stop_prevent(e);
            }
            if (pref.test_mode['93']) {
              var filename = et.getAttribute('download');
              if (filename) {
                var href = et.getAttribute('href');
                var tgt_domain = href.search(/^(https*:)*\/\//)!=-1 && href.replace(/^(https*:)*\/\//,'').match(/^([^/]*)\//)[1];
                for (var i=0;i<site0.site2keys.length;i++) if (site2[site0.site2keys[i]].domain_url===tgt_domain) {tgt_domain = site0.site2keys[i]; break;}
//                if (site2['ALL'][tgt_domain]) tgt_domain = site2[site2['ALL'][tgt_domain]].nickname;
                if (tgt_domain!=site2[site.nickname].domain_url) {
                  httpd.req({tgts:[{url:href, domain:tgt_domain, archive:filename, responseType:'blob'}], max:1, initiator:'download', callback_1:function(){}},6);
                  return stop_prevent(e);
                }
              }
            }
            if (pref.test_mode['87']) {
              if (et.href.indexOf('//www.youtube.com')!==-1) insert_iframe_youtube(et.href.replace(/watch\?v=/,'embed/'), e);
              else if (et.href.indexOf('//youtu.be')!==-1)   insert_iframe_youtube(et.href.replace(/youtu.be/,'youtube.com/embed'), e);
              else {
                var cmd = et.getAttribute('data-cmd');
                if (cmd && cmd.indexOf(pref.script_prefix)===0) {
                  cmd = cmd.substr(pref.script_prefix.length+1);
                  if (cmd==='remove_next') remove_next(e);
                }
              }
            }
          }
          function insert_iframe_youtube(url, e, et){
            var et = e.target;
            var close_button = document.createElement('span');
            close_button.innerHTML = ' [<a data-cmd="'+pref.cpfx+'remove_next">Remove</a>] ';
            if (!et.nextSibling || et.nextSibling.innerHTML!==close_button.innerHTML) {
              e.target.parentNode.insertBefore(close_button,et.nextSibling);
              var div = document.createElement('div');
              div.innerHTML = '<iframe src="' + url + '" width="640" height="360" frameborder="0" allowfullscreen=""></iframe>';
              div.classList.add('media-embed');
              et.parentNode.insertBefore(div,close_button.nextSibling);
            }
            stop_prevent(e);
          }
          function remove_next(e){
            e.target.parentNode.parentNode.removeChild(e.target.parentNode.nextSibling);
            e.target.parentNode.parentNode.removeChild(e.target.parentNode);
          }
          function stop_prevent(e){
            e.stopPropagation();
            e.preventDefault();
          }
        },
        get_last_viewed: function(){
          return (this.last_viewed)? site2[site.nickname].general_event_handler[site.whereami].get_mark(this.last_viewed, get_now_height()+this.last_Y) : null;
        },
        clear_last_viewed: function(){this.last_viewed = null;},
        init: function(){
//          var dynamic_image_hover = site2[site.nickname].parse_funcs[site.whereami+'_html'].dynamic_image_hover;
          var geh = site2[site.nickname].general_event_handler[site.whereami];
//          if (geh && geh.mouseover && (geh.add_mouseover || dynamic_image_hover)) if (pref.test_mode['103']) comf.dom_addEventListener(this.subscribers, this.parent, 'mouseover', geh.mouseover);
//          comf.dom_addEventListener(this.subscribers, this.ppn, 'change', this.change);
//          if (pref.test_mode['87'] || pref.test_mode['93'] || !pref.test_mode['104']) comf.dom_addEventListener(this.subscribers, this.ppn, 'click', this.click);
          if (!pref.test_mode['104']) site.popup_body.addEventListener('click', this.click, false);
          this.setup();
////          if (!pref.test_mode['98']) if (pref.test_mode['103']) site.popup_body.addEventListener('mouseover', site2[site.nickname].general_event_handler['page'].mouseover, false);
//          site.root_body.addEventListener('dragstart', this.dragstart, false);
//          site.root_body.addEventListener('dragend', this.dragend, false);
////          this.ppn.addEventListener('dragstart', this.dragstart, false);
////          this.ppn.addEventListener('dragend', this.dragend, false);
////          site.popup_body.addEventListener('dragstart', this.dragstart, false);
////          site.popup_body.addEventListener('dragend', this.dragend, false);
        },
        init2: function(clg){
          var tgt = {'.':clg, __proto__:this};
          if (pref.test_mode['87'] || pref.test_mode['93'] || !pref.test_mode['104']) comf.dom_addEventListener(this.subscribers, this.ppn, 'click', this.click.bind(tgt));
          comf.dom_addEventListener(this.subscribers, this.ppn, 'change', this.change.bind(tgt));
          site2['DEFAULT'].popups_posts.init2(pref.test_mode['141']? clg.pppn : clg.ppn, tgt);
          comf.dom_addEventListener(this.subscribers, site.root_body, 'dragstart', this.dragstart.bind(tgt));
          if (!pref.test_mode['188']) comf.dom_addEventListener(this.subscribers, this.ppn, 'mousedown', this.dnd_merge_mousedown.bind(tgt));
        },
        destroy: function(){
          this.setup(true);
          comf.dom_removeEventListener(this.subscribers);
        },
        mouseover: function(e, force){
          var et = e.target;
          site2['DEFAULT'].popups_posts.over(e, force);
          if (pref.liveTag.info && pref.tooltips['info'].show)
            if (et.className===pref.cpfx+'tag') Tooltips.req_show('info', e, liveTag.tag_onmouseover); // mouseover only for faster execution. mouseleave is needed strictly.
            else Tooltips.hide_if2(e); // e.currentTarget); // hide2();
          
        },
        mouseleave_bl: function(e){
          if (pref.liveTag.info) Tooltips.hide_if2(e); // e.currentTarget); // hide2();
        },
        dragstart: (function(){ // snatch image hover and merge by drag'n'dropping thumbnails
          var clg = null;
          var src = null;
          var dst = null;
          var hover = null;
          function dragenter(e){
            dst = gGEH.get_key_rec(e); // must calculate here, e at dragend is the same as e at dragstart.
            if (hover && src!==dst) {
              DIH.image_hover_remove(e);
              hover = null;
            }
          }
          function dragend(e,dx,dy){
            document.body.removeEventListener('dragenter',dragenter,false);
            if (hover) DIH.image_hover_snatch(e,dx,dy);
            else if (src && !pref.test_mode['188']) {
//              var dst = gGEH.get_key_rec(e); // can't calculate dst here, e.target is the same as e at dragstart.
              if (dst!==src) clg.triage_event(src, 'MERGECU', dst||null); // 'dst||null' for unmerge
            }
          }
          return function(e){
            if (!GEH.prototype.geh.isThumbnail(e)) return;
            clg = this['.'];
            var pfm = clg.pref[clg.mode];
            if (clg.view==='catalog' && pfm.merge_list && !pfm.merge) {
              src = gGEH.get_key_rec(e);
              hover = src;
              document.body.addEventListener('dragenter',dragenter,false);
              gGEH.drag.started(e, dragend, 'copy');
            } else gGEH.drag.started(e, DIH.image_hover_snatch, 'copy');
            site2['DEFAULT'].popups_posts.pop_down_temporarily(e);
          };
        })(),
//        dragstart: function(e){ // working code
//          if (GEH.prototype.geh.isThumbnail(e)) gGEH.drag.started(e, DIH.image_hover_snatch, 'copy');
//          site2['DEFAULT'].popups_posts.pop_down_temporarily(e);
//        },
//        drag_sx: 0,
//        drag_sy: 0,
//        drag_effect: null,
//        dragstart: function(e){
//          var myself = GEH.prototype;
//          myself.drag_sx = e.screenX;
//          myself.drag_sy = e.screenY;
//          if (myself.geh.isThumbnail(e)) {
//            document.body.addEventListener('dragover',myself.dragover,false);
//            myself.drag_effect = 'copy';
//          }
//        },
//        dragover: function(e){
//          e.preventDefault();
//          e.dataTransfer.dropEffect = GEH.prototype.drag_effect;
//        },
//        dragend: function(e){
////          if (!pref[embed_mode].thumbnail.hover.dragfloat || (!DIH.hover_ex && e.target.tagName!=='IMG')) return;
//          var myself = GEH.prototype;
//          if (myself.drag_effect) {
//            document.body.removeEventListener('dragover',myself.dragover,false);
//            myself.drag_effect = null;
//          }
//          DIH.image_hover_snatch(e, myself.drag_sx, myself.drag_sy);
////          var img = DIH.image_hover_snatch();
////          var right = ((img)? parseInt(img.style.right,10) : cnst.left2right(e.clientX - e.offsetX, e.target)) - (e.screenX - myself.drag_sx);
////          var top = ((img)? parseInt(img.style.right,10) : e.clientY - e.offsetY) + e.screenY - myself.drag_sy;
////          var cn = cnst.init3({
////            func_str:'right:'+right+'px:top:'+top+'px:ftb:overflow:hidden:Show'}).cn;
//////            func_str:'right:'+right+'px:top:'+top+'px:tb:ftb:overflow:auto:Show'}).cn;
////          cn.appendChild(img || e.target.cloneNode());
////          if (img) DIH.image_hover_snatch_end();
//        },
        dnd_merge_mousedown: (function(){
          var pn_th = null;
          var clg = null;
          var sx = 0;
          var sy = 0;
          var pn_dragEffect = null;
          var pn_th_oL = 0;
          var pn_th_oT = 0;
          var syo = 0;
          function mouseleave(e){
            var dx = e.screenX - sx;
            var dy = e.screenY - sy;
            if (Math.abs(dy)<Math.abs(dx)) return; // vertical +-45 degree
            site2['DEFAULT'].popups_posts.pop_down_temporarily(e, true);
            var pn = pn_th.cloneNode(true);
            pn.style.width = pn_th.offsetWidth;
            pn.style.height = pn_th.offsetHeight;
            pn.style.position = 'fixed';
            pn.style.opacity = 0.4;
            pn.style.pointerEvents = 'none';
            var bcr = pn_th.getBoundingClientRect();
            pn_th_oL = bcr.left; // pn_th.offsetLeft;
            pn_th_oT = bcr.top; // pn_th.offsetTop;
            pn_dragEffect = pn;
            mousemove(e);
            clg.ppn.appendChild(pn);
//            pn_th.removeEventListener('mouseleave', mouseleave, false);
            clg.ppn.addEventListener('mousemove', mousemove, false);
//            var evt = document.createEvent('MouseEvents'); // doesn't work
//            evt.initMouseEvent(e.type, e.canBubble, e.cancelable, e.view,
//                               e.detail, sx, sy, e.clientX - dx, e.clientY - dy,
//                               e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
//                               e.button, e.relatedTarget);
//            pn_th.dispatchEvent(evt);
//            pn_th.draggable = true;
//            evt.initMouseEvent(e.type, e.canBubble, e.cancelable, e.view,
//                               e.detail, e.screenX, e.screenY, e.clientX, e.clientY,
//                               e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
//                               e.button, e.relatedTarget);
//            pn_th.dispatchEvent(evt);
//            gGEH.drag.started(e, merge, 'move');
          }
          function mouseenter(e){ // reentry
            if (!pn_dragEffect) return; // chrome51 doesn't fire 'mouseleave' while selection is continueing, but fire 'mouseenter'
            clg.ppn.removeChild(pn_dragEffect);
            clg.ppn.removeEventListener('mousemove', mousemove, false);
            pn_dragEffect = null;
            window.getSelection().removeAllRanges();
          }
          function mousemove(e){
            window.getSelection().removeAllRanges();
            var dx = e.screenX - sx;
            var dy = e.screenY - sy;
            if (!pn_dragEffect) return; // for racing
            pn_dragEffect.style.left = pn_th_oL + dx + 'px';
            pn_dragEffect.style.top = pn_th_oT + syo + dy + 'px';
          }
          function end_proc(e){
            clg.ppn.removeEventListener('mouseup', mouseup, false);
            clg.ppn.removeEventListener('mouseleave', end_proc, false);
//            if (pn_dragEffect) return (mouseenter(e), true);
//            else
            pn_th.removeEventListener('mouseleave', mouseleave, false);
            pn_th.removeEventListener('mouseenter', mouseenter, false);
//            pn_th.draggable = false;
            site2['DEFAULT'].popups_posts.pop_up_unlock();
            return pn_dragEffect && (mouseenter(e), true);
          }
          function mouseup(e){
            if (!end_proc(e)) return;
            var src = gGEH.get_key_recursive(pn_th, clg.ppn, true);
            var dst = gGEH.get_key_rec(e);
            if (src && dst!==src) clg.triage_event(src, 'MERGECU', dst||null); // 'dst||null' for unmerge
          }
          return function(e){ // mousedown
            clg = this['.'];
            var pfm = clg.pref[clg.mode];
            if (clg.view!=='headline' || !pfm.merge_list || pfm.merge) return;
            pn_th = this.geh.recSearch_thread(e.target, clg.ppn, clg.view==='headline'? pref.cpfx+'headline' : undefined);
            if (!pn_th) return;
            sx = e.screenX;
            sy = e.screenY;
            syo = e.offsetY - pn_th.offsetHeight/2;
            clg.ppn.addEventListener('mouseup', mouseup, false);
            clg.ppn.addEventListener('mouseleave', end_proc, false);
            pn_th.addEventListener('mouseleave', mouseleave, false);
            pn_th.addEventListener('mouseenter', mouseenter, false);
          };
        })(),
      };
      cataLog.GEH = GEH;
      cataLog.general_event_handler = new GEH(pClg); // triage_parent);
//      cataLog.general_event_handler.last_viewed = gGEH.last_viewed; // avoids racing condition of 'receive_message'
      setTimeout(cataLog.general_event_handler.init.bind(cataLog.general_event_handler), 500); // WHY 500ms delay?

      var GEH_mouseover_force = (function(func){return function(e){func(e,true);e.stopPropagation()};})(GEH.prototype.mouseover);
//      var GEH_mouseover_force = (function(func){return function(e){func(e,true);};})(GEH.prototype.mouseover);
      if (!pref.test_mode['98']) site.popup_body.addEventListener('mouseover',GEH_mouseover_force,false);
      site.root_body.addEventListener('mouseover', GEH.prototype.mouseover, false);
//      triage_parent.addEventListener('mouseover', (embed_mode==='float')? GEH_mouseover_force : GEH.prototype.mouseover, false);
//      triage_parent.addEventListener('mouseleave', site2['DEFAULT'].popups_posts.leave, false);
      if (site.components.boardlist) {
        site.components.boardlist.addEventListener('click',GEH.prototype.click_bl,false);
//        site.components.boardlist.addEventListener('mouseover',GEH.prototype.mouseover,false);
//        site.components.boardlist.addEventListener('mouseleave',GEH.prototype.mouseleave_bl,false);
      }
      site2['DEFAULT'].popups_posts.init(pClg.ppn); // triage_parent);

      if (!pref.test_mode['114']) {
        pClg.GEH = cataLog.general_event_handler;
      }
      function make_watcher(){
//        var pn_test = document.createElement('div');
        var clg = new Clg(false,'float', true);
        if (pref.float.clone) {
          var clone_clg = Clg.prototype.Clgs.filter(function(v){return clg.pref.float.board_list_sel===v.pref[v.mode].board_list_sel;})[0]; // hits myself always
          if (clone_clg!==clg) clg.clone_threads(clone_clg);
        }
//        if (pref.test_mode['133']) for (var name in pClg.threads) clg.clone_thread(name);
//        clg.ppn.setAttribute('style','white-space:nowrap');
//        clg.ppn.setAttribute('style','text-overflow:ellipsis;white-space:nowrap;width:100%');
        clg.GEH = new GEH(clg);
        if (site2[site.nickname].catalog_native_prep_watcher) {
          site2[site.nickname].catalog_native_prep(clg.components.pn12_0_4, clg.components.pn12_0, clg.health_indicator.pn2, clg);
          if (clg.native.viewChanged) clg.native.viewChanged();
        }
        clg.show_catalog();
      }
      Clg.prototype.clone_threads = function(clg){
        for (var name in clg.threads) if (!this.threads[name]) {
          this.clone_thread_1(name);
          this.view_attr_set(name, null, true);
          this.idx_reorder(name, true);
        }
//        this.idx_reorder_exe();
      }
      Clg.prototype.clone_thread_1 = function(name){
//          var max_txt = 500;
          var lth = liveTag.mems.getFromName(name);
//          var pn = document.createElement('div');
//          pn.className = pref.cpfx+'headline'; // +' thread'; // might cause CSS problem so much.
////          pn.setAttribute('style','white-space:nowrap;text-overflow:ellipsis;overflow:hidden;width:100%');
//          gGEH.pns_all_keys.set(pn,name);
//          var txt = site2[lth.domain].post_com2txt(lth.th);
//          var sub_com = ((lth.th.sub? '<b>'+lth.th.sub+'</b>: ': '') + txt);
//          var sub_com_max = max_txt - (lth.th.sub? 2:0);
//          if (sub_com.length>=sub_com_max) sub_com = sub_com.slice(0, sub_com_max) + '...';
//          pn.innerHTML = '<span></span> '+sub_com;
//          clg.view_attr_set(name,pn);
          var pth = gClg.AllThreads[name]; // pClg.threads[name];
          var keys = Object.keys(pth[16]);
          var tgt16 = {th:{pn:lth.th.type_data==='html'? lth.th.pn.cloneNode(true) : null, __proto__:lth.th}, lth:lth, __proto__:tgt_th16_proto};
          var nocopy = ['th', 'icon_sticky', 'icon_showAlways', 'showAlways', 'clg', 'threads'];
          for (var i=0;i<keys.length;i++) if (nocopy.indexOf(keys[i])==-1) tgt16[keys[i]] = pth[16][keys[i]];
        this.threads[name] = {0:false, 1:false, 9:[null,0,null,0], 10:null, 16:tgt16, 20:pth[20], /*22:null,*/ 24:null, __proto__:pth};
//          clg.threads[name] = {0:false, 1:false, 9:[null,0,null,0], 16:{th:{pn:null, __proto__:lth.th}, __proto__:pClg.threads[name][16]}, 22:null, 24:null, __proto__:pClg.threads[name]};
//          clg.threads[name] = {0:pn, 1:false, 9:[null,0,null,0], 16:{__proto__:pClg.threads[name][16]}, 22:null, 24:[pn.firstChild, null,null,null,0x06], __proto__:pClg.threads[name]};
      }
      window.addEventListener('storage', Clg.prototype.Sync_watch_load, false); // debug
      if (pClg.mode!=='float' && pref.float.auto_launch) setTimeout(make_watcher,1000); // 1000 for dummy, this works without delay.
      
      if (stats && pref.stats.auto_acquisition_scan && site.whereami!=='archive') setTimeout(stats.auto_acquisition_init, pref.stats.auto_acquisition_scan_delay*1000);
      return {
        destroy: function(){ // destructor
//          if (!pref.test_mode['114']) {
//            styleSheet.register_cc('headline','white-space:nowrap;text-overflow:ellipsis;overflow:hidden;width:100%');
//            make_watcher();
//            return this;
//          }

          cataLog.general_event_handler.destroy();
//          for (var name in threads) gClg.remove_thread(name);
//          pref_func.remove_onchange(pn12_0_2); // prevent leak.
//          pref_func.remove_onchange(pn12_0_4); // prevent leak.
          pref_func.mirror_targets.pn12_0_2 = null;
          pref_func.mirror_targets.pn12_0_4 = null;
//          catalog_clear_threads(0);
          if (brwsr.sw_cache && brwsr.sw_cache.subscribe) brwsr.sw_cache.subscribe(false);
//          pref_func.health_indicator = null;
          health_indicator.destroy();
//          if (embed_catalog) if (catalog_native_destroy) catalog_native_destroy();
          cnst.auto_shrink_board_selector.destroy(board_sel);
          window_beforeunload();
//          pref_func.tooltips.remove_hier(pn12_triage);
          triage.destroy();
//          pref_func.tooltips.remove_hier(pn12_0_4);
          Tooltips.remove_root(pn12_0_4);
          Tooltips.hide();
//          clearTimeout(auto_update_timer);
//          auto_update.stop_if_running();
          pn12.parentNode.removeChild(pn12);
          window.removeEventListener('storage', site2[site.nickname].prep_own_posts_event, false); // debug
          pn12 = null;
          liveTag.pn = null;
          for (var i in cataLog) cataLog[i] = null;
          window.removeEventListener('storage', Clg.prototype.Sync_watch_load, false); // will be deleted.
          return null;
        },
        triage_exe_pipe: triage_exe_pipe,
        catalog_insert: catalog_insert,
        pn12_0_4: pn12_0_4,
        pn12_0_2: pn12_0_2,
//        catalog_resized: catalog_resized,
        get_threads: function(){return threads;},
//        catalog_filter_changed: catalog_filter_changed,
        scan_boards: scan_boards,
        show_catalog: show_catalog,
//        threads: threads,
        catalog_liveTag_scan_site: onchange_funcs['scanSite'],
        make_watcher: make_watcher,
      }
    }
    return {
      catalog_func: function(){return catalog_func;},
//      scan_tags_common: scan_tags_common,
      show_hide: show_hide,
    }
  }

  var stats = (pref3.stats.use && !site0.isStep)? (function(){
    var tree_404 = {};
    function thread_removed(dbt){
      var lth = liveTag.mems[dbt[0]][dbt[1]][dbt[2]];
      if (lth.ss) {
        if (Array.isArray(lth.ss)) for (var i=0;i<lth.ss.length;i++) {if (lth.ss[i].rm) lth.ss[i].rm.update_thread(lth.s, 0, true);}
        else if (lth.ss.rm) lth.ss.rm.update_thread(lth.s, 0, true);
      }
      if (pref.stats.retain_404 && lth.s) {
        if (!tree_404[dbt[0]]) tree_404[dbt[0]] = {};
        if (!tree_404[dbt[0]][dbt[1]]) tree_404[dbt[0]][dbt[1]] = {};
        tree_404[dbt[0]][dbt[1]][dbt[2]] = liveTag.mems[dbt[0]][dbt[1]][dbt[2]];
//        if (tree_404[dbt[0]][dbt[1]].sr || Object.keys(tree_404[dbt[0]][dbt[1]]).length>64) update_board_rm(tree_404[dbt[0]][dbt[1]]);
        if (pref.test_mode['44'] || tree_404[dbt[0]][dbt[1]].sr || Object.keys(tree_404[dbt[0]][dbt[1]]).length>64) update_board_rm(tree_404[dbt[0]][dbt[1]]);
      }
    }
    
    var label_funcs = {
      hour: function(t){return new Date(t*60000).toString().split(' ')[4].replace(/:\d*$/,'');},
      day: function(t){return new Date(t*60000).toString().replace(/\s\d{4}.*/,'');},
      sunday : function(t){
        var str = new Date(t*60000).toString();
        return (str.split(' ')[0]=='Sun')? str.replace(/\s\d{4}.*/,'') : '';
      },
      month : function(t){
        var str = new Date(t*60000).toString();
        return (str.split(' ')[2]=='01')? str.replace(/\s\d{4}.*/,'') : '';
      },
      quarter_year : function(t){
        var strs = new Date(t*60000).toString().split(' ');
        return (strs[2]<=7 && ['Jan','Apr','Jul','Oct'].indexOf(strs[1])!=-1)? strs.slice(1,4).join(' ') : '';
      },
    }
    label_funcs['1'] = [60, label_funcs.hour, 1];
//    label_funcs['2'] = [60, label_funcs.hour, 1]; // debug
    label_funcs['10'] = [60, label_funcs.hour, 1];
    label_funcs['60'] = [1440, label_funcs.day, 1];
    label_funcs['1440'] = [1440, label_funcs.sunday, 0];
    label_funcs['10080'] = [1440, label_funcs.quarter_year, 0];
//    var time_unit_arr = [1,2,60,1440,10080]; // debug
    var time_unit_arr = [1,10,60,1440,10080];

    function thread_created(lth){
      for (var i=0;i<instances.length;i++) {
        var result = pref_func.merge_obj5(lth.key, instances[i].tgts_obj2,null);
        if (result) instances[i].check_and_add_thread(lth);
      }
    }
    var time_root = Math.floor(Date.now()/60000);
    var instances= [];
//    var count = 0;
    var Stats = function(chart, tgts, key){
      instances.push(this);
      this.chart = chart;
      this.tolerance = 0;
//      this.name = 'graph_'+ count++;
      this.draw = DelayBuffer.prototype.delayed_do.bind(new DelayBuffer(this.refresh.bind(this), pref.stats.draw_delay*1000));
      this.wdg_tick = new Watchdog(this.prep_time.bind(this), 60000);
      this.fromNo = (pref3.stats.estimate_posts)? new StatsFromNo(this) : null; // for subscribe, this pointer can't be changed.
      this.tgts_changed(tgts, key, true); // this.tgts, this.key
      this.time_unit_changed(pref.chart.inst.time_sel, true); // this.time_base, this.time_unit, this.time_last, time_sel
      this.len_changed((this.chart && this.chart.len>pref.stats.len_capture)? this.chart.len : pref.stats.len_capture); // this.len, this.data_p, this.data_t, this.data_l, this.min_idx, this.data_p_old, this.data_t_old, this.shift, this.rm.data_p, this.rm.data_t
    };
    Stats.prototype = {
      destroy: function(){
        this.chart = null;
        this.save_all_unsubscribe(true);
        this.wdg_tick.stop();
        instances.splice(instances.indexOf(this),1);
      },
      merge_1: function(dst,src){
        if (src) for (var i=0;i<src.length;i++) if (dst[i]<src[i]) dst[i] = src[i]; // src[i] may be undefined.
        return dst;
      },
      merge_1_hier: function(dst,src, lower){
        var num = this.time_unit/lower.time_unit;
        var start = Math.ceil((lower.time_base - this.time_base)/this.time_unit);
        var i = (num - (lower.time_base%this.time_unit)/lower.time_unit)%num; // start offset
        while (i<src.length) {
          this.merge_1_hier_1(dst, src, start, i, num, lower);
          i+=num;
          start++;
        }
      },
      merge_1_hier_1: function(dst,src,idx_d,idx_s,num, lower){
        var data = 0;
        var end = (src.length>=idx_s+num)? num : src.length - idx_s;
        for (var j=0;j<end;j++) data += src[idx_s+j];
        if (pref.debug_mode['15'])
          if (data>dst[idx_d])
            console.log('merge_1_hier_1: '+this.time_unit+': '+idx_d+', '+dst[idx_d]+'->'+data+', '+
                        new Date((this.time_base+idx_d*this.time_unit)*60000).toLocaleString()+', '+
                        new Date((lower.time_base+idx_s*lower.time_unit)*60000).toLocaleString()+', '+num);
        if (data>dst[idx_d]) dst[idx_d] = data;
      },
      save_all_unsubscribe: function(from_destructor){ // destructive
        if (this.tgts && pref.stats.save && auto_acquisition_keys['@'+this.key]!==undefined) {
          var timestamp = Math.floor(Date.now()/60000);
          if (auto_acquisition_keys['@'+this.key]!==timestamp) { // prevent from saving twice in auto_acquisition
            var chart_bak = this.chart;
            this.chart = null; // don't draw
            var idx = time_unit_arr.indexOf(this.time_unit);
////            this.save();
////            for (var i=1;i<time_unit_arr.length;i++) {
////              this.time_unit_changed((idx+i)%time_unit_arr.length);
////              this.save();
////            }
            var data_lower = null;
            for (var i=0;i<time_unit_arr.length;i++) {
              this.time_unit_changed(i);
              this.save(data_lower);
              data_lower = {data_p:this.data_p, data_t:this.data_t, time_unit:this.time_unit, time_base:this.time_base};
            }
            if (!from_destructor) this.time_unit_changed(idx);
            this.chart = chart_bak;
            auto_acquisition_keys['@'+this.key] = timestamp;
          }
        }
        if (this.tgts) {
          this.prep('both',this.prep_thread_unsubscribe);
          if (this.fromNo) this.fromNo.prep('live',this.fromNo.prep_board_unsubscribe);
        }
      },
      Zcompress: function(str){
        return str.replace(/0,(0,)+/g,function(match){return 'z'+match.length/2+',';});
//        var test2 = this.Zdecompress(test);
//        if (test2!==str) console.log('ERROR: '+str+', '+test+', '+test2);
//        return str;
      },
      Zdecompress: function(str){
        return str.replace(/z(\d+),/g,function(match,p1){
          var str = '';
          var len = parseInt(p1,10);
          for(var i=0;i<len;i++) str+='0,';
          return str;
        });
      },
      get_ls_key: function(){
        if (pref.test_mode['38']) return pref.script_prefix + '.graph.' + this.time_unit + '.' + this.key+'_test38';
        return pref.script_prefix + '.graph.' + this.time_unit + '.' + this.key;
      },
      save: function(data_lower){ // destructive
        if (this.data_p && pref.stats.load && pref.stats.save && localStorage) {
          this.merge_1(this.data_p, this.data_p_old);
          this.merge_1(this.data_t, this.data_t_old);
          if (data_lower) {
            this.merge_1_hier(this.data_p, data_lower.data_p, data_lower);
            this.merge_1_hier(this.data_t, data_lower.data_t, data_lower);
          }
          var i=0;
          while (i<this.data_p.length && this.data_p[i]===0 && this.data_t[i]===0) i++;
          var data_save = [this.time_base+i*this.time_unit, this.data_p.slice(0,this.len+1).slice(i), this.data_t.slice(0,this.len+1).slice(i)];
if (!pref.test_mode['42']) {
          if (this.fromNo) {
            var fN = this.fromNo;
            fN.valid = true;
            fN.prep('live',fN.prep_board_check_valid);
            if (!fN.valid) for (var j=0;j<fN.data_p.length;j++) fN.data_p[j] = 0;
            else if (fN.data_p_old) {
              var idx_last = fN.data_p_old.length-1;
              if (fN.data_p[idx_last]>fN.data_p_old[idx_last]) fN.data_p_old[idx_last] = fN.data_p[idx_last]; // overwrite last data, because last data may NOT complete its period.
            }
            if (fN.data_p_old) fN.data_p = fN.data_p_old.concat(fN.data_p.slice(fN.data_p_old.length));
            for (var j=fN.data_p.length-2-this.tolerance;j<fN.data_p.length;j++) fN.data_p[j] = 0; // fill 0 to last 2 or more.
            data_save[3] = fN.data_p.slice(0,this.len+1).slice(i);
          }
}
          localStorage[this.get_ls_key()] = this.Zcompress(JSON.stringify(data_save));
          if (pref.debug_mode['15']) console.log('saved: '+this.get_ls_key());
        }
      },
      load: function(){
        if (pref.stats.load && localStorage) {
          var data = localStorage[this.get_ls_key()];
          if (data) {
            data = JSON.parse(this.Zdecompress(data));
            var start = (this.time_base - data[0])/this.time_unit;
            var end   = Math.floor((time_root - this.time_last)/this.time_unit); // negative value.
            if (end===0) end = undefined;
            if (start>=0) {
              this.data_p_old = data[1].slice(start, end);
              this.data_t_old = data[2].slice(start, end);
              if (this.fromNo && data[3]) this.fromNo.data_p_old = data[3].slice(start, end);
            } else {
              this.data_p_old = this.array_init([],0,0).slice(0,-start).concat(data[1].slice(0,end));
              this.data_t_old = this.array_init([],0,0).slice(0,-start).concat(data[2].slice(0,end));
              if (this.fromNo && data[3]) this.fromNo.data_p_old = this.array_init([],0,0).slice(0,-start).concat(data[3].slice(0,end));
            }
            if (this.fromNo && this.fromNo.data_p_old) while (this.fromNo.data_p_old[this.fromNo.data_p_old.length-1]===0) this.fromNo.data_p_old.pop();
          } else {
            this.data_p_old = null;
            this.data_t_old = null;
          }
        } 
      },
      tgts_changed: function(tgts, key, skip_init){
        this.save_all_unsubscribe();
        this.tgts = tgts;
        this.key = key;
        this.tgts_obj2 = pref_func.str2obj2(tgts.join(','),null,true);
        this.prep('both',this.prep_thread_subscribe);
        if (!skip_init) this.init();
      },
      time_unit_changed: function(sel, skip_init){
        if (this.fromNo) this.fromNo.prep('live',this.fromNo.prep_board_unsubscribe);
//        this.save();
//        if (typeof(sel)!=='number') sel = sel.target.selectedIndex;
        this.time_sel = sel;
        this.time_unit = time_unit_arr[sel];
        if (!skip_init) this.init();
      },
      len_changed: function(len){
//        this.save();
        this.len = len;
        this.init();
      },
      init: function(){
        this.time_last = null; // force to remake
        this.data_p = [];
        this.data_t = [];
        this.data_l = [];
        this.rm = (this.chart && (this.chart.show.ep || this.chart.show.et))? new StatsRm(this) : null;
        if (this.fromNo) {
          this.fromNo.data_p = [];
          this.fromNo.data_p_old = [];
          this.fromNo.min_idx = 0;
        }
        this.refresh(true);
      },
      array_init: function(arr,start,val){
        for (var i=start;i<this.len+2;i++) if (arr[i]===undefined) arr[i] = val; // len+2
        return arr;
      },
      tz_offset: new Date().getTimezoneOffset(),
      label_init: function(start){
        var lfunc = label_funcs[this.time_unit];
        var i = (lfunc[2])? Math.floor((Math.floor(((this.time_base-this.tz_offset) + (this.len+2)*this.time_unit)/lfunc[0])*lfunc[0] - (this.time_base-this.tz_offset))/this.time_unit) :
                            this.len+2;
        while (i>=start) {
          this.data_l[i] = lfunc[1](this.time_base + i*this.time_unit);
          i -= (lfunc[2])? lfunc[0]/this.time_unit : 1;
        }
      },
      prep_time: function(init){
        var time_now = Math.floor((Date.now()/60000+this.tolerance)/this.time_unit)*this.time_unit;
        if (this.time_last!==time_now) {
          var distance = (time_now - this.time_last)/this.time_unit;
          this.time_last = time_now;
          this.time_base = time_now - this.time_unit * this.len;
          var start = 0;
          if (!init && distance!==0) {
            if (distance>this.len) distance = this.len;
            this.data_p.splice(0,distance);
            this.data_t.splice(0,distance);
            this.data_l.splice(0,distance);
            if (this.rm) {
              this.rm.data_p.splice(0,distance);
              this.rm.data_t.splice(0,distance);
            }
            if (this.fromNo) {
              this.fromNo.data_p.splice(0,distance);
              if (this.fromNo.data_p_old) {this.fromNo.data_p_old.splice(0,distance); if (this.fromNo.data_p_old.length===0) this.fromNo.data_p_old = null;}
              this.fromNo.min_idx -= distance;
            }
            if (this.data_p_old) {this.data_p_old.splice(0,distance); if (this.data_p_old.length===0) this.data_p_old = null;}
            if (this.data_t_old) {this.data_t_old.splice(0,distance); if (this.data_t_old.length===0) this.data_t_old = null;}
            start = this.len - distance;
            this.min_idx -= distance;
            if (this.min_idx<0) this.min_idx = 0; // this.min_idx <= this.len, therefore this may occur.
          }
          this.array_init(this.data_p,start,0);
          this.array_init(this.data_t,start,0);
          this.array_init(this.data_l,start,'');
          if (this.rm) {
            this.array_init(this.rm.data_p,start,0);
            this.array_init(this.rm.data_t,start,0);
          }
          if (this.fromNo) this.array_init(this.fromNo.data_p,start,0);
          this.label_init(start);
          this.shift = (init)? 0 : this.shift + distance;
        }
        this.wdg_tick.restart(this.time_unit*60000);
      },
      prep: function(tree, func, func_t){
        for (var i=0;i<this.tgts.length;i++) {
          var dbt = comf.name2domainboardthread(this.tgts[i],true);
          if (tree==='live' || tree==='both') this.prep_1(liveTag.mems, dbt, func, func_t);
          if (tree==='dead' || tree==='both') this.prep_1(tree_404, dbt, func, func_t);
        }
      },
      prep_1: function(tree, dbt, func, func_t){
        if (dbt[2]) {
          if (tree[dbt[0]] && tree[dbt[0]][dbt[1]] && tree[dbt[0]][dbt[1]][dbt[2]] && func_t!==null) ((func_t)? func_t : func).call(this,tree[dbt[0]][dbt[1]][dbt[2]]);
        } else if (dbt[1]) {
          if (tree[dbt[0]] && tree[dbt[0]][dbt[1]]) this.prep_board(tree[dbt[0]][dbt[1]],func, func_t);
        } else {
          if (tree[dbt[0]]) this.prep_domain(tree[dbt[0]],func, func_t);
        }
      },
      prep_domain: function(ld,func){
        for (var b in ld) this.prep_board(ld[b],func, func_t);
      },
      prep_board: function(ldb,func){
        for (var t in ldb) func.call(this,ldb[t]);
      },
      prep_thread_init: function(ldbt){
        if (ldbt.s) this.update_thread(ldbt.s, 0);
      },
      check_and_add_thread: function(ldbt){
        if (ldbt.s && !this.prep_thread_unsubscribe(ldbt,true)) {
          this.update_thread(ldbt.s, 0, true);
          this.prep_thread_subscribe(ldbt);
        }
      },
      prep_thread_subscribe: function(ldbt){
        if (Array.isArray(ldbt.ss)) ldbt.ss.push(this);
        else if (ldbt.ss) ldbt.ss = [ldbt.ss, this];
        else ldbt.ss = this;
      },
      prep_thread_unsubscribe: function(ldbt, check_only){
        if (Array.isArray(ldbt.ss)) {
          var idx = ldbt.ss.indexOf(this);
          if (check_only) return (idx!==-1);
          if (idx!=-1) {
            ldbt.ss.splice(idx,1);
            if (ldbt.ss.length===1) ldbt.ss = ldbt.ss[0];
          }
        } else if (ldbt.ss===this) {
          if (check_only) return true;
          delete ldbt.ss;
        } else return false;
      },
      refresh: function(init){
        if (pref.debug_mode['15']) console.log('updated: '+this.data_p.slice(this.len-10).toString()+', '+this.min_idx);
        this.prep_time(init);
        if (init) {
          this.prep('live',this.prep_thread_init);
          this.rm_tmp = new StatsRm(this, this.data_p, this.data_t);
          this.rm_tmp.prep('dead',this.rm_tmp.prep_board_init, this.prep_thread_init);
          if (this.rm) this.rm.prep('dead',this.rm.prep_board_init, this.prep_thread_init);
          if (this.fromNo) this.fromNo.prep('live',this.fromNo.prep_board_init, true);
          this.load();
          if (this.chart) this.chart.replace_data(this);
        } else if (this.shift!==0 || this.min_idx!==this.data_p.length) {
          if (this.fromNo) this.fromNo.prep('live',this.fromNo.prep_board_init, false, true);
          if (this.chart) this.chart.update_data2(this);
        }
        this.min_idx = this.data_p.length; // 'this.min_idx = this.len-1;' is suitable if local and server clocks are synced.
      },
//      remove_thread: function(s, start, from_thread){
//        this.update_thread(s, start, from_thread);
//        if (this.min_idx!==undefined) {
//          this.__proto__.min_idx = this.min_idx;
//          delete this.min_idx;
//        }
//      },
      update_thread: function(s, start, from_thread){
        if (!s) return;
        if (start===0) this.update_thread_1(this.data_t, s, 0, 0);
        this.update_thread_1(this.data_p, s, start, s.length-1);
        if (from_thread) this.draw();
      },
      update_thread_1: function(data, s, start, end){
        for (var j=end;j>=start;j--) {
          var idx = Math.floor((s[j]-this.time_base)/this.time_unit);
          var idx_over = idx - this.len -1;
          if (idx_over>0) {
            if (pref.stats.tolerant) {
              this.tolerance = idx_over*this.time_unit + this.tolerance;
              if (this.tolerance>pref.stats.tolerance) this.tolerance = pref.stats.tolerance;
              this.prep_time(false);
            }
            idx = this.len+1;
          }
          if (idx>=0) {
//            data[(idx<=this.len)? idx : this.len+1]++;
            data[idx]++;
            if (idx<this.min_idx) this.min_idx = idx;
          } else break;
        }
      },
    };
    var StatsRm = function(parent, data_p, data_t){
      this.data_p = data_p || [];
      this.data_t = data_t || [];
      Object.defineProperty(this,'min_idx',{set: function(val){this.__proto__.min_idx = val;},
                                            get: function(){return this.__proto__.min_idx;}, // necessary
                                            configurable:true, enumerable:true});
      for (var i in StatsRm_prototype) this[i] = StatsRm_prototype[i];
      this.__proto__ = parent;
    };
    var StatsRm_prototype = {
      prep_board: function(ldb,func, func_t){
        if (ldb.sr) func.call(this,ldb);
        else for (var t in ldb) func_t.call(this,ldb[t]);
      },
      prep_board_init: function(ldb){ // for initial only
        this.prep_board_init_1(ldb.sr, this.data_p);
        this.prep_board_init_1(ldb.srt,this.data_t);
      },
      prep_board_init_1: function(ldbs,dst){ // for initial only
        var data = ldbs[this.time_sel][1];
        var offset = (this.time_base - ldbs[this.time_sel][0])/this.time_unit; // offset>=0 is always true.
        var i = offset;
        while (i<data.length) {
          if (data[i]) dst[i-offset] += data[i];
          i++;
        }
      },
    };
    var StatsFromNo = function(parent){
      this.data_p = [];
      this.min_idx = 0;
      for (var i in StatsFromNo_prototype) this[i] = StatsFromNo_prototype[i];
      this.__proto__ = parent;
    };
    var StatsFromNo_prototype = {
      prep_board_subscribe: function(ldb){
        if (!ldb.s) setup_board_no(ldb,'s');
        ldb.s[this.time_sel][2].push(this);
      },
      prep_board_unsubscribe: function(ldb){
        if (ldb.s && this.time_sel) {
          var idx = ldb.s[this.time_sel][2].indexOf(this);
          if (idx!=-1) ldb.s[this.time_sel][2].splice(idx,1);
        }
      },
      prep: function(tree, func, subscribe, aggregate){
        if (subscribe) this.__proto__.prep.call(this.fromNo,'live',this.fromNo.prep_board_subscribe, null);
        if (aggregate) for (var i=this.min_idx;i<this.data_p.length;i++) this.data_p[i] = 0;
        this.new_vals = {};
        this.__proto__.prep.call(this, 'live', func, null);
        for (var i in this.new_vals) this[i] = this.new_vals[i];
        delete this.new_vals;
      },
      prep_board: function(ldb,func){
        func.call(this,ldb);
      },
      prep_board_init: function(ldb,func){
        var data = ldb.s[this.time_sel][1];
        var offset = (this.time_base - ldb.s[this.time_sel][0])/this.time_unit; // offset>=0 is always true.
        var i = this.min_idx + offset;
        if (i<2) i = 2; // skip [0], because [0] contains old data.
        var data_last;
        var j=i-1;
        while (j>0 && !data[j]) j--;
        if (j>0) data_last = data[j];
        else {
          while (i<data.length && !data[i]) i++;
          data_last = data[i++];
        }
        if (pref.stats.patch_tm) {
          var idx_last = ((j>0)? j : i-1)-offset;
          var idx_last2 = idx_last; // for not writing any data
          while (i<data.length) {
            if (data[i]) {
              this.data_p[idx_last] += data[i] - data_last;
              data_last = data[i];
              idx_last2 = idx_last;
              idx_last = i-offset;
            }
            i++;
          }
          if (this.new_vals.min_idx===undefined || this.new_vals.min_idx > idx_last2) this.new_vals.min_idx = idx_last2;
        } else {
          while (i<data.length) {
            if (data[i]) {
              this.data_p[i-offset] += data[i] - data_last;
              data_last = data[i];
            }
            i++;
          }
          if (this.new_vals.min_idx===undefined || this.new_vals.min_idx > i - offset) this.new_vals.min_idx = i - offset;
        }
      },
      update_posts_no: function(time_base,idx_in){
        var idx = (time_base-this.time_base)/this.time_unit + idx_in;
        if (idx>=0 && idx<this.min_idx) this.min_idx = idx;
      },
      prep_board_check_valid: function(ldb,func){
        this.valid &= ldb.s[time_unit_arr.length]>=0;
      },
//      __proto__: Stats.prototype
    };
////    function setup_board_no(ldb, data, time_sel){
////      for (var t in ldb) if (ldb[t].s) update_posts_no_1(ldb[t].s, ldb[t].sn, 0, ldb, time_sel);
////      var j=0;
////      while (j<data.length && !data[j]) j++;
////      if (j!==0 && j!=data.length) for (var i=0;i<j;i++) data[i] = data[j];
////if (pref.test_mode['43']) {
////      while (j<data.length) {if (!data[j]) data[j] = data[j-1];j++;} // cause spike
////} else {  
////      while (j<data.length) { // average, not debugged.
////        var j_old = j++;
////        while (j<data.length && !data[j]) j++;
////        if (j_old+1===j) continue;
////        if (j<data.length) {
//////console.log('setup_board_no: averaging: '+j_old+', '+j);
////          var step = (data[j]-data[j_old])/(j-j_old);
////          for (var i=1;i<j-j_old;i++) data[j_old+i] = data[j_old]+Math.floor(step*i);
////        }
////      }
////}
////    }
    function update_board_rm(ldb){
      if (!ldb.sr) {
        setup_board_no(ldb,'sr');
        setup_board_no(ldb,'srt');
//        for (var i=0;i<time_unit_arr.length;i++) for (var j=0;j<pref.stats.len_capture+2;j++) ldb.sr[i][1][j] = 0;
      }
      for (var t in ldb) {
        if (ldb[t].s) for (var i=0;i<time_unit_arr.length;i++) {
          update_posts_no_1(ldb.sr[i],  null, 0, ldb[t].s.length, ldb[t].s, time_unit_arr[i], update_posts_rm_1_func, 0);
          update_posts_no_1(ldb.srt[i], null, 0, 1,               ldb[t].s, time_unit_arr[i], update_posts_rm_1_func, 0);
        }
        delete ldb[t];
      }
    }
    function setup_board_no(ldb,key){
      Object.defineProperty(ldb, key, {value:[], configurable:true, writable:false});
      for (var i=0;i<time_unit_arr.length;i++) {
//        ldb[key][i] = [null,[],[],0]; // [time_base, data, subscribers, tolerance]
        ldb[key][i] = [null,(i<3)?[]:{length:pref.stats.len_capture+2, __proto__:Array.prototype},[],0];
//        ldb[key][i] = [null,{length:pref.stats.len_capture+2, __proto__:Array.prototype},[],0]; // debug // length isn't updated automatically after 'splice' shorten it.
        update_posts_no_time(ldb[key][i], time_unit_arr[i]);
      }
      if (key==='s') ldb[key][time_unit_arr.length] = -Object.keys(ldb).length; // count
    }
    function update_posts_no_time(ldbst, time_unit, idx_over){
      var time_now = Math.floor((Date.now()/60000 + ldbst[3])/time_unit)*time_unit;
      var time_base = time_now - time_unit * pref.stats.len_capture;
      if (!ldbst[0]) {ldbst[0] = time_base;return;} // initialize
      else if (time_base!==ldbst[0] || idx_over) {
        var shift = (time_base-ldbst[0])/time_unit;
        var shift_add = 0;
        if (idx_over>shift && pref.stats.tolerant) {
          if (ldbst[3] + (idx_over-shift)*time_unit<pref.stats.tolerance) {
            ldbst[3] = idx_over*time_unit;
            shift_add = idx_over - shift;
          } else {
            ldbst[3] = pref.stats.tolerance;
            shift_add = Math.floor((pref.stats.tolerance-ldbst[3])/time_unit);
          }
        }
        ldbst[1].splice(0, shift + shift_add);
        if (!Array.isArray(ldbst[1])) ldbst[1].length = pref.stats.len_capture+2;
//        var last_data = ldbst[1][ldbst[1].length-1];
//        for (var i=0;i<shift+shift_add;i++) ldbst[1][ldbst[1].length] = last_data;
        ldbst[0] = time_base + shift_add*time_unit;
        return shift + shift_add;
      }
    }
    function update_posts_rm_1_func(data,idx){
      data[idx] = (data[idx] || 0) +1;
    }
    function update_posts_no_1_func(data,idx,th,j){
      var post_no = th.posts[j].no || th.no;
      if (data[idx]===undefined || data[idx]<post_no) data[idx] = post_no;
    }
    function update_posts_no_1_func_min(data,idx,th,j){ // for 4chan's bug, corrupted timeline when a thread is moved from other board.
      var post_no = th.posts[j].no || th.no;
      if (data[idx]===undefined || data[idx]>post_no) data[idx] = post_no; // THIS REQUIRES IN-ORDER OR FULL SCAN in update_posts_no_1 and PICKUP in ??? must be shifted by 1.
    }
    function update_posts_no(ldb, th, start, lths){
      if (!ldb.s) setup_board_no(ldb,'s');
      for (var i=0;i<time_unit_arr.length;i++) update_posts_no_1(ldb.s[i], th, start, lths.length, lths, time_unit_arr[i],
        (pref.stats.patch_tm)? update_posts_no_1_func_min : update_posts_no_1_func, th.posts.length);
      ldb.s[time_unit_arr.length]++;
    }
    function update_posts_no_1(ldbst, th, start, end, lths, time_unit, func, j){
      var time_base = ldbst[0];
      var data = ldbst[1];
      var idx_old;
      for (var p=end-1;p>=start;p--) {
        var idx = Math.floor((lths[p]-time_base)/time_unit);
        if (--j>=0 && idx===idx_old && !pref.stats.patch_tm) continue; // full scan at patch_tm
        var idx_over = idx - pref.stats.len_capture -1;
        if (idx_over>0) {
          idx -= update_posts_no_time(ldbst, time_unit, idx_over);
          if (idx>pref.stats.len_capture+1) idx = pref.stats.len_capture+1;
          time_base = ldbst[0];
        } else if (idx<0) idx = 0;
        func(data,idx,th,j);
        if (idx===0 && !pref.stats.patch_tm) break;
        idx_old = idx;
      }
      for (var k=0;k<ldbst[2].length;k++) ldbst[2][k].update_posts_no(time_base,idx);
    }
        
    var auto_acquisition_keys = {};
    function get_ac_key(sel){return pref.catalog_board_list_obj[sel][0].key;} // key is a user string.
    function auto_acquisition_beforeunload(){
      if (pref.stats.auto_acquisition && cataLog.embed_mode!=='thread')
        for (var i=pref.catalog_board_list_obj.length-1;i>=0;i--)
          if (auto_acquisition_keys['@'+get_ac_key(i)]!==undefined) {
            var tgts = pClg.refresh_1('chart', false, false, false, false, i, false, true);
            if (typeof(tgts)!=='function') new Stats(null, tgts, get_ac_key(i), false).destroy();
          }
    }
    window.addEventListener('beforeunload', auto_acquisition_beforeunload, false);
    function auto_acquisition_init(idx){
      if (pref.stats.auto_acquisition && pClg && pClg.refresh_1 && cataLog.embed_mode!=='thread') {
        if (idx===undefined) idx = pref.catalog_board_list_obj.length-1;
        while (idx>=0 && (!pref.catalog_board_list_obj[idx][0].cmds || pref.catalog_board_list_obj[idx][0].cmds.indexOf('stats')===-1)) idx--;
        if (idx>=0) {
          if (pref.stats.auto_acquisition) stats.register_auto_acquisition(idx);
          var result = pClg.refresh_1('chart', false, false, false, 'stats_ac', idx, false, true, function(){auto_acquisition_init(idx-1);});
          if (typeof(result)==='function') result(function(){auto_acquisition_init(idx);});
        }
      }
    }
//    if (pref.stats.auto_acquisition_scan && site.whereami!=='archive') setTimeout(auto_acquisition_init, pref.stats.auto_acquisition_scan_delay*1000);
    return {
      aggregate: function(th, posts, passive){
        var ldb = liveTag.mems[th.domain][th.board];
        var lth = ldb[th.no];
        if (!lth.s) {
          lth.s = []; // stat
          if (!passive) posts = (lth.pd && pref.test_mode['128'])? site2[lth.domain].update_posts_merge_prep(th.posts, lth.pd, -1, true) : th.posts;
          else if (posts.next) posts.step = (posts.next - posts.start)/posts.length;
        }
        var tu = th.parse_funcs.time_unit;
        var start = lth.s.length;
        var p = start;
        var i = 0; // can count deleted posts
        if (!passive)       while (i<posts.length) lth.s[p++] = Math.floor(posts[i++].time * tu / 60000);
        else {
          var period = (posts.next||posts.now) - th.time;
          var base = pref.test_mode.num || 3600000;
          if (p<=1 && period>base) { // assumes geometric sequence // 'p<=1' is patch for RSS
            var multiple = Math.pow(2,Math.log2((period+base)/base)/posts.length);
            var tn = p? base*multiple : base;
            while (p<posts.length) {
              lth.s[p++] = Math.floor((posts.start + tn-base) / 60000);
              tn *= multiple;
            }
          } else while (i<posts.length) lth.s[p++] = Math.floor((posts.start + posts.step*i++) / 60000);
        }
//        else          while (i<posts.length) lth.s[p++] = Math.floor(posts[i++]           / 60000);
//        var i = th.posts.length - (th.nof_posts - lth.s.length); // working, but can't count deleted posts
//        if (i<0) return; // i=0; // {p=-i; i=0;} // patch for short thread (last 50 replies) at embed_mode==='thread'.
//        while (i<th.posts.length) lth.s[p++] = Math.floor(th.posts[i++].time * tu / 60000);
        if (lth.ss) { // subscribers
          if (Array.isArray(lth.ss)) for (var i=0;i<lth.ss.length;i++) lth.ss[i].update_thread(lth.s,start, true);
          else lth.ss.update_thread(lth.s,start, true);
        } else if (start===0) thread_created(lth);
        if (pref3.stats.estimate_posts && !passive) update_posts_no(ldb, th, start, lth.s, th.posts.length);
      },
      aggregate_passive: function(th, nof_new_posts, time_now){
        var time_prev = liveTag.mems[th.domain][th.board].st || th.time_tu;
        var time_step = (time_now - time_prev)/nof_new_posts;
        var posts = {start:time_prev, step:time_step, length:nof_new_posts, next:th.time_nextThread, now:time_now}; // putting this into function call causes 'Not optimized:' on chrome50
        this.aggregate(th, posts, true);
//        var posts = [];
//        for (var i=0;i<nof_new_posts;i++) posts[i] = time_prev + i*time_step;
//        this.aggregate(th, posts, true);
      },
      thread_removed: thread_removed,
      Stats: Stats,
      register_auto_acquisition: function(sel){
        auto_acquisition_keys['@'+get_ac_key(sel)] = null;
        cnst.auto_shrink_board_selector.color();
      },
      query_auto_acquisition: function(sel){
        return auto_acquisition_keys['@'+get_ac_key(sel)]!==undefined;
      },
//      redraw_all: function(){
//        for (var i=0;i<instances.length;i++) instances[i].init();
//      }
      auto_acquisition_init: auto_acquisition_init,
    };
  })() : null;
               

  function make_chart_obj(pn1){
//    var pref_graph = {key: null, pipe: null}; // working code.
//    pn1.addEventListener('click', show_hide, false);
//    var data = chart_data_init();
//    pref_graph.key = pref.script_prefix + '.graph.' + site.board;
//    if (localStorage && pref.load_data && localStorage.getItem(pref_graph.key)!==null) data = brwsr.JSON_parse(localStorage.getItem(pref_graph.key));
//
//    var pn2_func = null;
//    function show_hide(){  // Toggle Show/Hide
//      if (pn2_func==null) pn2_func = prep_pn2();
//      else pn2_func = pn2_func.destroy();
//    }
//    var size_loc = ['400px', '400px', '5px', '50px', '120px', '390px'];

    var time_unit_sel_html =
      '<select name="chart.inst.time_sel">'+
        '<option>1 min</option>'+
        '<option>10 mins</option>'+
        '<option>1 hour</option>'+
        '<option>1 day</option>'+
        '<option>1 week</option>'+
      '<select>';
    var chart_options_str = [
        'Number of points: <ITB3"chart.inst.len"><br>',
        'Scale of new thread: <ITB3"chart.inst.scale_thread"><br>',
//        '<input type="checkbox" name="chart.inst.separate">Separate estimated, observed and alive<br>',
        'Show: Posts/Threads:<br>',
        '&emsp;&emsp;<IC"chart.inst.show.np"><IC"chart.inst.show.nt" disabled style="visibility:hidden">Estimated, <IC"chart.inst.clip_np">Clip:<ITB4"chart.inst.clip_np_val"><br>',
        '&emsp;&emsp;<IC"chart.inst.show.p"><IC"chart.inst.show.t">Observed<br>',
        '&emsp;&emsp;<IC"chart.inst.show.ep"><IC"chart.inst.show.et">Alive<br>',
        '<IC"chart.inst.show_legend">Legend<br>',
        '<IC"chart.inst.options.animation">Animation (Doesn\'t work on FF)<br>',
        '<IC"chart.inst.options.pointDot">Point dot: radius <ITB1"chart.inst.options.pointDotRadius"><br>'
      ];
    if (!pn1) return {
      time_unit_sel_html: time_unit_sel_html,
      chart_options_str: chart_options_str,
    }

//    pn1.addEventListener('click', make_new_chart, false);
    var charts = [];
    function make_new_chart(){
      if (!window.Chart) {
        var scr = document.createElement('script');
        scr.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.1-beta.4/Chart.min.js';
        site.script_body.appendChild(scr); // default behavior is 'async', see https://ja.javascript.info/script-async-defer
        scr.onload = make_new_chart;
      } else new PostChart();
    }
    function window_focus(){
      cnst.foreach(charts, function(inst){inst.chart_posts.options.animation = inst.options.animation;});
    }
    function window_blur(){
      if (pref.chart.off_anime_blur) cnst.foreach(charts, function(inst){inst.chart_posts.options.animation = false;});
    }
    function destroy_all(){
      cnst.foreach(charts, function(inst){inst.destroy();});
    }
    window.addEventListener('beforeunload', destroy_all, false);

    var chartCount = 0;
    var PostChart = function(pn_popup, tgts, skelton){
//      this.data_graph;
//      this.chart_posts;
      if (!skelton) comf.deep_copy(pref.chart.inst, this);
      if (!pn_popup) {
        cnst.subscribe(charts,this);
        if (chartCount.length===0) {
          comf.dom_addEventListener(common_obj.events_beforeunload, window, 'focus', window_focus);
          comf.dom_addEventListener(common_obj.events_beforeunload, window, 'blur', window_blur);
        }
        this.name = 'graph_' + chartCount++;

        this.pn2_obj = cnst.init3({
          this_obj:this,
          __proto__:this.pn2_template});
        var pn2 = this.pn2_obj.pn;
        var pn2_2 = cnst.add_to_tb(pn2, time_unit_sel_html + (!skelton? '<select name="chart.inst.board_sel"><select>' : '')+ cnst.icons.button_settings());
        pn2.childNodes[1].innerHTML = pref_func.format_html_str(
          '<div style="display:none">'+
            '<div style="float:right">'+ chart_options_str.join('') + '</div>'+
            '<div style="clear:both"></div>'+
          '</div>'+
          '<div style="position:relative"></div>');
        pref_func.apply_prep(pn2,false, undefined, undefined, undefined, skelton? {chart:{inst:this}} : undefined);
        var sels = pn2.getElementsByTagName('select');
        cnst.auto_shrink_selector(sels['chart.inst.time_sel']);
        this.legend = cnst.init3({func_str:'border:1px solid lightblue:cursor:move:position:absolute:left:80px:top:40px'}); // :dragStopProp'});
        this.legend.pn.style.zIndex = null;
        this.pn2 = pn2;
        this.set_legend_str();
        var onchange_entry_bound = this.onchange_entry.bind(this);
        pn2.childNodes[0].onchange = onchange_entry_bound;
        pn2.childNodes[1].childNodes[0].onchange = onchange_entry_bound;
        if (skelton) {
          cnst.bottom_top(pn2);
////          pn2.style.top = parseInt(skelton.pn_canvas_root.style.top,10) - pn2.offsetHeight + 'px';
////          pn2.style.left = skelton.pn_canvas_root.style.left;
//          this.show_legend = false;
//          var pns = pn2.querySelectorAll('input[name],textarea[name],select[name]');
//          for (var i=0;i<pns.length;i++) {
//            var tgt_hier = pref_func.get_tgt(pns[i].getAttribute('name').replace(/chart.inst./,''),this);
//            pref_func.apply_prep_load(pns[i],tgt_hier[0], tgt_hier[1]);
//          }
////          pn2.childNodes[1].replaceChild(skelton.pn_canvas_root, pn2.childNodes[1].childNodes[1]);
          return pn2;
        }
        this.pn_board_sel = sels['chart.inst.board_sel'];
        this.board_sel = cnst.auto_shrink_board_selector.setup(this.pn_board_sel, pref.catalog_board_list_sel, function(idx){this.board_sel=idx;}.bind(this));
        this.pn_canvas_root = pn2.childNodes[1].childNodes[1];
      } else {
        this.pn_canvas_root = pn_popup;
        this.show_legend = false;
        this.show = {np:false, p:true, ep:false, nt:false, t:true, et:false};
      }
      this.pn_canvas_root.style.width = pref.chart.window_width + 'px';
      this.pn_canvas_root.style.height = pref.chart.window_height + 'px';
      this.pn_canvas_root.style.resize = 'both';
      this.pn_canvas_root.style.overflow = 'hidden';
      this.pn_canvas = document.createElement('canvas');
      this.pn_canvas_root.appendChild(this.pn_canvas);
      this.chart_size_changed(true); // patch
  //      this.chart_create_draw(); called from stats
      this.stats = (!pn_popup)? new stats.Stats(this, this.get_tgts(), pref.catalog_board_list_obj[this.board_sel][0].key) : // call 'chart_create_draw' in this.
                                new stats.Stats(this, tgts, 'popup');
  //      this.track_legend_pos();
  ////      pn2.addEventListener('resize',  //) // can't get.
      this.canvas_active = true;
      this.pn_canvas_root.onmousemove = this.deactivate_canvas_if_mouse_on_corner.bind(this);
      this.pn_canvas_root.onclick = this.chart_size_changed.bind(this);
  //      pn2.ondragover = this.dragover_pn2.bind(this);
  //      pn2.ondrop = this.drop_pn2.bind(this);
  //      this.legend.pn.ondragend = function(e){e.stopPropagation();};
      if (this.show_legend) this.append_legend();
      if (!pn_popup) cnst.bottom_top(pn2);
    };
    PostChart.prototype = {
      get_tgts: function(scan_name){
        var tgts = (!pClg.refresh_1)? [site.nickname+site.board]
          : pClg.refresh_1('chart', false, false, false, scan_name, this.board_sel, false, true);
        if (pref.test_mode['203']) return tgts;
        for (var i=tgts.length-1;i>=0;i--) {
          var dbt = cnst.name2domainboardthread(tgts[i],true);
          var vm = site2[dbt[0]].nativeVirtualBoard && liveTag.mems[dbt[0]] && liveTag.mems[dbt[0]][dbt[1]] && liveTag.mems[dbt[0]][dbt[1]].vm;
          if (vm) Array.prototype.splice.apply(tgts,[i,1].concat(vm));
        }
        var tgts_unique = {};
        for (var i=0;i<tgts.length;i++) tgts_unique[tgts[i]] = null;
        return Object.keys(tgts_unique);
      },
      board_sel_changed: function(e){
        e.target.blur();
        var tgts = this.get_tgts((pref.chart.instant_scan)? this.name : null); // also scan
        if (typeof(tgts)==='function') tgts(this.board_sel_changed.bind(this));
        else this.stats.tgts_changed(tgts, pref.catalog_board_list_obj[this.board_sel][0].key);
      },
      len_changed: function(){
        if (this.len>this.stats.len) this.stats.len_changed.call(this.stats, this.len); // call redraw
        else if (this.stats.len!=pref.stats.len_capture) this.stats.len_changed.call(this.stats, pref.stats.len_capture); // call redraw
        else this.replace_data(this.data_src);
      },
      deactivate_canvas_if_mouse_on_corner: function(e){
        var ecTs = e.currentTarget.style;
        if ((parseInt(ecTs.width,10) - e.offsetX<=10 && parseInt(ecTs.height,10) - e.offsetY<=10) ^ !this.canvas_active) {
          this.pn_canvas.style.pointerEvents = this.canvas_active? 'none' : 'auto';
          this.canvas_active = !this.canvas_active;
        }
      },
      chart_size_changed: function(init){ // patch
//        if (str==='top' || str==='bottom') this.pn_canvas_root.style.overflow = '';
//        else if (typeof(str)==='string') this.pn_canvas_root.style.overflow = 'hidden';
//        this.pn_canvas_root.style.resize = 'both';
//        if (this.pn_canvas_root.style.overflow==='hidden') {
//        if (this.pn2_obj.maximize_state_str==='float') {
        this.pn_canvas.style.width  = this.pn_canvas_root.style.width;
        this.pn_canvas.style.height = this.pn_canvas_root.style.height; // (this.pn_canvas_root.style.height.replace(/px/,'')-10)+'px';
        this.pn_canvas.width  = this.pn_canvas_root.clientWidth;
        this.pn_canvas.height = this.pn_canvas_root.clientHeight; // - 10;
//        }
        if (init!==true) this.chart_redraw();
      },
      chart_redraw: function(){ // old 'chart_create_draw' in this.
        if (this.chart_posts) this.chart_posts.destroy();
        
        this.data_graph = chart_data_init(); // deep copy always.
        var dd = this.data_graph.datasets;
        var fN = this.data_src.fromNo;
        var dr = this.data_src.rm;
        var ds = this.data_src;
//        this.idxs  = (dr)? ((fN)? {p:1, t:3, ep:2, et:4, np:0} : {p:0, t:2, ep:1, et:3}) :
//                           ((fN)? {t:1, np:0} : {t:1, p:0});
        var layer = 0;
        this.idxs = {};
        var idxs = this.idxs;
        idxs.np = (fN && this.show.np)? layer++ : -1;
        idxs.p  = (      this.show.p )? layer++ : -1;
        idxs.ep = (dr && this.show.ep)? layer++ : -1;
        idxs.t  = (      this.show.t )? layer++ : -1;
        idxs.et = (dr && this.show.et)? layer++ : -1;

        if (idxs.np!=-1) dd[idxs.np] = chart_data_init_1('post_fromNo','151,187,205',0.2);
        if (idxs.p !=-1) dd[idxs.p ] = chart_data_init_1('Posts','98,152,179', 0.2);
        if (idxs.ep!=-1) dd[idxs.ep] = chart_data_init_1('exist_p','66,119,136',0.15); // '151,187,205',0.2
        if (idxs.t !=-1) dd[idxs.t ] = chart_data_init_1('Threads','204,0,0', 0.2);
        if (idxs.et!=-1) dd[idxs.et] = chart_data_init_1('exist_t','140,0,0',0.2); // '204,128,128',0.2

if (pref.test_mode['39'] && dd[pref.test_mode.num]) dd[pref.test_mode.num] = chart_data_init_1(dd[pref.test_mode.num].label,pref.test_mode.test_str, pref.test_mode.num_f);
        var start = (this.len<this.stats.len)? this.stats.len - this.len : 0;
        this.data_graph.labels = ds.data_l.slice(start); // I don't know why, but shallow copy is required, I can't use source array directly.
        if (idxs.p !=-1) dd[idxs.p].data = this.stats.merge_1(ds.data_p.slice(start), (ds.data_p_old)? ds.data_p_old.slice(start) : null);
        if (idxs.t !=-1) {
          dd[idxs.t].data = this.stats.merge_1(ds.data_t.slice(start), (ds.data_t_old)? ds.data_t_old.slice(start) : null);
          if (this.scale_thread!=1) this.scale_setup(dd[idxs.t].data);
        }
        if (dr) {
//          dd[5] = chart_data_init_1('old_t','204,0,0');
//          dd[0].data = ds.data_p.slice(start);
//          dd[1].data = ds.data_t.slice(start);
//          dd[2].data = dr.data_p.slice(start); // raw
//          dd[3].data = dr.data_t.slice(start);
          if (idxs.ep!=-1) dd[idxs.ep].data = this.arr_dec_slice(ds.data_p, dr.data_p, start);
          if (idxs.et!=-1) {
            dd[idxs.et].data = this.arr_dec_slice(ds.data_t, dr.data_t, start);
            if (this.scale_thread!=1) this.scale_setup(dd[idxs.et].data);
          }
//          dd[2].data = ds.data_p.map(function(v,i){return v - this[i];}.bind(dr.data_p)); // TOO SLOW...
//          dd[3].data = ds.data_t.map(function(v,i){return v - this[i];}.bind(dr.data_t));
if (pref.test_mode['40']) {
//          dd[4] = chart_data_init_1('old_p','98,152,179');
//          dd[4].fillColor = 'rgba(0,255,0,0.2)';
          dd[layer] = chart_data_init_1('old_p','0,255,0',0.1);
          dd[layer++].data = ds.data_p_old.slice(start);
          dd[layer] = chart_data_init_1('p_raw','98,152,179',0.2);
          dd[layer++].data = ds.data_p.slice(start);
//          dd[5].data = ds.data_t_old.slice(start);
}
        }
        if (fN && idxs.np!=-1) {
          dd[idxs.np].data = (fN.data_p_old)? fN.data_p_old.concat(fN.data_p.slice(fN.data_p_old.length)).slice(start) : fN.data_p.slice(start);
          if (fN.data_p_old) {
            var idx_last = fN.data_p_old.length-1;
            if (fN.data_p[idx_last]>fN.data_p_old[idx_last]) if (idx_last-start>=0) dd[idxs.np].data[idx_last-start] = fN.data_p[idx_last]; // overwrite last data, because last data may NOT complete its period.
          }
          if (this.clip_np) {
            var data_np = dd[idxs.np].data;
            for (var i=0;i<data_np.length;i++) data_np[i] = this.clip_np_func(data_np[i]);
          }
//          if (pref.stats.patch_tm) dd[idxs.np].data = dd[idxs.np].data.slice(1).concat(0); // shift only at initial
//          dd[idxs.np].data = fN.data_p.slice(start);
//          var old_sliced = fN.data_p_old.slice(start);
//          for (var i=0;i<old_sliced.length;i++)
//            if (old_sliced[i])
//              dd[idxs.np].data[i] = old_sliced[i];
if (pref.test_mode['41']) {
          dd[layer] = chart_data_init_1('post_fromNo_old','0,255,0',0.2);
          dd[layer++].data = fN.data_p_old.slice(start);
          dd[layer] = chart_data_init_1('post_fromNo','0,128,0',0);
          dd[layer++].data = fN.data_p.slice(start);
}
        }
//        Chart.defaults.global.animation = pref.stats.animation;
        this.scale_thread_old = this.scale_thread;
        var ctx = this.pn_canvas.getContext('2d');
        try {
          this.chart_posts = new Chart(ctx).Line(this.data_graph, this.options);
        } catch (e){} // error is thrown if no data is given.
      },
      clip_np_func: function(data){
        return (data<0)? 0 :
               (data>this.clip_np_val)? this.clip_np_val : data;
      },
      arr_dec_slice: function(src, dec, start){
        var arr = [];
        var p = 0;
        for (var i=start;i<src.length;i++) arr[p++] = src[i] - dec[i];
        return arr;
      },
      scale_setup: function(dst,old){
        if (old!==undefined) for (var i=0;i<dst.length;i++) dst[i].value = dst[i].value/old*this.scale_thread;
        else for (var i=0;i<dst.length;i++) dst[i] *= this.scale_thread;
        return dst;
      },
      scale_changed: function(){
        this.set_legend_str();
        if (this.scale_thread_old!==0) {
          this.scale_setup(this.chart_posts.datasets[this.idxs.t].points, this.scale_thread_old);
          if (this.idxs.et!==undefined && this.data_graph.datasets[this.idxs.et]) this.scale_setup(this.chart_posts.datasets[this.idxs.et].points, this.scale_thread_old);
          this.chart_posts.update();
        } else this.chart_redraw();
        this.scale_thread_old = this.scale_thread;
      },
      set_legend_str: function(){
//        var str = '<ul class="line-legend"><li style="color:rgba(98,152,179,1)"><span style="color: black;">Posts</span></li><li style="color:rgba(204,0,0,1)"><span style="color: black;">Threads ' + ((this.scale_thread==1)? '' : 'x'+ this.scale_thread) + '</span></li></ul>'
        var str = '<span style="color:rgba(98,152,179,1)">\u25cf</span><span style="color: black;"> Posts</span><br>'+
                  '<span style="color:rgba(204,0,0,1)">\u25cf</span><span style="color: black;"> Threads ' + ((this.scale_thread==1)? '' : 'x'+ this.scale_thread+' ') + '</span>'
        this.legend.pn.innerHTML = str;
      },
//      dragover_pn2: function(e) {e.preventDefault();}, // working code.
//      drop_pn2: function (e) {
//        e.preventDefault(); // FF require this.
//        var str = e.dataTransfer.getData('text');
//        if (str!='') {
//          if (pref.import_format=='obj') data = JSON.parse(str);
//          else {
//            var str_s = str.split(/\r\n|\r|\n|\\n/);
//            data.labels = str_s[0].split(',');
//            for (var i=0;i<2;i++) {
//              var str_sc = str_s[i+1].split(',');
//              for (var j=0;j<str_sc.length;j++) data.datasets[i].data[j] = (str_sc[j]!='')? parseInt(str_sc[j],10) : 0;
//            }
//          }
//        }
//        chart_redraw();
//      },
//      toggle_settings: function(e){ // working code
//        cnst.show_hide(this.pn2_settings, null, this.legend.pn);
//        cnst.toggleButton(e.currentTarget);
//      },
      destroy : function (){
        if (!brwsr.ff) this.chart_posts.stop();
        this.chart_posts.destroy();
//        pn2.removeEventListener('click', chart_redraw, false);
//        pn2.removeEventListener('dragover', dragover_pn2, false);
//        pn2.removeEventListener('drop', drop_pn2, false);
//        pn2.removeEventListener('dragend', div_dragend_pn2, false);
//        pn6.removeEventListener('dragend', function(e){e.stopPropagation();}, false);
        //        size_loc = [pn2.style.width, pn2.style.height, pn2.style.left, pn2.style.bottom, pn6.style.left, pn6.style.bottom];
        this.stats.destroy();
        this.stats = null;
        if (this.pn2) {
          cnst.div_destroy(this.legend.pn, false, this.legend);
          cnst.div_destroy(this.pn2, true, this.pn2_obj);
          cnst.auto_shrink_board_selector.destroy(this.pn_board_sel);
          cnst.unsubscribe(charts, this);
        }
        return null;
      },
//      clear : function () {this.chart_redraw();}, // working code.
//      update_data : function(time_str, posts, threads){
//        while (data_graph.labels.length>=pref.max_graph) chart_posts.removeData();
//        chart_posts.addData([posts,threads*pref.scale_thread],time_str);
//        chart_posts.update();
//      },
//      update: function(){
//        chart_redraw();
//      },
      replace_data: function(src){
        if (!this.stats) this.stats = src; // patch
        this.data_src = src;
        this.chart_redraw();
      },
      update_data2 : function(src){
        var i = src.shift;
        while (i>0) {this.chart_posts.removeData();i--;}
        var data = [];
        for (var i=src.data_p.length-src.shift;i<src.data_p.length;i++) {
          if (src.fromNo && this.idxs.np!=-1) data[this.idxs.np] = (this.clip_np)? this.clip_np_func(src.fromNo.data_p[i]) : src.fromNo.data_p[i];
          if (this.idxs.p!=-1) data[this.idxs.p] = src.data_p[i];
          if (this.idxs.t!=-1) data[this.idxs.t] = src.data_t[i]*this.scale_thread;
          if (src.rm) {
            if (this.idxs.ep!=-1) data[this.idxs.ep] =  src.data_p[i] - src.rm.data_p[i];
            if (this.idxs.et!=-1) data[this.idxs.et] = (src.data_t[i] - src.rm.data_t[i])*this.scale_thread;
          }
          this.chart_posts.addData(data, src.data_l[i]);
        }
        var tgt_p = (this.idxs.p!=-1)? this.chart_posts.datasets[this.idxs.p] : null;
        var tgt_t = (this.idxs.t!=-1)? this.chart_posts.datasets[this.idxs.t] : null;
        var tgt_ep = (this.idxs.ep!=-1 && src.rm)? this.chart_posts.datasets[this.idxs.ep] : null;
        var tgt_et = (this.idxs.et!=-1 && src.rm)? this.chart_posts.datasets[this.idxs.et] : null;
        var tgt_np = (this.idxs.np!=-1 && src.fromNo)? this.chart_posts.datasets[this.idxs.np] : null;
        var p = src.min_idx - (this.stats.len - this.len);
        var i = src.min_idx;
        if (p<0) {
          i += -p;
          p=0;
        }
        if (pref.stats.patch_tm && tgt_np && p>0 && i>0) tgt_np.points[p-1].value = (this.clip_np)? this.clip_np_func(src.fromNo.data_p[i-1]) : src.fromNo.data_p[i-1];
        while (i<src.data_p.length-src.shift) {
          if (tgt_p) tgt_p.points[p].value =  (src.data_p_old && src.data_p_old[i]>src.data_p[i])? src.data_p_old[i] : src.data_p[i];
          if (tgt_t) tgt_t.points[p].value = ((src.data_t_old && src.data_t_old[i]>src.data_t[i])? src.data_t_old[i] : src.data_t[i]) *this.scale_thread;
//            chart_posts.label.points[p].value = src.data_l[i];
          if (tgt_ep) tgt_ep.points[p].value =  src.data_p[i] - src.rm.data_p[i];
          if (tgt_et) tgt_et.points[p].value = (src.data_t[i] - src.rm.data_t[i]) *this.scale_thread;
          if (tgt_np) tgt_np.points[p].value = (this.clip_np)? this.clip_np_func(src.fromNo.data_p[i]) : src.fromNo.data_p[i];
          p++;
          i++;
        }
        src.shift = 0;
        this.chart_posts.update();
      },
      append_legend: function(){this.pn_canvas_root.appendChild(this.legend.pn);},
      remove_legend: function(){this.pn_canvas_root.removeChild(this.legend.pn);},
    }
    PostChart.prototype.pn2_template = {
      get func_str(){return 'left:' +(((chartCount-1)%5)*20)+ 'px:tile2:get:bottom:' +(((chartCount-1)%5)*20)+ ':Show:tb'}, // ':resize:both:float:left:Show:tb:width:' +
//        pref.chart.window_width + 'px:height:' + pref.chart.window_height + 'px:resize:both:overflow:hidden';},
//      rolldown: PostChart.prototype.append_legend,
//      rollup: PostChart.prototype.remove_legend,
      maximize: PostChart.prototype.chart_size_changed,
//      maximize_embed_style_pn: {left:'auto', top:'auto', position:'relative', resize:'none'}, //  {left:'auto', top:'auto', position:'static', resize:'none'}
      maximize_embed_style_pnch1: {}, //  {width:'auto', height:'auto', resize:'none'}
      maximize_float_style_pnch1: {}, //  {width:state[3], height:state[4], resize:'both'}
      exit: PostChart.prototype.destroy,
      get maximize_tgt(){return this.this_obj.pn_canvas_root;}, 
    };
    PostChart.prototype.onchange_entry = function(e){
      var tgt_hier = pref_func.get_tgt(e.target.name.replace(/chart.inst./,''),this);
      var prop = tgt_hier[1];
//      var val = (e.target.type==='checkbox')? e.target.checked : (e.target.type==='text')? parseInt(e.target.value,10) : e.target.selectedIndex;
//      target_hier[0][prop] = val;
      pref_func.apply_prep_set(tgt_hier[0], tgt_hier[1], e.target);
      if (this.onchange_funcs[prop]) this.onchange_funcs[prop].call(this,e);
    };
    PostChart.prototype.onchange_funcs = {
      pointDot: PostChart.prototype.chart_redraw,
      pointDotRadius: PostChart.prototype.chart_redraw,
      scale_thread: PostChart.prototype.scale_changed,
      animation: function(){this.chart_posts.options.animation = this.options.animation;},
      len: PostChart.prototype.len_changed,
      separate: function(){this.stats.init();},
      time_sel: function(e){e.target.blur();this.stats.time_unit_changed(this.time_sel);},
      board_sel: PostChart.prototype.board_sel_changed,
      show_legend: function(e){if (e.target.checked) this.append_legend(); else this.remove_legend();},
      clip_np: PostChart.prototype.chart_redraw,
      clip_np_val: function(){
        if (!this.clip_np) {
          this.clip_np = true;
          this.pn2.querySelector('input[name="chart.inst.clip_np"]').checked = true;
        }
        PostChart.prototype.chart_redraw.call(this);
      },
    };
    PostChart.prototype.onchange_funcs.np = PostChart.prototype.onchange_funcs.separate;
    PostChart.prototype.onchange_funcs.p  = PostChart.prototype.onchange_funcs.separate;
    PostChart.prototype.onchange_funcs.ep = PostChart.prototype.onchange_funcs.separate;
    PostChart.prototype.onchange_funcs.t  = PostChart.prototype.onchange_funcs.separate;
    PostChart.prototype.onchange_funcs.et = PostChart.prototype.onchange_funcs.separate;

    return {
      clear: function(){}, // dummys
      dump: function(format){},
      data_update: function(time_str, posts, threads){},
      PostChart: PostChart,
//      clear: function(){ // working code.
//        data = chart_data_init();
//        if (pn2_func!=null) pn2_func.clear();
//      },
//      dump: function(format){ // http://stackoverflow.com/questions/22055598/writing-a-json-object-to-a-text-file-in-javascript
//        var url;
//        if (format=='csv') {
//          var out_labels = data.labels;
//          var out_data0 = data.datasets[0].data;
//          var out_data1 = data.datasets[1].data;
//          url = 'data:text/json;charset=utf8,' + encodeURIComponent(out_labels) + '\\n' + encodeURIComponent(out_data0) + '\\n' + encodeURIComponent(out_data1);
//        } else if (format=='obj') url = 'data:text/json;charset=utf8,' + JSON.stringify(data);
//        window.open(url, '_blank');
//        window.focus();
//      },
//      data_update: function(time_str, posts, threads){
//        if (pn2_func!=null) pn2_func.data_update(time_str, posts, threads);
//        if (data.labels.length>=pref.max_capture) {
//          data.datasets[0].data.slice(-pref.max_capture+1);
//          data.datasets[1].data.slice(-pref.max_capture+1);
//          data.labels.slice(-pref.max_capture+1); // labels are copied shallowly, but now made deep copy manually.
//        }
//        data.labels.push(time_str);
//        data.datasets[0].data.push(posts);
//        data.datasets[1].data.push(threads);
//        if (pref.aggregator=='true' && pref.write_to_ls && localStorage) {
//          localStorage.setItem(pipe_name,  JSON.stringify([time_str, posts, threads]));
//          localStorage.setItem(pref_graph.key, JSON.stringify(data));
//        }
      //      },
      time_unit_sel_html: time_unit_sel_html,
      chart_options_str: chart_options_str,
      make: make_new_chart,
    }
    function chart_data_init() {
      return {
        labels: ['dummy','dummy'], // at least 2 data required at first, or filling collapse. This is probably a BUG in Chart.js.
        datasets: []
      };
    }
    function chart_data_init_1(label, color, alpha) {
      return {
        label: label,
        fillColor: 'rgba(' + color + ',' + alpha + ')',
        strokeColor: 'rgba(' + color + ',1)',
        pointColor: 'rgba(' + color + ',1)',
        pointStrokeColor: '#fff',
        pointHighlightFill: '#fff',
        pointHighlightStroke: 'rgba(' + color + ',1)',
        data: [0,0]
      };
    }
  }

  function make_setting_obj(){ // pn8){
////  var setting = (function(){
////    var pn8 = div_init(8,'55px','30px','button','settings');
////    var pn8 = cnst.init('left:55px:bottom:30px:button:settings:Show');
//    pn8.addEventListener('click', show_hide, false);
    var pn7 = null;
    var pn7_1 = null;
    function show_hide(){  // Toggle Show/Hide
      pn7 = prep_pn7(pn7==null);
    }
    function prep_pn7(make){
      if (make) {
//        pn7 = div_init(7,'5px','50px','div','');
//        pn7 = cnst.init('left:5px:bottom:50px:Show:tb',cnst.void_func,cnst.void_func,show_hide,cnst.void_func);
//if (pref.test_mode['17']) pn7 = cnst.init('left:0px:tile:get:bottom:Show',cnst.void_func,cnst.void_func,show_hide,cnst.void_func); // leaks nodes.
if (pref.test_mode['17']) {pn7 = document.createElement('div');pn7.innerHTML='<div></div><div></div>';site.root_body.appendChild(pn7);} // leaks nodes.
else 
        pn7 = cnst.init('left:0px:tile:get:bottom:Show:tb',cnst.void_func,cnst.void_func,show_hide,cnst.void_func);
if (pref.test_mode['17']) pn7_1 = pn7;
else 
        pn7_1 = pn7.childNodes[1];
        pn7_1.innerHTML = '<div><div style="float:left">\
Statistics:<br>\
&emsp;Interval: <input type="text" name="interval_found" size="6" style="text-align: right;">min<br>\
&emsp;<input type="checkbox" name="check_page"> Show page no. of this thread<br>\
&emsp;&emsp;<input type="checkbox" name="show_page_fraction"> Show fraction of page No.<br>\
&emsp;<input type="checkbox" name="check_post"> Show num of new posts in this board<br>\
&emsp;<input type="checkbox" name="check_thread"> Show num of new threads in this board<br>\
&emsp;&emsp; Max_capture: <input type="text" name="max_capture" size="6" style="text-align: right;"> points<br>\
&emsp;&emsp;&emsp;Show recent : <input type="text" name="max_graph" size="6" style="text-align: right;"> points in graph<br>\
&emsp;&emsp;&emsp;Scale of #threads in graph: <input type="text" name="scale_thread" size="6" style="text-align: right;"><br>\
<!-- &emsp;&emsp;<input type="radio" name="autoconf" value="auto"> Automatic configuration<br>\
&emsp;&emsp;<input type="radio" name="autoconf" value="manual"> Manual configuration<br> -->\
&emsp;&emsp;&emsp;<input type="radio" name="aggregator" value="true"> Aggregate data<br>\
&emsp;&emsp;&emsp;&emsp;<input type="checkbox" name="write_to_ls"> Write to localStrage (server)<br>\
&emsp;&emsp;&emsp;<input type="radio" name="aggregator" value="false"> Listen from localStrage (client)<br>\
&emsp;&emsp;<input type="checkbox" name="load_data"> Load data from localStrage at the start<br>\
&emsp;&emsp;Import format (drop whole text on graph)<br>\
&emsp;&emsp;&emsp;<input type="radio" name="import_format" value="csv"> csv<br>\
&emsp;&emsp;&emsp;<input type="radio" name="import_format" value="obj"> obj<br>\
&emsp;&emsp;<input type="button" value="Dump(csv)"><input type="button" value="Dump(obj)">&emsp;<input type="button" value="Clear data"><br>\
<input type="checkbox" name="auto_start"> Start automatically (Delayed 10sec.)<br>\
<br>'+
//          'UIP tracker for 4chan:<br>'+
//          '&emsp;<input type="checkbox" name="uip_tracker.on"> Show num of unique IPs after post No.<br>'+
//          '&emsp;&emsp;<input type="checkbox" name="uip_tracker.posts"> Show num of posts(checking for deletion timing)<br>'+
//          '&emsp;&emsp;Interval: <input type="text" name="uip_tracker.interval" size="3" style="text-align: right;">sec'+
//          '&emsp;<input type="checkbox" name="uip_tracker.adaptive"> Adaptive<br>'+
//          '&emsp;<input type="checkbox" name="uip_tracker.auto_open"> Open next thread automatically<br>'+
//          '&emsp;&emsp;Conditions:<br>'+
//          '&emsp;&emsp;&emsp;After <input type="text" name="uip_tracker.auto_open_th" size="3" style="text-align: right;">th post<br>'+
//          '&emsp;&emsp;&emsp;OP contains <textarea style="height:1em" cols="20" name="uip_tracker.auto_open_kwd"></textarea><br>'+
//          '<br>'+
//'For dollchan: (workaround for bugs)<br>\
//&emsp;<input type="checkbox" name="workaround_for_dollchan"> Consistency checker for thubmnails of attached images<br>\
//&emsp;&emsp;runs at<br>\
//&emsp;&emsp;<input type="radio" name="wafd_tb" value="tb">every time when mouse leaves from thumbnail\'s area<br>\
//&emsp;&emsp;<input type="radio" name="wafd_tb" value="reply">only when mouse hovers on the reply button<br>\
//&emsp;<input type="checkbox" name="wafd_open_spoiler"> Open text spoilers<br>\
//&emsp;&emsp;(You must turn off the same function in dollchan beforehand)<br>\
'<!-- Post form:<br>\
&emsp;<input type="checkbox" name="hide_rules"> Hide rules<br>\
&emsp;<input type="checkbox" name="hide_Go"> Hide \'Go\'<br> -->\
<br>\
<br>'+
//Share loaded html with other tabs to update<br>\
//&emsp;<input type="checkbox" name="info_server"> Broadcast loaded html to other tabs (server)<br>\
//&emsp;<input type="checkbox" name="info_client"> Listen other tab\'s broadcasting (client)<br>\
//<br>\
//Command interface for overwriting site preference<br>\
//&emsp;<textarea style="height:1em" cols="40" name="cli.json_str"></textarea><br>\
//&emsp;<input type="button" value="JSON"><br>\
//&emsp;<textarea style="height:1em" cols="40" name="cli.eval_str"></textarea><br>\
//&emsp;<input type="button" value="EVAL"><br>\
//</div><div style="float:left">&emsp;&emsp;&emsp;</div><div style="float:left">'+
//Catalog:<br>\
//&emsp;Cross domain connection:<br>\
//&emsp;&emsp;<input type="radio" name="catalog_cross_domain_connection" value="direct"> Direct connection<br>\
//&emsp;&emsp;<input type="radio" name="catalog_cross_domain_connection" value="indirect"> Indirect connection<br>\
//<!-- &emsp;&emsp;<input type="checkbox" name="catalog_fake_access"> Fake access made by human to avoid poor administration<br>\
//&emsp;&emsp;&emsp;(This causes heavier network traffic and server load,<br>\
//&emsp;&emsp;&emsp;but administrators can\'t see what script you are using)<br>\
//&emsp;Configuration:<br>\
//&emsp;&emsp;(To get faster feeling, you should check them all.)<br> -->\
//&emsp;Networking: load on demand for reducing initial network traffic<br>\
//&emsp;&emsp;<input type="checkbox" name="catalog_draw_on_demand"> Threads<br>\
//&emsp;&emsp;<input type="checkbox" name="catalog_load_on_demand"> HTMLs<br>\
//&emsp;Localtime offset<input type="text" name="localtime_offset" size="2" style="text-align: right;"><br>'+
//          '&emsp;Tagging:<br>'+
//          '&emsp;&emsp;Ignore tags latter than <input type="text" name="catalog.tag.ignore" size="2" style="text-align: right;">th in a board/thread<br>'+
//          '&emsp;&emsp;Ignore boards/threads which have more than <input type="text" name="catalog.tag.max" size="2" style="text-align: right;"> tags<br>'+
//          '&emsp;Board group configuration:<br>'+
//          '&emsp;&emsp;<textarea rows="9" cols="60" name="catalog_board_list_str"></textarea><br>'+
//          '&emsp;&emsp;<input type="checkbox" name="catalog.board.owners_recommendation"> Read owner\'s recommendation<br>'+
////          '&emsp;&emsp;<input type="button" value="Scan"> Scan board tags<br>'+
//          '&emsp;&emsp;<input type="button" value="Generate"> Generate board groups from tags<br>'+
//          '&emsp;<input type="checkbox" name="catalog.style_general_list"> Use general style<br>'+
//          '&emsp;&emsp;<textarea rows="4" cols="40" name="catalog.style_general_list_str"></textarea><br>'+
//'&emsp;Catalog/Pop-up/Search<br>\
//<!-- &emsp;&emsp;<input type="checkbox" name="catalog_format.show.images_2nd">\
//<input type="checkbox" name="catalog_format.hover.images_2nd">\
//<input type="checkbox" name="catalog_format.search.images_2nd"> 2nd or more images in OP<br> -->\
//&emsp;&emsp;<input type="checkbox" name="catalog_format.show.posts">\
//<input type="checkbox" name="catalog_format.hover.posts">\
//<input type="checkbox" name="catalog_format.search.posts"> Posts<br>\
//&emsp;&emsp;<input type="checkbox" name="catalog_format.show.fileinfo">\
//<input type="checkbox" name="catalog_format.hover.fileinfo">\
//<input type="checkbox" name="catalog_format.search.fileinfo"> File information<br>\
//<!--&emsp;&emsp;<input type="checkbox" name="catalog_checkbox_deletion_show">\
//<input type="checkbox" name="catalog_checkbox_deletion_hover">\
//<input type="checkbox" name="catalog_checkbox_deletion_search"> Checkbox for deletion<br> -->\
//&emsp;&emsp;<input type="checkbox" name="catalog_format.show.contents">\
//<input type="checkbox" name="catalog_format.hover.contents">\
//<input type="checkbox" name="catalog_format.search.contents"> Format contents<br>\
//&emsp;&emsp;<input type="checkbox" name="catalog_format.show.layout">\
//<input type="checkbox" name="catalog_format.hover.layout">\
//<input type="checkbox" name="catalog_format.search.layout"> Format layout<br>\
//&emsp;&emsp;<input type="checkbox" name="catalog_format.show.style">\
//<input type="checkbox" name="catalog_format.hover.style">\
//<input type="checkbox" name="catalog_format.search.style"> Format style<br>\
//<!-- &emsp;&emsp;<input type="checkbox" name="catalog_border_show">&emsp;&emsp;&emsp; Show border<br> -->\
//<!-- &emsp;&emsp;<input type="checkbox" name="catalog_enable_background">&emsp;&emsp;&emsp; Use backgfound color<br> -->\
//&emsp;&emsp;<input type="checkbox" name="catalog_footer"> Info(num of posts, images and page)<br>\
//&emsp;&emsp;&emsp;<input type="checkbox" name="catalog_footer_br"> always over/under the image<br>\
//&emsp;&emsp;<input type="checkbox" name="catalog_popup"> Use pop-up window<br>\
//&emsp;&emsp;&emsp;appear/disappear:<br>\
//&emsp;&emsp;&emsp;<input type="radio" name="catalog_popdown" value="imm">immediately<br>\
//&emsp;&emsp;&emsp;<input type="radio" name="catalog_popdown" value="delay">delayed \
//<input type="text" name="catalog_popup_delay" size="6" style="text-align: right;">\
//<input type="text" name="catalog_popdown_delay" size="6" style="text-align: right;"> ms<br>\
//&emsp;&emsp;&emsp;<input type="checkbox" name="catalog_popup_size_fix"> Fix size when you move it<br>\
//&emsp;&emsp;&emsp;<input type="checkbox" name="catalog_localtime"> Localtime<br>\
//&emsp;&emsp;Num of posts in thread headline: <input type="text" name="catalog_t2h_num_of_posts" size="3" style="text-align: right;"><br>\
//&emsp;&emsp;<input type="checkbox" name="catalog_triage"> Enable triage pop-up<br>\
//&emsp;&emsp;&emsp; Style:<textarea style="height:1em" cols="40" name="catalog_triage_str"></textarea><br>\
//<!-- &emsp;<input type="checkbox" name="catalog_enable_cross_board"> Enable cross-board catalog<br> -->\
//<!-- &emsp;<input type="checkbox" name="catalog_enable_cross_domain"> Enable cross-domain catalog<br> -->\
//<!-- &emsp;&emsp; Cache working in <textarea style="height:1em" cols="20" name="catalog_sw_domain"></textarea><br> -->'+
//&emsp;Click to:<br>\
//&emsp;&emsp;<input type="radio" name="catalog_click" value="open">Go to/Open the thread<br>\
//&emsp;&emsp;<input type="radio" name="catalog_click" value="expand">Expand/shrink the OP in catalog<br>\
//&emsp;&emsp;&emsp;&emsp;<input type="checkbox" name="catalog_expand_at_initial"> Expand at initial<br>\
//&emsp;&emsp;&emsp;<input type="checkbox" name="catalog_no_popup_at_expanded"> Don\'t popup when the catalog is expanded<br>\
//&emsp;&emsp;<input type="checkbox" name="catalog_open_in_new_tab"> Open the thread in new tab<br>\
//&emsp;&emsp;&emsp;<input type="checkbox" name="catalog_use_named_window"> Prevent opening a thread in multiple tabs<br>\
//<br>\
'</div></div><div style="clear: both">\
<input type="button" value="cancel">\
<input type="button" value="apply">\
<input type="button" value="ok">\
&emsp;<input type="button" value="apply+save">\
<input type="button" value="load_default">\
<input type="button" value="load_samples">'+
//&emsp;&emsp;<input type="checkbox" name="debug_mode"> Debug mode\
//<input type="checkbox" name="show_tooltip"> Show tooltips\
'</div>';
if (!pref.test_mode['17']) { // node leak test.
        pref_apply_prep(false);
//        if (brwsr.ff) pn7.draggable = false;
        cnst.bottom_top(pn7);
}
      }
if (!pref.test_mode['17']) {
      var fm = pn7_1.getElementsByTagName('input');
      for (var i=0;i<fm.length;i++) {
        if (fm[i].type=='button') {
          if (make) fm[i].addEventListener('click', button_action, false);
          else fm[i].removeEventListener('click', button_action, false);
        }
      }
}
      if (make) return pn7;
      else {
if (pref.test_mode['17']) {
  pn7_1.innerHTML = '';
  pn7.parentNode.removeChild(pn7);
  return null;
} else
        pn7_1 = null;
        return cnst.div_destroy(pn7, true); // returns null
      }

//      function pref_apply_prep(set){
//        var fm = pn7_1.getElementsByTagName('input');
//        for (var i=0;i<fm.length;i++) {
//          if (fm[i].type=='button') continue;
//          var tgt = fm[i].name;
//          if (set) {
//            if      (typeof(pref[tgt])=='number' ) pref[tgt] = parseInt(fm[i].value,10);
//            else if (typeof(pref[tgt])=='boolean') pref[tgt] = fm[i].checked;
//            else if (typeof(pref[tgt])=='string' ) if (fm[i].checked) pref[tgt] = fm[i].value;
//          } else {
//            if      (typeof(pref[tgt])=='number' ) fm[i].value = pref[tgt];
//            else if (typeof(pref[tgt])=='boolean') fm[i].checked = pref[tgt];
//            else if (typeof(pref[tgt])=='string' ) if (pref[tgt] == fm[i].value) fm[i].checked = true;
//          }
//        }
//        if (set) {
//          sessionStorage.pref = JSON.stringify(pref);
//          listener();
//          timer_obj.init();
//        }
//      }
      function pref_apply_prep(set){
        pref_func.apply_prep(pn7_1,set);
        if (set) {
          listener();
          timer_obj.init();
//          if (wafd!=null) wafd.workaround_for_dollchan_pref_changed();
        }
      }
      function button_action(e){
//        console.log(e);
        var src = e.currentTarget.value;
        if      (src=='apply'       ) pref_apply_prep(true);
        else if (src=='cancel'      ) show_hide();
        else if (src=='ok'          ) {pref_apply_prep(true);show_hide();}
        else if (src=='apply+save'  ) {pref_apply_prep(true);if (localStorage) localStorage[pref.script_prefix+'.pref']=JSON.stringify(pref);}
        else if (src=='load_default') {pref = pref_default();pref_apply_prep(false);pref_apply_prep(true);pref_func.obj_init();} // last pref_apply_prep is for writing to sessionStrage.
//        else if (src=='load_backwash_style') {pref_func.pref_overwrite(pref,pref_func.pref_samples['backwash']);pref_apply_prep(false);}
        else if (src=='load_samples') {pref_func.pref_samples.init();}
        else if (src=='Dump(csv)'   ) chart_obj.dump('csv');
        else if (src=='Dump(obj)'   ) chart_obj.dump('obj');
        else if (src=='Clear data'  ) chart_obj.clear();
        else if (src=='JSON'        ) {pref_func.apply_prep(pn7_1.getElementsByTagName('TEXTAREA')['cli.json_str'],true);pref_func.site2_json();}
        else if (src=='EVAL'        ) {pref_func.apply_prep(pn7_1.getElementsByTagName('TEXTAREA')['cli.eval_str'],true);pref_func.site2_eval();}
      }
    }
    return show_hide;
//  })();
  }

  var pipe_name = pref.script_prefix + '.graph.' + site.board + '__pipe__';
  var info_raw  = site.nickname + site.board;
//  var listener = (function(){
  function make_listener(){
    var on = false;
    var value_old;
    function listen_event(e){
//      console.log('EVENT:'+e.key);
      if (e.newValue===null) return;
      if (e.key===pipe_name && pref.aggregator==='false') {
        var tmp = brwsr.JSON_parse(e.newValue);
        if (tmp[0]!=value_old) {
          value_old = tmp[0];
          chart_obj.data_update(tmp[0], tmp[1], tmp[2]);
          timer_obj.show_page([null, '+'+tmp[1]+'+'+tmp[2]+'@'+tmp[0]]);
        }
//      } else if (e.key.search(info_raw)!=-1 && pref.info_client) {
//        var page_no = e.key.substr(info_raw.length);
//        var timer = timer_obj.timer();
//        if (timer!=null) {
//          var value = JSON.parse(e.newValue);
//          timer.page_check4(value[0], page_no, new DOMParser().parseFromString(value[1],'text/html'));
//        }
      }
    }
    function setup(){
      if (!on && pref.aggregator==='false') {
        window.addEventListener   ('storage', listen_event, false);
        on = true;
      } else if (on && pref.aggregator==='true') {
        window.removeEventListener('storage', listen_event, false);
        on = false;
      }
    }
    setup();
    return setup;
  };
//  })();
  var listener = null;
  if (site.features.listener && pref.features.listener) listener = make_listener();

  function make_timer_obj(pn0){
//  var timer_obj = (function(){
////    var pn0 = div_init(0,'5px',  '5px' ,'txt','init');
//    var pn0 = cnst.init('left:0px:bottom:0px:txt:init:fontSize:24px:Show:tile:set:left:tile:set:bottom');
    pn0.addEventListener('click', function(){init(false);}, false);  // Toggle ON/OFF
    var timer_obj2 = null;
    function init(property_changed){
      if (timer_obj2 == null && (pref.check_page || (pref.aggregator=='true' && (pref.check_thread || pref.check_post)))) timer_obj2 = make_timer(pn0);
      else if (timer_obj2 != null) {
        if (!property_changed || (!pref.check_page && (pref.aggregator=='false' || (!pref.check_thread && !pref.check_post)))) stop_destroy();
        else if (property_changed) timer_obj2.timer().restart(false);
      }
    }
    function stop_destroy(){
      timer_obj2 = timer_obj2.destroy();
      pn0.style.color = 'gray';
    }
//    if (pref.auto_start) init(false);
    if (pref.auto_start) setTimeout(function(){init(false);},10000);
    var pn0_str = ['',''];
    return {
      init : function(){init(true);},
      finished : function(){stop_destroy();},
      timer: function(){return timer_obj2;},
      show_page : function(str){
//        pn0.appendChild(document.createTextNode(str+'@'+cnst.get_time()));
//        pn0.textContent = str+'@'+cnst.get_time();
        if (                 str[0]!=null) pn0_str[0] = str[0]+'@'+cnst.get_time();
        if (str.length>=2 && str[1]!=null) pn0_str[1] = str[1];
        if (str.length>=3 && str[2]!=null) pn0_str[0] = str[2];
//        pn0.textContent = pn0_str1 +', '+ pn0_str2 +', '+pn0_str3;
        if (pn0_str[0]!='' && pn0_str[1]!='') pn0.textContent = pn0_str[0] +', '+ pn0_str[1];
        else pn0.textContent = pn0_str[0] + pn0_str[1];
      }
    }
//  })();
  }

  function make_timer(pn0){
    var page = (function(){
      var idx = [];
      var p = 0;
      var now = 0;
      var str_place='';
      var check_page = pref.check_page;
      if (window.location.href.search(site.thread_keyword)==-1) check_page = false;
//      var flag = [false,false,mode_graph_only]; // top, last, myself, to be got.
      
//      idx[0] = 0;
//      idx[1] = site.max_page -1; // last page
//      for (var i=2;i<site.max_page;i++) idx[i]=i-1;
//      idx[site.max_page] = site.max_page; // +1 for myself.
      function init(){
        var j=0;
        idx[0] = -1; // -1 for mot match.
        idx[1] = -1;
        if (pref.check_post) idx[j++] = 0;
        if (pref.check_thread) idx[j++] = site.max_page-1;
        var next = (now+1)%(site.max_page+1);
        if (now !=idx[0] && now !=idx[1]) idx[j++] = now;  // idx[2] for page_no.
        if (next!=idx[0] && next!=idx[1]) idx[j++] = next; // idx[3] for next_page.
        for (var i=0;i<=site.max_page;i++)
          if (i!=now && i!=next && i!=idx[0] && i!=idx[1]) idx[j++] = i;
        flag = [!pref.check_post, !pref.check_thread, !check_page];
        p=0;
//        if (flag[0] && flag[1] && flag[2]) timer.stop();
      }
      init();
  
      return {
        no   : function() {return idx[p];},
        step : function() {p=(p+1)%(site.max_page+1);},
        prep_next : function() {init();},
        flag_set : function(i) {flag[i]=true;},
        all_got  : function() {return flag[0] && flag[1] && flag[2];},
        str_set  : function(i,j) {if (check_page && i!=site.max_page) {str_place = i + '.' + j; now=i;}}, // patch for a bug of showing '20.0'.
        str_get  : function() {return str_place;},
        init : function(){init();}
      }
    })();

    var last_post_old = 0;
    var last_post = 0;
    var last_ops_old = [];
    var num_of_new_threads = 0;
    var timer = (function(){
      var req = new XMLHttpRequest();
      req.addEventListener('load',  req_events, false);
      req.addEventListener('error', req_events, false);
      req.addEventListener('abort', req_events, false);
    
      function req_events(evt) {
        var parser = new DOMParser();
        if (req.status==404) timer.dead();
        else {
          var date = Date.now();
          if (pref.info_server && page.no()!=site.max_page && brwsr.sw_cache)
            brwsr.sw_cache.setItem(info_raw+'p'+page.no(),{date: date, status: req.status, responseText: req.responseText});
          if (pref.catalog_snoop_refresh && catalog_obj.catalog_func()!=null)
            catalog_obj.catalog_func().catalog_insert(info_raw+'p'+page.no(),{date: date, status: req.status, responseText: req.responseText});
          page_check2(parser.parseFromString(req.responseText, 'text/html'));
        }
      }
      function get_page(url) {
        req.open('GET', url, true);
        req.send(null);
      }
      var checking = false;
      var time_str;
      function page_check3(){
        if (!checking) {
          checking = true;
          time_str = cnst.get_time();
          page_check();
        }
      }
      function page_check(){
//        timer.stop(false);
//        var url = site.url_prefix + page.no() + '.html';
//        var url = site.make_url([site.board, page.no(), 'p')[0];
        var url = site2[site.nickname].make_url4([site.nickname, site.board, page.no(), 'page_html'])[0];
        if (page.no()==site.max_page) url = window.location.href;
        get_page(url);
      }

      var interval_missing = 1000;
//      var interval_found   = 60000 * 10;
//      var interval_error   = 60000 * 10; // 10 min.
      var retry_count = 0;
      var retry_limit = (site.max_page+1)+5;
      var id = null;
      var interval_old = 0;
      function timer_restart(force){
        if (force || interval_old != pref.interval_found) {
          timer_stop(false);
          id = setInterval(page_check3, pref.interval_found*60000);
        }
        interval_old = pref.interval_found;
      }
      timer_restart(true);
      page_check3();
      pn0.style.color = '#000000';

      function timer_stop(end){
        if (id!=null) clearInterval(id);
        id = null;
        if (end) pn0.style.color = 'gray';
      }

//      var myself = get_nos(document,-1,-1);
      var myself = (window.location.href.search(site.thread_keyword)!=-1)? get_nos(document,-1,-1) :-2;
      return {
//        req : function(){return req;}, // for debug
        req : req, // for debug
        found: function(){
          retry_count = 0;
//          id = setInterval(page_check3, pref.interval_found*60000);
          checking = false;
        },
        missing: function(){
          if (req.status==200 && retry_count++<retry_limit) setTimeout(page_check, interval_missing);
          else {
            retry_count = 0;
//            id = setInterval(page_check, interval_error);
            checking = false;
          }
        },
        stop:  function(end){timer_stop(end);},
        dead:  function(){timer_obj.show_page(['Dead']);timer_stop(true);timer_obj.finished();},
        myself: function(){return myself;},
        restart: function(force){timer_restart(force)},
        timestr: function(){return time_str;},
        destroy: function(){
          req.removeEventListener('load',  req_events, false);
          req.removeEventListener('error', req_events, false);
          req.removeEventListener('abort', req_events, false);
        }
      }
    })();

    if (brwsr.sw_cache && pref.info_client) brwsr.sw_cache.subscribe(true);
    return {
      destroy: function(){
        if (brwsr.sw_cache) brwsr.sw_cache.subscribe(false);
        timer.stop(true);
        timer.destroy();
        return null;
      },
      timer: function(){return timer;},
//      page_check4: page_check4
      page_check5: page_check5
    }

    function page_check5(key,value,args){
      var page_no = comf.name2domainboardthread(key,true)[2].substr(1);
      page_check4(value.date, page_no, new DOMParser().parseFromString(value.responseText,'text/html'));
    }
    function page_check4(time_stamp, page_no, doc){
      var ops = site.get_ops(doc);
      var myself = timer.myself();
      for (var i=0;i<ops.length;i++) {
        if (myself==ops[i]) {
          page.str_set(page_no,i);
          var str = page.str_get() + '@' + new Date(time_stamp).toLocaleTimeString();
          timer_obj.show_page([null,null,str]);
          page.prep_next();
          timer.restart(true);
          if (pref.check_page) options.func0_exe(page.str_get());
          break;
        }
      }
    }

    function page_check2(doc){
      var page_no = page.no();
      get_nos(doc,timer.myself(),page_no);
      if (page.all_got() && page.no()!=site.max_page) {
        var increase_posts = (last_post_old==0)? 0 : last_post-last_post_old;
        var str = '';
        if (pref.check_page)   str = page.str_get();
        if (pref.check_post)   str = str + '+' + increase_posts;
        if (pref.check_thread) str = str + '+' + num_of_new_threads;
        timer_obj.show_page([str]);
        page.prep_next();
        timer.found();
        increase_posts = parseInt(increase_posts,10);
        if (isNaN(increase_posts) || increase_posts==null) increase_posts = 0;
        if (pref.check_post || pref.check_thread) chart_obj.data_update(timer.timestr(),increase_posts, num_of_new_threads);
        last_post_old = last_post;
        if (pref.check_page) options.func0_exe(page.str_get());
      } else {
        timer_obj.show_page([page_no+'?']);
        page.step();
        timer.missing();
      }
    }

    function get_nos(tgt_doc,myself,page_no){
//      var ops = [];
//      var num = 0;
//      var divs = tgt_doc.getElementsByTagName('div');
//      for (var i=0;i<divs.length;i++) {
//        if (divs[i].className == 'thread' || divs[i].className == 'thread kc_showReplies') {
//          var op_no = divs[i].id.substring(7); // substring(7) for removing 'thread_'
//          if (myself==op_no) {str_place = page.str_set(page_no,num); page.flag_set(2);}
//          ops[num++] = op_no;
//        }
//      }
      var ops = site.get_ops(tgt_doc);
      for (var i=0;i<ops.length;i++) if (myself==ops[i]) {page.str_set(page_no,i); page.flag_set(2);break;}
      if (myself==-1) return ops[0];
      if (page_no==0) {
        page.flag_set(0);
        last_post = get_last_post(tgt_doc);
      }
      if (page_no==site.max_page-1) {
        page.flag_set(1);
        var len = last_ops_old.length
        if (len!=0) {
          var last_op = ops[ops.length-1];
          var i=0;
          while (i<len && last_op!=last_ops_old[len-i-1]) i++;
          num_of_new_threads = i;
        } else num_of_new_threads = 0;
        last_ops_old = ops;
      }
    }

    function get_last_post(tgt_doc){
      var posts = site.get_posts(tgt_doc);
//      var posts = [];
//      var anchors = tgt_doc.getElementsByTagName('a');
//      var num = 0;
//      for (var i=0;i<anchors.length;i++)
//        if (anchors[i].name != '') posts[num++] = anchors[i].name;
      var last_post = posts[0];
      for (var i=1;i<posts.length;i++) {
        if (posts[i]>last_post) last_post=posts[i];
//      else break; // Cause a bug when a post had made in a sunk thread and immediately deleted.
      }
      return last_post;
    }
  }

////function make_wafd(){ // deleted 2018.10.23
////    function on_change_workaround_for_dollchan(exe){
//////      pref.workaround_for_dollchan = workaround_for_dollchan.checked;
////      if (pref.workaround_for_dollchan == true) {
//////        document.getElementById('postform_row_files').style.display = '';
//////        var tmp = document.getElementsByTagName('input');
//////        for (var i=0;i<tmp.length;i++) if (tmp[i].type=='file') tmp[i].parentNode.style.display = '';
////        if (exe) on_change_workaround_for_dollchan_2(); // temporarily.
////        if (pref.wafd_tb=='tb') document.getElementById('postform_label_comment').addEventListener('mouseout', on_change_workaround_for_dollchan, false);
////        else site.components.postform_submit.addEventListener('mouseenter', on_change_workaround_for_dollchan, false);
////      } else {
//////        document.getElementById('postform_row_files').style.display = 'none';
////        if (pref.wafd_tb=='tb') document.getElementById('postform_label_comment').removeEventListener('mouseout', on_change_workaround_for_dollchan, false);
////        else site.components.postform_submit.removeEventListener('mouseenter', on_change_workaround_for_dollchan, false);
////      }
////    }
////    function workaround_for_dollchan_pref_changed(){
////      if (pref.workaround_for_dollchan == true) {
////        pref.workaround_for_dollchan = false;
////        pref.wafd_tb = (pref.wafd_tb=='tb')? 'reply' : 'tb';
////        on_change_workaround_for_dollchan(null);
////        pref.workaround_for_dollchan = true;
////        pref.wafd_tb = (pref.wafd_tb=='tb')? 'reply' : 'tb';
////        on_change_workaround_for_dollchan(null);
////      }
////      open_spoiler();
////    }
////    function on_change_workaround_for_dollchan_2(){
////      var inputs = document.getElementsByTagName('input');
////      var files = [];
////      for (var i=0;i<inputs.length;i++) if (inputs[i].type=='file') files.push(inputs[i]);
////      var grand_parent = document.getElementById('postform_row_files');
////      var parent;
////      for (var i=0;i<grand_parent.childNodes.length;i++) if (grand_parent.childNodes[i].align=='left') parent = grand_parent.childNodes[i];
//////      for (var i=0;i<4;i++) for (var j=0;j<4;j++) if (files[i].name==('file_'+j)) parent.childNodes[i].appendChild(files[i]);
////      for (var i=0;i<files.length;i++) {
////        var no = parseInt(files[i].name.replace(/file_/,""));
////        parent.childNodes[no].appendChild(files[i]);
//////        parent.childNodes[no].style.display = '';
//////        files[i].addEventListener('change', on_change_workaround_for_dollchan_3, false);
////        var evt = document.createEvent('UIEvents');
////        evt.initUIEvent('change', false, true, window, 1);
////        files[i].dispatchEvent(evt);
////      }
////
//////      var images = images = document.getElementsByClassName('de-file-img');
//////      var thumbnails = [];
//////      for (var i=0;i<images.length;i++) if (images[i].tagName=='IMG') thumbnails.push(images[i]);
////      var thumbnails_parent = document.getElementById('postform_label_comment');
////      var show_thumbnails = true;
////      for (var i=0;i<files.length;i++) {
//////        if (files[i].value=='' && thumbnails[i]!=undefined) thumbnails[i].parentNode.removeChild(thumbnails[i]);
////        if (files[i].value=='') {
////          var thumbnail = thumbnails_parent.getElementsByTagName('img')[0];
////          if (thumbnail!=undefined) thumbnail.parentNode.removeChild(thumbnail);          
////        }
////        if (files[i].value!='' || show_thumbnails) {
////          thumbnails_parent.childNodes[i].style.display = '';
////          if (files[i].value=='') show_thumbnails = false;
////        } else thumbnails_parent.childNodes[i].style.display = 'none';
//////        console.log(i + ': '+ files[i].value);
////      }
////    }
//////    function on_change_workaround_for_dollchan_3(){
//////      console.log('change');
//////    }
////    if (pref.workaround_for_dollchan == true) on_change_workaround_for_dollchan(false); // initial
////
////    var spoiler_org = null;
////    var rule = [];
////    function open_spoiler(){
////      if (pref.wafd_open_spoiler == true) {
////        rule = [];
////        for (var j=0;j<document.styleSheets.length;j++)
////          for (var i=0;i<document.styleSheets[j].cssRules.length;i++)
////            if (document.styleSheets[j].cssRules[i].selectorText && document.styleSheets[j].cssRules[i].selectorText.search(/spoiler/)!=-1) rule.push(j,i)
////        if (document.styleSheets[rule[0]].cssRules[rule[1]].selectorText.search(/hover/)!=-1) {rule.push(rule.shift());rule.push(rule.shift());}
////        spoiler_org = document.styleSheets[rule[0]].cssRules[rule[1]].style.cssText;
////        document.styleSheets[rule[0]].cssRules[rule[1]].style.cssText = document.styleSheets[rule[2]].cssRules[rule[3]].style.cssText;
////      } else if (spoiler_org!=null) document.styleSheets[rule[0]].cssRules[rule[1]].style.cssText = spoiler_org;
////    }
////    if (pref.wafd_open_spoiler == true) open_spoiler(); // initial
////    return{
//////      on_change_workaround_for_dollchan: function(exe){on_change_workaround_for_dollchan(exe);},
////      workaround_for_dollchan_pref_changed: function(){workaround_for_dollchan_pref_changed();}
////    }
////  }

 function make_post_form_obj(){ // pn9){
//  var post_form = (function(){
//    var pn9 = div_init(9,'120px','30px','button','post_form');
//    var pn9 = cnst.init('left:120px:bottom:30px:button:post_form:Show');
    var tack;
//    pn9.addEventListener('click', show_hide2, false);
    if (site2[site.nickname].postform && site.postform) tack = cnst.set_tack_float2(site.postform, postform_float, postform_dock);
//    if (site2[site.nickname].postform) {
//      tack = site2[site.nickname].postform.init();
//      if (tack) cnst.set_tack_float(tack, postform_float, postform_dock);
////      if (tack) tack.addEventListener('click', show_hide, false);
//    }
    function postform_focus(){
      site.components.postform_comment.focus();
    }
    function postform_float(e){
      if (site2[site.nickname].postform.activation) site2[site.nickname].postform.activation();
      var obj = cnst.tack_float_nSblgs(e);
      obj.rolldown = postform_focus;
      pn10 = obj.pn;
      obj.cn.childNodes[0].innerHTML = 
        '<div>'+
          '<input type="checkbox" name="prevent_redirection">prevent redirection'+
        '</div>'+
        '<div></div>';
      pn10_2 = obj.cn.childNodes[0];
      pref_func.apply_prep(pn10_2,false);
      pn10_2.getElementsByTagName('input')['prevent_redirection'].onchange = on_change_redirection;
      options.func0_prep(obj.cn.childNodes[0].childNodes[1],obj.pn, obj.cn);
      postform_focus();
      if (site.postform.getAttribute('name') === pref.cpfx+'draft') {
        site.postform.style.display = null;
        obj.pn.style.top = '0px';
        obj.pn.style.left = '0px';
      }
    }
    function postform_dock(e){
      cnst.tack_dock_nSblgs(e);
      if (site.postform.getAttribute('name') === pref.cpfx+'draft') site.postform.style.display = 'none';
    }
    function show_hide2(){
      if (tack.style.display!=='none') postform_float({currentTarget:tack});
      else postform_dock({currentTarget:pn10.childNodes[0]});
    }
    var pn10 = null;
    var pn10_2 = null;
//    var pn10_2 = cnst.add_to_tb(pn10,'<input type="checkbox">workaround for dollchan<input type="checkbox">prevent redirection');
//    var pn10_2 = document.createElement('div');
//    pn10_2.style.float = 'right'; // doesn't work on FF
//    pn10_2.innerHTML = '<input type="checkbox">workaround for dollchan<input type="checkbox">prevent redirection'
//    var prevent_redirection = pn10_2.childNodes[2];
//    prevent_redirection.onchange = on_change_redirection;
//    var workaround_for_dollchan = pn10_2.childNodes[0];
//    workaround_for_dollchan.onchange = on_change_workaround_for_dollchan;
//    var workaround_for_dollchan;
//    var on = false; // working code
////    var hidden_elements = [];
//    function show_hide(){
//      if (site2[site.nickname].postform.activation) site2[site.nickname].postform.activation();
//      var tgt    = site.postform;
//      if (!on) {
//        if (pn10==null) {
//          var left = tgt.offsetLeft - tgt.scrollLeft;
//          var top = tgt.offsetTop - tgt.scrollTop + site.header_height();
//          pn10 = cnst.init('left:'+left+'px:top:'+top+'px:overflow:hidden:Show:tb',function(){site.components.postform_comment.focus();},cnst.void_func,show_hide,cnst.void_func);
////          pn10 = cnst.init('left:0px:tile:get:bottom:overflow:hidden:Show:tb',function(){site.components.postform_comment.focus();},cnst.void_func,show_hide,cnst.void_func)[0];
//          pn10.id = 'pn10_debug';
////          pn10_2 = cnst.add_to_tb(pn10,'<input type="checkbox" name="workaround_for_dollchan">workaround for dollchan<input type="checkbox" name="prevent_redirection">prevent redirection');
//          pn10_2 = cnst.add_to_tb(pn10,'<input type="checkbox" name="prevent_redirection">prevent redirection');
//          pref_func.apply_prep(pn10_2,false);
////          prevent_redirection = pn10_2.childNodes[2];
//          prevent_redirection = pn10_2.childNodes[0];
//          prevent_redirection.onchange = on_change_redirection;
////          workaround_for_dollchan = pn10_2.childNodes[0];
////          workaround_for_dollchan.onchange = on_change_workaround_for_dollchan;
////          pn10.childNodes[0].insertBefore(pn10_2,pn10.childNodes[0].childNodes[2]);
////          if (brwsr.ff)  pn10.childNodes[0].childNodes[2].style.float = 'right'; // doesn't work
////          if (brwsr.ff) pn10.childNodes[0].childNodes[2].outerHTML = pn10.childNodes[0].childNodes[2].outerHTML.replace(/<div/,"<div style=\"float: right\""); // discard events configuration
////          if (brwsr.ff) pn10.childNodes[0].childNodes[2].setAttribute('style','float: right');
//          pn10.childNodes[1].innerHTML = '<div style="display: none"></div><div></div>';
////          options.func0_prep(pn10.childNodes[1].childNodes[0],pn10.childNodes[0]);
//          options.func0_prep(pn10.childNodes[1].childNodes[0],pn10);
//        }
////        parent = tgt.parentNode; // working code
////        no = 0;
////        while (parent.childNodes[no]!=tgt && no < parent.childNodes.length) no++;
////        pn10.childNodes[1].childNodes[1].appendChild(tgt);
////        site.root_body.appendChild(pn10);
//        tgt.parentNode.insertBefore(pn10,tgt);
//        pn10.childNodes[1].childNodes[1].appendChild(tgt);
////        var labels = document.getElementsByClassName('label'); // working code for KC, but dangerous.
////        for (var i=0;i<labels.length;i++) {
////          if (labels[i].innerHTML=="Go to:" || labels[i].innerHTML=="Password:") {
////            if (labels[i].parentNode.style.display != 'none'){
////              labels[i].parentNode.style.display = 'none';
////              hidden_elements.push(labels[i].parentNode);
////            }
////          }
////        }
////        if (site.postform_rules!=null) {
////          site.postform_rules.style.display = 'none';
////          hidden_elements.push(site.postform_rules);
////        }
//        if (site2[site.nickname].postform) site2[site.nickname].postform.on(site.postform);
//        site.components.postform_comment.focus();
////        cnst.bottom_top(pn10);
//      } else {
//        if (site2[site.nickname].postform) site2[site.nickname].postform.off(pn10);
////        while (hidden_elements.length!=0) hidden_elements.pop().style.display = '';
//        pn10.parentNode.insertBefore(pn10.childNodes[1].childNodes[1].lastChild,pn10);
//        pn10.parentNode.removeChild(pn10);
//      }
//      on = !on;
//      if (tgt.getAttribute('name') === pref.cpfx+'draft') tgt.style.display = (on)? '' : 'none';
//    }

    var submits = [];
    var forms_post = [];
    var forms = document.getElementsByTagName('form');
//    for (var i=0;i<forms.length;i++) if (forms[i].method!='post') forms.splice(i,1); // post, delete, report // doesn't work.
    for (var i=0;i<forms.length;i++) if (forms[i].method=='post') forms_post.push(forms[i]); // post, delete, report
    for (var i=0;i<forms_post.length;i++) {
      var inputs = forms_post[i].getElementsByTagName('input');
      for (var j=0;j<inputs.length;j++) if (inputs[j].type=='submit') submits.push(inputs[j]);
    }
//    var posted = [];
//    for (var i=0;i<submits.length;i++) {posted.push(false); submits[i].addEventListener('click', function(){posted[i] = true;}, false);} // doesn't work because of closure.
//    for (let i=0;i<submits.length;i++) {posted.push(false); submits[i].addEventListener('click', function(){posted[i] = true;}, false);} // 'let' can't be used in chrome script.
    var posted = false;
    var targets = [];
    for (var i=0;i<submits.length;i++) submits[i].addEventListener('click', function(){posted = true;}, false);
//    window.addEventListener('beforeunload', on_beforeunload, false);
    var pn11;
    function on_change_redirection(){
//      pref.prevent_redirection = prevent_redirection.checked;
      if (pn10_2!=null) pref_func.apply_prep(pn10_2,true);
      if (pref.prevent_redirection == true) {
        if (!pn11) {
          pn11 = cnst.make_iframe('redirect_target');
          pn11.onload = function(){if (pn11.location && pn11.location.href!='about:blank') pn11.location.replace('about:blank');}; // clear always.
        }
        for (var i=0;i<submits.length;i++) {
          targets[i] = forms[i].target;
          forms[i].target = 'redirect_target';
        }
      } else for (var i=0;i<submits.length;i++) forms[i].target = targets[i];
    }
    if (pref.prevent_redirection == true) on_change_redirection(); // initial
//    function on_beforeunload(e){ // doesn't work. too late?
//      if (prevent_redirection.checked == true) {
//        console.log('beforeunload: ' + e);
////        for (var i=0;i<submits.length;i++) {
////          if (posted[i]) forms[i].target = 'rediret_target';
////          posted[i] = false;
////        }
//        if (posted) for (var i=0;i<submits.length;i++) {
//          targets[i] = forms[i].target;
//          forms[i].target = 'redirect_target';
//        }
//      }
//    }

//    function on_change_workaround_for_dollchan(){
////      pref.workaround_for_dollchan = workaround_for_dollchan.checked;
//      if (pn10_2!=null) pref_func.apply_prep(pn10_2,true);
//      if (pref.workaround_for_dollchan == true) {
////        document.getElementById('postform_row_files').style.display = '';
////        var tmp = document.getElementsByTagName('input');
////        for (var i=0;i<tmp.length;i++) if (tmp[i].type=='file') tmp[i].parentNode.style.display = '';
//        if (pn10_2!=null) on_change_workaround_for_dollchan_2(); // temporarily.
//        if (pref.wafd_tb=='tb') document.getElementById('postform_label_comment').addEventListener('mouseout', on_change_workaround_for_dollchan, false);
//        else site.components.postform_submit.addEventListener('mouseenter', on_change_workaround_for_dollchan, false);
//      } else {
////        document.getElementById('postform_row_files').style.display = 'none';
//        if (pref.wafd_tb=='tb') document.getElementById('postform_label_comment').removeEventListener('mouseout', on_change_workaround_for_dollchan, false);
//        else site.components.postform_submit.removeEventListener('mouseenter', on_change_workaround_for_dollchan, false);
//      }
//    }
//    if (pref.workaround_for_dollchan == true) on_change_workaround_for_dollchan(); // initial
//    function on_change_workaround_for_dollchan(){
//      if (pn10_2!=null) pref_func.apply_prep(pn10_2,true);
//      wafd.on_change_workaround_for_dollchan(true);
//    }
//    if (pref.workaround_for_dollchan == true) wafd.on_change_workaround_for_dollchan(false); // initial

//    function on_change_workaround_for_dollchan_2(){
//      var inputs = document.getElementsByTagName('input');
//      var files = [];
//      for (var i=0;i<inputs.length;i++) if (inputs[i].type=='file') files.push(inputs[i]);
//      var grand_parent = document.getElementById('postform_row_files');
//      var parent;
//      for (var i=0;i<grand_parent.childNodes.length;i++) if (grand_parent.childNodes[i].align=='left') parent = grand_parent.childNodes[i];
////      for (var i=0;i<4;i++) for (var j=0;j<4;j++) if (files[i].name==('file_'+j)) parent.childNodes[i].appendChild(files[i]);
//      for (var i=0;i<files.length;i++) {
//        var no = parseInt(files[i].name.replace(/file_/,""));
//        parent.childNodes[no].appendChild(files[i]);
////        parent.childNodes[no].style.display = '';
////        files[i].addEventListener('change', on_change_workaround_for_dollchan_3, false);
//        var evt = document.createEvent('UIEvents');
//        evt.initUIEvent('change', false, true, window, 1);
//        files[i].dispatchEvent(evt);
//      }
//
////      var images = images = document.getElementsByClassName('de-file-img');
////      var thumbnails = [];
////      for (var i=0;i<images.length;i++) if (images[i].tagName=='IMG') thumbnails.push(images[i]);
//      var thumbnails_parent = document.getElementById('postform_label_comment');
//      var show_thumbnails = true;
//      for (var i=0;i<files.length;i++) {
////        if (files[i].value=='' && thumbnails[i]!=undefined) thumbnails[i].parentNode.removeChild(thumbnails[i]);
//        if (files[i].value=='') {
//          var thumbnail = thumbnails_parent.getElementsByTagName('img')[0];
//          if (thumbnail!=undefined) thumbnail.parentNode.removeChild(thumbnail);          
//        }
//        if (files[i].value!='' || show_thumbnails) {
//          thumbnails_parent.childNodes[i].style.display = '';
//          if (files[i].value=='') show_thumbnails = false;
//        } else thumbnails_parent.childNodes[i].style.display = 'none';
////        console.log(i + ': '+ files[i].value);
//      }
//    }
////    function on_change_workaround_for_dollchan_3(){
////      console.log('change');
////    }

//    var pn11 = cnst.make_iframe('redirect_target');
//    pn11.onload = function(){if (pn11.location && pn11.location.href!='about:blank') pn11.location.replace('about:blank');}; // clear always.
//////    var pn11 = div_init(11,'100px','100px','div'); // working code.
//////    pn11.style.display = 'none';
////    var pn11 = cnst.init('left:100px:bottom:100px:display:none:Show');
////    pn11.id = 'pn11_debug';
////    pn11.addEventListener('click', function(){on_load('parent_click');}, false);
////    // delete contents for preventing from getting new posts by auto-updater.
//////    function pn11_init(){ // works on Chrome, but causes infinite loop on FF.
////      pn11.innerHTML = '<iframe name="redirect_target" id="redirect_target"></iframe>';
////////      document.getElementsByName('redirect_target')[0].setAttribute('onload','alert("AAA");return false;'); // work
////////      document.getElementsByName('redirect_target')[0].setAttribute('onload','on_load();'); // fail;
////////      document.getElementsByName('redirect_target')[0].onload  = function(){on_load('child_load')}; // work
////////      document.getElementsByName('redirect_target')[0].onclick = function(){on_load('child_click')}; // for debug.
////      pn11.childNodes[0].onload  = function(){on_load('child_load')}; // work
////      pn11.childNodes[0].onclick = function(){on_load('child_click')}; // for debug.
//////    }
//////    pn11_init();
////    function on_load(from_where){
////      console.log('load: '+from_where);
////      site.components.postform_submit.disabled = false;
//////      top.redirect_target.document
//////      if (posted) for (var i=0;i<submits.length;i++) forms[i].target = targets[i];
//////      posted = false;
//////      if (pn11.style.display=='none') pn11_init();
//////      if (pn11.style.display=='none') frames['redirect_target'].location.replace('about:blank');
////      if (pn11.style.display=='none') if (frames['redirect_target'].location.href!='about:blank') frames['redirect_target'].location.replace('about:blank');
////    }
//////    var pn11_on = false;
////    function debug(){
//////      if (!pn11_on) site.root_body.appendChild(pn11);
//////      else site.root_body.removeChild(pn11); // can't prevent redirection.
////      if (pn11.style.display=='none') pn11.style.display = '';
////      else {
////        pn11.style.display = 'none';
//////      pn11_on = !pn11_on;
//////      frames['redirect_target'].document.open();
//////      frames['redirect_target'].document.write('deleted');
//////      frames['redirect_target'].document.close();  // delete contents for preventing from getting new posts by auto-updater.
//////        pn11_init();
////        if (frames['redirect_target'].location.href!='about:blank') frames['redirect_target'].location.replace('about:blank');
////      }
////    }
////    
//////// working code
//////    var pn11 = div_init(11,'100px','100px','div');
//////    pn11.id = 'pn11_debug';
//////    pn11.innerHTML = '<iframe name="redirect_target" id="redirect_target"></iframe>'
//////    pn11.addEventListener('click', function(){on_load('parent_click');}, false);
////////    document.getElementsByName('redirect_target')[0].setAttribute('onload','alert("AAA");return false;'); // work
////////    document.getElementsByName('redirect_target')[0].setAttribute('onload','on_load();'); // fail;
//////    document.getElementsByName('redirect_target')[0].onload  = function(){on_load('child_load')};
//////    document.getElementsByName('redirect_target')[0].onclick = function(){on_load('child_click')}; // for debug.
//////    var forms = document.getElementsByTagName('form');
//////    for (var i=0;i<forms.length;i++) {
//////      if (forms[i].method=='post') forms[i].target = 'redirect_target'; // post, delete, report
//////    }
////////    window.addEventListener('load', on_load, false);
//////    pn11.addEventListener('load', on_load, false);
////////    pn11.redirect_target.addEventListener('load', on_load, false);
//////    function on_load(from_where){
//////      console.log('load: '+from_where);
//////      site.components.postform_submit.disabled = false;
////////      top.redirect_target.document
//////    }
    return show_hide2;
//  })();
  };


  var pn_debug = 0;
//  var pn_debug_button = div_init(-1,'200px','30px','button','debug');
//  var pn_debug_button = cnst.init('left:200px:bottom:30px:button:debug:Show');
//  var pn_debug_out    = div_init(2,'200px','50px','txt','debug_out');
//  var pn_debug_out    = cnst.init('left:200px:bottom:50px:txt:debug_out');
  function make_debug_obj(){ // pn_debug_button){
//    pn_debug_button.addEventListener('click', debug, false);
    function debug(e){
      console.log('debug');
      if (site.components.postform_submit!=null) site.components.postform_submit.disabled = false;
//      pn_debug_out.textContent += 'debug';
//      if (post_form_obj) post_form_obj.debug();
//      worker.port.postMessage("Scott");
//      worker.port.postMessage(JSON.stringify(['SET','test2','Scott']));
//      worker.port.postMessage(JSON.stringify(['GET','test2']));
//      var str = timer_obj.timer().timer().req.responseText;
//      console.log(str);
//      worker.port.postMessage(JSON.stringify(['ECHO','ON']));
//      brwsr.sw_cache.setItem(site.nickname+site.board+'0',JSON.stringify([cnst.get_time(), str]));

    }
    return debug;
  }

  // can't use sw_cache in 8chan because of 'Content Security Policy' header
  brwsr.sw_cache = (brwsr.sw_cache && window.SharedWorker && (pref.info_server || pref.info_client))? (function(){
// working code.
//    script = 'self.onmessage = function(e){self.postMessage(e.data);};'
//    var blob = new Blob([script], {type: 'text/javascript'});
//    var blobURL = URL.createObjectURL(blob);
//    var echoWorker = new unsafeWindow.Worker(blobURL);
//    URL.revokeObjectURL(blobURL);
//    echoWorker.onmessage = function (oEvent) {
//      console.log("Worker said : " + oEvent.data);
//    };
//    echoWorker.postMessage("ali");

// http://www.sitepoint.com/javascript-shared-web-workers-html5/
    if (!localStorage) return null;
    var worker = null;
    var sw_alive = false;
    var key_ls = pref.script_prefix+'.bs'; // backing store
    var url = localStorage[key_ls]; // should use cookie instead of localStorage.
    if (url) {
      if (!prep_sw(url)) prep_sw();
    } else prep_sw();
    function prep_sw(blobURL) {
      if (!blobURL) {
        var script = '\
          var ports = [];\
          var store = {};\
          var connections = 0;\
          self.addEventListener("connect", function(e){\
            var port = e.ports[0];\
            var no = connections++;\
            var func = function(e){msg_parser(e,port,no)};\
            port.addEventListener("message", func, false);\
            port.start();\
            port.postMessage(JSON.stringify(["INFO","Connected: #" + no]));\
            ports.push({port:port, func:func, echo:true, subscribe:false});\
          }, false);\
          function msg_parser(e,port,no){\
            if (ports[no].echo) port.postMessage(e.data);\
            var fields = JSON.parse(e.data);\
            if (fields[0]=="ECHO") ports[no].echo = fields[1];\
            else if (fields[0]=="GET") port.postMessage(JSON.stringify(["ACK",fields[1],store[fields[1]]]));\
            else if (fields[0]=="CLOSE") {\
              port.removeEventListener("message", ports[no].func, false);\
              ports[no] = null;\
            }\
/*            else if (fields[0]=="SET") store[fields[1]]=fields[2];*/\
            else if (fields[0]=="SET") {\
              var old_val = store[fields[1]];\
              if (old_val!=fields[2]) {\
                var msg = JSON.stringify(["EVENT",fields[1]]);\
                for (var i=0;i<ports.length;i++)\
                  if (i==no) store[fields[1]]=fields[2];\
                  else if (ports[i] && ports[i].subscribe) ports[i].port.postMessage(msg);\
              }\
            } else if (fields[0]=="STAT") stat_post(port,"STAT_ACK",fields[1]);'+
           'else if (fields[0]=="SUBSCRIBE") ports[no].subscribe = fields[1];'+
         '}'+
         'var gc = setInterval(gc_func, 600000);'+
         'function gc_func(){'+
           'var date = Date.now() - 3600000;'+
           'for (var i in store) if (store[i].date < date) delete store[i];'+
//           'stat_post(ports[0].port,"STAT_REP",false);'+
//           'for (var i in store) ports[0].port.postMessage(JSON.stringify([i,store[i].date,store[i].date-date]));'+
         '}'+
         'function stat_post(port,str,dump){'+
           'var live = 0;'+
           'for (var i=0;i<ports.length;i++) if (ports[i]) live++;'+
//           'var count = 0;'+
//           'for (var i in store) count++;'+
//           'var msg = JSON.stringify([str,"Connected: "+live+"/"+connections+", Stored: "+count]);'+
           'var msg = JSON.stringify([str,"Connected: "+live+"/"+connections+", Stored: "+Object.keys(store).length]);'+
           'port.postMessage(msg);'+
           'var count = 0;'+
           'if (dump) for (var i in store) port.postMessage(JSON.stringify(["DUMP_ACK",(count++)+": "+i+", "+JSON.stringify(store[i])]));'+
         '}\
        ';
        var blob = new Blob([script], {type: 'text/javascript'});
        blobURL = URL.createObjectURL(blob);
        localStorage[key_ls] = blobURL;
      }
      try {
        worker = new unsafeWindow.SharedWorker(blobURL); // FF throws exception when blobURL is invalid. But chorome doesn't throw.
//        worker = new unsafeWindow.SharedWorker('blob:https%3A//krautchan.net/87996484-6a43-44c8-ab11-efe455fd2b2b');
//        URL.revokeObjectURL(blobURL); // Chrome need several secondss to invoke. If you place this here, it doesn't work on chrome.
        worker.port.addEventListener('message', sw_out, false);
//        worker.port.addEventListener('message', sw_get, false);
        worker.port.addEventListener('message', sw_tryget, false);
        worker.port.start();
//        worker.port.postMessage("Alyssa");
//        worker.port.postMessage(JSON.stringify(['ECHO','Alyssa']));
//        worker.port.postMessage(JSON.stringify(['SET','test0','val_0']));
//        worker.port.postMessage(JSON.stringify(['SET','test1','val_1']));
//        worker.port.postMessage(JSON.stringify(['GET','test0']));
//        worker.port.postMessage(JSON.stringify(['GET','test1']));
//        console.log(url);
        return true;
      } catch(e) {
//        console.log('catch error at making worker');
//        prep_sw(undefined); // FF with 'Content Security Policy' header will make infinite loop.
        return false;
      }
    }
    function sw_out(e){
//      alert('Worker said : ' + e.data);
//      console.log('Worker said : ' + e.data);
      console.log(new Date().toLocaleTimeString() + ', Worker said : ' + e.data.substr(0,120));
      if (!sw_alive) {
        worker.port.postMessage(JSON.stringify(['STAT']));
//        worker.port.postMessage(JSON.stringify(['STAT',true]));
        if (!pref.debug_mode['0']) worker.port.postMessage(JSON.stringify(['ECHO',false]));
//        worker.port.postMessage(JSON.stringify(['ECHO',true]));
//        worker.port.postMessage(JSON.stringify(['ECHO',false]));
        window.addEventListener('beforeunload',
          function(){
            worker.port.postMessage(JSON.stringify(['CLOSE']));
            worker.port.close();
          }, false);
      }
      sw_alive = true;
      if (!pref.debug_mode['0']) worker.port.removeEventListener('message', sw_out, false);
    }
    if (!brwsr.ff && worker) setTimeout(function(){
//      console.log('alive = '+ sw_alive);
      if (url!==null && !sw_alive) {
        worker.port.removeEventListener('message', sw_out, false);
        worker.port.close();
        if (!prep_sw()) brwsr.sw_cache = null; // kill myself
      }
    },5000);
//    var store = [];
//    function sw_get(e){
//      var fields = JSON.parse(e.data);
//      if (fields[0]=="ACK") store[fields[1]]=fields[2];
//    }
    var callbacks = [];
    var tryget_ids = [];
    var fields = null; // static object for scan.
    function sw_tryget(e){
      fields = JSON.parse(e.data);
      if (fields[0]=="ACK") {
        var key = fields[1];
        clearTimeout(tryget_ids[key]);
        if (callbacks[key]) {
          callbacks[key][0](key,fields[2],callbacks[key][1]); // callback with value.
          delete callbacks[key];
        }
      } else if (fields[0]=="EVENT") {
        if (pref.info_client && fields[1].search(info_raw)!=-1 && fields[1].substr(info_raw.length,1)=='p') {
          var page_no = fields[1].substr(info_raw.length+1);
          var timer = (timer_obj)? timer_obj.timer() : null;
          if (timer!=null) {
//            var value = JSON.parse(fields[2]);
//            timer.page_check4(value[0], page_no, new DOMParser().parseFromString(value[1],'text/html'));
            brwsr.sw_cache.trygetItem(fields[1],timer.page_check5);
          }
        }
        if (pref.info_client && catalog_obj && catalog_obj.catalog_func()!=null && pref.catalog_snoop_refresh)
          catalog_obj.catalog_func().catalog_insert(fields[1]);
      }
      fields = null;
    }
    function tryget_timeout(key){ // timeout is sequential always.
      callbacks[key][0](key,null,callbacks[key][1]); // callback with null.
      delete callbacks[key];
    }
    return (!worker)? null : {
      setItem: function(key,value){worker.port.postMessage(JSON.stringify(['SET',key,value]));},
//      getItem: function(key){
//        worker.port.postMessage(JSON.stringify(['GET',key]));
//        while (store[key]==undefined) sleep(0);
//        var retval = store[key];
//        delete store[key];
//        return retval;
//      },
      trygetItem: function(key,callback,args){
        callbacks[key] = [callback,args];
        tryget_ids[key] = setTimeout(function(){tryget_timeout(key);},2000); // timeout 2sec
        worker.port.postMessage(JSON.stringify(['GET',key]));
      },
      subscribe: function(sub){worker.port.postMessage(JSON.stringify(['SUBSCRIBE',sub]));},
      addEventListener: function(){
      }
    }
  })() : null;

  if (!site0.isStep) (function(){
    var key = 'CatChan_archive_'+window.name;
    var func_invoke = (pref.catalog.embed_page && site.whereami==='page' && sessionStorage && sessionStorage[key])? function(){
      catalog_obj.show_hide();
      setTimeout(function(){
        var data = JSON.parse(sessionStorage[key]);
        if (sessionStorage[key+'M']) data[2] = function(){
          send_message_emu = JSON.parse(sessionStorage[key+'M']);
          receive_message_emu(window.name);
          if (pref.archive.IDB.redirect_404_CSP) {
            var str = document.head.innerHTML + document.body.innerHTML;
            var blob = new Blob([str],{type:'text/html'});
            var url = URL.createObjectURL(blob);
            window.open(url,window.name+'B');
          }
        };
        send_message_emu = [data];
        receive_message_emu(window.name);
      },100);
    } : catalog_obj && catalog_obj.show_hide;
    if (site.whereami==='404' && pref.archive.IDB.redirect_404) {
      if (sessionStorage) setTimeout(function(){
        var key_a = key+'A';
        if (!sessionStorage[key_a]) {
          var dbt = comf.fullname2dbt(window.name);
          sessionStorage[key_a] = JSON.stringify(['ARCHIVER',['SUB_INIT', {IDB:true, domain:dbt[0], board:dbt[1], no:dbt[2]}]]);
          if (received_messages_404) sessionStorage[key_a+'M'] = JSON.stringify(received_messages_404); // this requires message pump(setTimeout)
          var url = site2[site.nickname].make_url4([site.nickname, site.board, '0','page_html'])[0];
          window.name = window.name+'A';
//          setTimeout(function(){window.open(url,'_self');},5000); // for debug
          window.open(url,'_self');
        } else delete sessionStorage[key_a];
      },5000);
    } else if ((pref.catalog.embed && site.whereami==='catalog') || (pref.catalog.embed_page && site.whereami==='page') || (pref.thread.embed && site.whereami==='thread') || (pref.catalog.embed_archive && site.whereami==='archive')) { //patch
      if (catalog_obj) {
        if (site2[site.nickname].catalog_native_prep_wait_loop) site2[site.nickname].catalog_native_prep_wait_loop(site.whereami, func_invoke);
        else if (pref.patch.delayed_invoke.use) setTimeout(func_invoke, pref.patch.delayed_invoke.sec*1000);
        else func_invoke();
      }
    }
    setTimeout(thread_reader_init,0); // invoke after catalog to reduce times of making site.myself
    setTimeout(uip_tracker_init,0);
  })();
// console debug commands for SharedWorker.
//
// var url = localStorage['CatChan.backing_store'];
// var worker = new window.SharedWorker(url);
// worker.port.onmessage = function(e){console.log(e.data.substr(0.120));};
// worker.port.start();
// worker.port.postMessage(JSON.stringify(['STAT']));


//  function test_sw_cache(){
//    sw_cache.setItem('/int/','aaa');
//    sw_cache.trygetItem('/int/',sw_dummy);
//    function sw_dummy(key,val){
//      console.log(key,val);
//    }
//  }
//  setTimeout(test_sw_cache,10000);

// OPTIONS FROM HERE
  options.func0_prep = function(pn,tb, ppn){ // schedule poster.
    var tb_0 = cnst.add_to_tb(tb,cnst.icons.button_settings());
//    tb_0.childNodes[0].onchange = function(){cnst.show_hide(pn);};
    pn.innerHTML = '<div style="float:left"><input type="checkbox">autobumper</div><div style="float:right"><input type="button" value="Schedule"><select name="time_sel"><option>at</option><option>later</option></select><input type="time" name="time" value="08:41"></div><div style="float:right"></div>';
    pn.childNodes[1].childNodes[0].addEventListener('click', schedule_post, false);
//// working code for relative time.
////    var cd_id = null;
////    var cd_id_m = null;
////    var cd_id_30s = null;
////    var bg_back     = pn.parentNode.style.background;
//////    var bg_back_org = bg_back;
////    var time_to_post;
////    function schedule_post(){
////      if (cd_id==null && cd_id_30s==null) {
//////        pn.childNodes[1].childNodes[0].value = 'Cancel';
////        var sel_id = pn.childNodes[1].childNodes[1].selectedIndex;
////        var time_val = pn.childNodes[1].childNodes[2].value;
////        
////        time_to_post = parseInt(time_val.substr(0,2),10)*3600 + parseInt(time_val.substr(3,2),10)*60;
////        if (sel_id==0) {
////          var time_str = cnst.get_time();
////          time_to_post -= parseInt(time_str.substr(0,2),10)*3600 + parseInt(time_str.substr(3,2),10)*60 + parseInt(time_str.substr(6,2),10);
////        }
////        if (time_to_post<=0) time_to_post += 86400;
////        cd_id = setTimeout(countdown_post_30s,(time_to_post-30)*1000); // NOT ACCURATE. slip 70s for 5h.
////        countdown_m(time_to_post%60);
////        set_background_and_button_value();
//////        pn.parentNode.style.background = '#f5ecf9';
////      } else {
////        if (cd_id    !=null) {clearTimeout(cd_id); cd_id = null;}
////        if (cd_id_m  !=null) {clearTimeout(cd_id_m);cd_id_m = null;}
////        if (cd_id_30s!=null) cd_id_30s = cd_id_30s();
//////        pn.parentNode.style.background = bg_back;
////        cd_txt.innerHTML = '';
//////        pn.childNodes[1].childNodes[0].value = 'Schedule';
////        set_background_and_button_value();
////      }
////    }
////    var cd_txt = pn.childNodes[2];
////    function countdown_m(dec){
////      var str = '';
////      if (time_to_post%60!=0) str = time_to_post%60 + 's';
////      if (time_to_post>=60)   str = Math.round((time_to_post%3600)/60) + 'm' + str;
////      if (time_to_post>=3600) str = Math.round(time_to_post/3600) + 'h' + str;
////      cd_txt.innerHTML = str;
////      time_to_post -= dec;
////      cd_id_m = setTimeout(function(){countdown_m(60);},dec*1000);
////    }
////    function countdown_post_30s(){
////      cd_id_30s = countdown_post(30);
////      clearInterval(cd_id_m);
////      cd_id = null;
////      cd_id_m = null;
////    }

    var cd_id = null;
    var cd_id_s = null;
    var bg_back     = pn.parentNode.style.background;
    var time_at_post;
    function schedule_post(){
      if (cd_id==null && cd_id_s==null) {
        var sel_id = pn.childNodes[1].childNodes[1].selectedIndex;
        var time_val = pn.childNodes[1].childNodes[2].value;
        var hour = parseInt(time_val.substr(0,time_val.indexOf(':')),10);
        var min  = parseInt(time_val.substr(time_val.indexOf(':')+1),10);
        var time_now = Date.now();
        if (sel_id==0) {
          time_at_post = new Date().setHours(hour,min,0);
          if (time_at_post<time_now) time_at_post+=86400*1000;
        } else time_at_post = time_now + (hour*3600+min*60)*1000;
        countdown_m();
      } else {
        if (cd_id  !=null) {clearTimeout(cd_id);cd_id=null;}
        if (cd_id_s!=null) {clearInterval(cd_id_s);cd_id_s=null;}
        cd_txt.innerHTML = '';
      }
      set_background_and_button_value(false);
    }
    var cd_txt = pn.childNodes[2];
    function countdown_m(){
      var time_now = Date.now();
      var time_till_post = time_at_post-time_now;
      if (time_till_post%60000>=1000 && time_till_post>60000) cd_id = setTimeout(countdown_m,time_till_post%60000);
      else if (time_till_post<90000) cd_id = setTimeout(function(){cd_id_s=countdown_post(30);cd_id=null;},time_till_post-30*1000);
      else cd_id = setTimeout(countdown_m,(time_till_post+30000)%60000+30000);
      var str = '';
//      if (time_till_post %60000) str = (time_till_post%60000)/1000 + 's'; // debug
      if (time_till_post %60000>=1000 && time_till_post%60000<=59499) str = Math.round((time_till_post%60000)/1000) + 's';
      if (time_till_post>=59500)   str = Math.floor(((time_till_post+500)%3600000)/60000) + 'm' + str;
      if (time_till_post>=3599500) str = Math.floor( (time_till_post+500)/3600000) + 'h' + str;
      cd_txt.innerHTML = str;
    }

    function countdown_post(sec){
      cd_txt.innerHTML = sec + 's';
      set_background_and_button_value(true);
      return setInterval(countdown_s, 1000);

      function countdown_s(){
        sec -= 1;
        cd_txt.innerHTML = sec + 's';
        if (sec==0) {
//          alert('post at '+ Date());
          var evt = document.createEvent('MouseEvents');
          evt.initUIEvent('click', false, true, window, 1);
          site.components.postform_submit.dispatchEvent(evt);
          schedule_post(); // cancel procedure
        }
      }
    }

    var autobumper = pn.childNodes[0].childNodes[0];
    autobumper.onchange = function(){set_background_and_button_value(false);};
    function set_background_and_button_value(cd_s){
      ppn.style.background = (cd_s)? '#ffc0cb' : ((cd_id!=null)? '#f5ecf9' : ((autobumper.checked)? '#e5f4f9' : bg_back));
      var value = (cd_s || cd_id!=null)? 'Cancel' : 'Schedule';
      pn.childNodes[1].childNodes[0].value = value;
    }
    var timer_deadtime = null;
    options.func0_exe = function(page){
      var posts_itt = document.getElementsByClassName('postreply').length - document.getElementsByClassName('de-post-deleted').length;
      if (parseFloat(page)>=site.max_page-1 && autobumper.checked && timer_deadtime==null && posts_itt < site.autosage) {
//      if (parseFloat(page)>=1 && autobumper.checked && timer_deadtime==null) { // for debug }
        cd_id_s = countdown_post(30);
        timer_deadtime = setTimeout(function(){timer_deadtime=null;},3600*1000); // 1h
      }
    }
  }
// NEXT TIME PASSWORD fDEPJ9Uo
// OPTIONS TO HERE

})();