Google掃除機(仮称)

Googleの検索結果に出て欲しくないページを、条件を指定して非表示にします。

当前为 2016-12-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @author         たかだか。(TakaDaka.)
// @name           Google掃除機(仮称)
// @namespace      https://twitter.com/djtkdk_086969
// @description    Googleの検索結果に出て欲しくないページを、条件を指定して非表示にします。
// @require        https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js
// @include        *://www.google.*/
// @include        *://www.google.*/?*
// @include        *://www.google.*/search*
// @include        *://www.google.*/webhp?*
// @exclude        *tbm=shop*
// @exclude        *tbm=vid*
// @version        1.3.0.160
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_deleteValue
// @grant          GM_addStyle
// @grant          GM_registerMenuCommand
// @license        GPL v3; http://www.gnu.org/copyleft/gpl.html
// @homepage       https://twitter.com/djtkdk_086969
// @compatible     firefox
// @compatible     chrome
// ==/UserScript==

/* Message Catalog (for translation) */
var cat = {
    "ja": {
        "langName": {
            "en": "Japanese",
            "ja": "日本語"
        },
        "abbrev": {
            "table": {
                "target": "対象",
                "type": "検索方法",
                "action": "動作",
                "criteria": "条件文字列",
                "contentType": "種類",
                "matchedString": "合致した文字列"
            },
            "target": {
                "url": "URL",
                "description": "説明",
                "title": "題名",
                "suggest": "関連"
            },
            "type": {
                "domain": "ドメイン",
                "str": "文字列",
                "str_head": "頭文字列",
                "word": "ワード",
                "regexp": "正規表現",
            },
            "action": {
                "hide_absolutely": "禁止",
                "hide": "隠す",
                "hide_description_warn": "⚠縮",
                "warn": "⚠",
                "hide_description": "縮小",
                "info": "通知",
                "allow": "許可"
            }
        },
        "full": {
            "target": {
                "url": "URL",
                "description": "説明文",
                "title": "タイトル",
                "suggest": "サジェスト"
            },
            "type": {
                "domain": "ドメイン",
                "str": "文字列",
                "str_head": "文字列(先頭一致)",
                "word": "ワード",
                "regexp": "正規表現",
            },
            "action": {
                "hide_absolutely": "完全に非表示",
                "hide": "非表示",
                "hide_description_warn": "警告+説明文非表示",
                "warn": "警告",
                "hide_description": "通知+説明文非表示",
                "info": "通知",
                "allow": "許可"
            },
            "results": {
                "page": "ページ",
                "missing": "検索語句無視",
                "img": "画像",
                "suggest": "関連語句",
                "suggest_excl": "関連(-除外)",
                "autocomplete": "検索語句予測"
            },
            "msg": {
                "addLast": "末尾に挿入",
                "addNextToSelection": "選択されたルールの次に挿入",
                "overwrite": "上書",
                "noChange": "変更しない",
                "rules": "個のルール",
                "rules_selected": "個選択済",
                "enabled": "有効",
                "disabled": "無効",
                "hidAbsolutely": "「完全に非表示」動作のため表示できません",
                "hidAbsolutelyB": "表示不可",
                "defaultRuleset": "既定のルールセット",
                "GscConfigMenu": "Google掃除機 設定",
                "close": "閉じる",
                "ruleset": "ルールセット",
                "rulesetEdit": "ルールセットの編集",
                "selectAll": "全選択",
                "unselectAll": "全解除",
                "toggleSelection": "選択の切替",
                "moveSelected": "選択されたルールを移動",
                "deleteSelected": "選択されたルールを削除",
                "target": "対象",
                "type": "検索方法",
                "action": "動作",
                "criteria": "条件文字列",
                "level": "レベル",
                "comment": "コメント",
                "manageRuleset": "ルールセットの管理",
                "friendlyNameForCurrentRuleset": "現在のルールセットの表示名",
                "friendlyName": "表示名",
                "deleteCurrentRuleset": "現在のルールセットを削除",
                "key": "キー",
                "addRuleset": "新規ルールセット追加",
                "importFromFile": "ファイルからインポートして現在のルールセットに追加",
                "exportSelected": "選択範囲をエクスポート",
                "urlList": "URLリスト(不完全)",
                "activityLog": "活動ログ",
                "clear": "クリア",
                "pointForDetails": '<span style="background-color: silver;">…</span> をポイントすると詳細が表示されます。',
                "indicateOverride": '<span class="gso_log_overridden">この表示</span>は他のルールにより動作が上書きされたことを表します。<br>',
                "configMisc": "その他の設定",
                "configLang": "言語",
                "configMessageLocation": "メッセージの場所",
                "configQuickBlock": "検索結果にクイックブロックボタンを表示",
                "configCheckForImage": "画像検索のチェック",
                "configRSNameWithComment": "プレースホルダにコメントを表示する",
                "configRSNameWithCommentB": "「#」で始まるコメントはこの設定を有効にしても表示されません",
                "configFixMissing": "検索語句無視対策機能を有効にする",
                "configHideMoshikashite": "2ページ目以降「もしかして:」を隠す",
                "configForceKWExcl": "サジェストに「マイナス検索」を適用",
                "configAlwaysLog": "合致したルールが存在しなくてもチェックした項目を全て記録する(デバッグ用)",
                "configFloat": "メッセージ・設定画面をスクロールに追従させる",
                "configAnimation": "アニメーション",
                "configMessageLocationPage": "ページ左上",
                "configMessageLocationConfig": "設定画面",
                "backupRestoreInit": "バックアップ/復元/初期化",
                "backupToFile": "ファイルにバックアップ",
                "restoreAll": "全設定をファイルから復元",
                "init": "全設定を初期化(元に戻せません!)",
                "initButton": "初期化",
                "initConfirm": "後悔しませんね?",
                "initialized": "全ての設定を初期化しました。",
                "about": "バージョン情報",
                "aboutAuthor": "作者",
                "aboutLicense": "ライセンス",
                "aboutJQ": '本スクリプトは<a href="https://jquery.com/" target="_blank">jQuery 2.2.0</a>を利用しています。<br>jQueryはMIT Licenseのもとで提供されています。',
                "saveChange": "変更を保存",
                "discardChange": "変更を破棄",
                "changeNotSaved": "[変更を保存]をクリックするまで設定は保存されません",
                "reloadToTakeEffect": "設定を反映させるには再読み込みを行ってください",
                "lastRulesetCannotBeDeleted": "最後のルールセットは削除できません",
                "untitled": "(無題)",
                "invalidJSON": "有効なJSONではないため、インポートに失敗しました。詳細はコンソールを参照してください。",
                "invalidConfig": "不正な設定ファイルです。詳細はコンソールを参照してください。",
                "ignoredKeywords": "無視されたキーワード",
                "searchAllIncluded": "全部含めて再検索",
                "block": "ブロック",
                "domain": "ドメイン",
                "excludedKeyword": "マイナス検索による除外",
                "ctlmsgSERP": "検索結果",
                "ctlmsgIMG": "画像",
                "ctlmsgKW": "関連語句",
                "ctlmsgMIS": "検索語句無視!",
                "ctlmsgVerbatim": "完全一致で再検索",
                "ctlmsgGood": "検索結果に問題なし",
                "ctlmsgBad": "検索結果を処理済",
                "ctlmsgBadB": "(クリックで切替)"
            }
        }
    },
    "en": {
        "langName": {
            "en": "English",
            "ja": "英語"
        },
        "abbrev": {
            "table": {
                "target": "Tgt.",
                "type": "M.Type",
                "action": "Action",
                "criteria": "Criteria",
                "contentType": "Type",
                "matchedString": "Matched String"
            },
            "target": {
                "url": "URL",
                "description": "Desc.",
                "title": "Title",
                "suggest": "Rel."
            },
            "type": {
                "domain": "Domain",
                "str": "String",
                "str_head": "Head",
                "word": "Word",
                "regexp": "RegExp",
            },
            "action": {
                "hide_absolutely": "XX",
                "hide": "X",
                "hide_description_warn": "!!",
                "warn": "!",
                "hide_description": "Shr.",
                "info": "Inf.",
                "allow": "OK"
            }
        },
        "full": {
            "target": {
                "url": "URL",
                "description": "Description",
                "title": "Title",
                "suggest": "Suggestion"
            },
            "type": {
                "domain": "Domain",
                "str": "String",
                "str_head": "String (Head)",
                "word": "Word",
                "regexp": "Regular Expression",
            },
            "action": {
                "hide_absolutely": "Hide Absolutely",
                "hide": "Hide",
                "hide_description_warn": "Warn / Hide Description",
                "warn": "Warn",
                "hide_description": "Info / Hide Description",
                "info": "Info",
                "allow": "Allow (Do nothing)"
            },
            "results": {
                "page": "Page",
                "missing": "Ignored KWs",
                "img": "Image",
                "suggest": "Suggestion",
                "suggest_excl": "Forced -KW",
                "autocomplete": "Autocomplete"
            },
            "msg": {
                "addLast": "Insert to the last",
                "addNextToSelection": "Insert next to the selected rule",
                "overwrite": "Overwrite",
                "noChange": "No change",
                "rules": "rule(s)",
                "rules_selected": "selected",
                "enabled": "Enabled",
                "disabled": "Disabled",
                "hidAbsolutely": "&quot;Hide Absolutely&quot; action is preventing results from appearing.",
                "hidAbsolutelyB": "Cannot be displayed",
                "defaultRuleset": "Default Ruleset",
                "GscConfigMenu": "Google Search Cleaner configuration",
                "close": "Close",
                "ruleset": "Ruleset",
                "rulesetEdit": "Edit Rulesets",
                "selectAll": "Select all",
                "unselectAll": "Unselect all",
                "toggleSelection": "Toggle selection",
                "moveSelected": "Move selected rules",
                "deleteSelected": "Delete selected rules",
                "target": "Target",
                "type": "Match Type",
                "action": "Action",
                "criteria": "Criteria",
                "level": "Level",
                "comment": "Comment",
                "manageRuleset": "Manage Rulesets",
                "friendlyNameForCurrentRuleset": "Friendly name for current ruleset",
                "friendlyName": "Friendly name",
                "deleteCurrentRuleset": "Delete current ruleset",
                "key": "Key",
                "addRuleset": "Add a new ruleset",
                "importFromFile": "Import rules from file and add them to the current ruleset",
                "exportSelected": "Export selection",
                "urlList": "URL List (Incomplete)",
                "activityLog": "Activity Log",
                "clear": "Clear",
                "pointForDetails": 'Point <span style="background-color: silver;">…</span> to show details.',
                "indicateOverride": '<span class="gso_log_overridden">This style</span> indicates that the action of the rule has been overridden by another rule.<br>',
                "configMisc": "Miscellaneous Configurations",
                "configLang": "Language",
                "configMessageLocation": "Message location",
                "configQuickBlock": "Show Quick Block buttons on the SERP",
                "configCheckForImage": "Check for image search",
                "configRSNameWithComment": "Show comments on the placeholder",
                "configRSNameWithCommentB": "The comments starting with '#' will not be shown regardless of this configuration.",
                "configFixMissing": "Enable Missing Keywords Indication",
                "configHideMoshikashite": "Hide 'Did you mean' on the second (or lower) SERP page",
                "configForceKWExcl": "Force keyword exclusion ('Minus Search') on Keyword Suggestion",
                "configAlwaysLog": "Always log checked entries (for debugging)",
                "configFloat": "Floating Message Box",
                "configAnimation": "Animation",
                "configMessageLocationPage": "Top-Left of the page",
                "configMessageLocationConfig": "Configuration Window",
                "backupRestoreInit": "Backup / Restore / Factory Reset",
                "backupToFile": "Backup to file",
                "restoreAll": "Restore all configuration from file",
                "init": "Reset all configuration (Cannot be undone!)",
                "initButton": "Reset",
                "initConfirm": "Are you sure?",
                "initialized": "Initialized all configuration.",
                "about": "About",
                "aboutAuthor": "Author",
                "aboutLicense": "License",
                "aboutJQ": 'This script uses <a href="https://jquery.com/" target="_blank">jQuery 2.2.0</a>.<br>jQuery is provided under MIT License.',
                "saveChange": "Save changes",
                "discardChange": "Discard changes",
                "changeNotSaved": "The changes are not saved unless you click [Save changes].",
                "reloadToTakeEffect": "Please reload the page to take effects.",
                "lastRulesetCannotBeDeleted": "The last ruleset cannot be deleted.",
                "untitled": "(Untitled)",
                "invalidJSON": "Import failed because the given file is not a valid JSON. See the console for details.",
                "invalidConfig": "Invalid configuration file. See the console for details.",
                "ignoredKeywords": "Ignored Keywords",
                "searchAllIncluded": "Search again with all keywords included",
                "block": "Block",
                "domain": "Domain",
                "excludedKeyword": "Minus Search Exclusion",
                "ctlmsgSERP": "Results",
                "ctlmsgIMG": "Images",
                "ctlmsgKW": "Keywords",
                "ctlmsgMIS": "Ignored Keywords!",
                "ctlmsgVerbatim": "Search Verbatim",
                "ctlmsgGood": "No problem",
                "ctlmsgBad": "Processed the results",
                "ctlmsgBadB": "(Click to toggle)"
            }
        }
    }

};

var action_priority = {
    "hide_absolutely": 6,
    "hide": 5,
    "hide_description_warn": 4,
    "warn": 2,
    "hide_description": 3,
    "info": 1,
    "allow": 0,
    "undef": -1
};

/* 現在の設定 */
var config = null;

/* デフォルトの設定 */
var config_default = {
    "rulesets": {
        "default": {
            "name": "既定のルールセット",
            "enabled": true,
            "rules": [
            ]},
    },
    'config': {
        'gso_lang': 'ja',
        'message_location': 'page',
        'quick_block': false,
        'check_for_image': true,
        'ruleset_name_with_comment': false,
        'fix_missing': true,
        'hide_moshikashite': true,
        'force_keyword_exclusion_on_suggestion': false,
        'always_log_checked_entries': false,
        'float': true,
        'animation': true,
        'verbose': false,
        'version': GM_info.script.version
    }
};

/* Utility functions */

function escapeRegexp(string) {
    return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, "\\$1");
}
function chk_str(target, ref, method) {
    /*
      条件に合致した場合、合致した文字列を含む配列を返す。
      そうでない場合は null を返す。
      target: 対象の文字列
      ref:    検索文字列
      method: 検索方法
    */
    var r = null;
    if(method == "domain") {
        if(/^(https?|ftp):\/\/\S/.test(target)) { //正しいURLである
            var target_subdomain = target
                .split("/")[2]
                .split(":")[0]
                .split(".")
                .reverse()
                .filter(function(element) {return (element !== "");});
            var ref_subdomain = ref.split(".").reverse()
                .filter(function(element) {return (element !== "");});
            if (ref_subdomain.every(
                function(element, index, array) {
                    return array[index] == target_subdomain[index];
                })) {
                return [ref];
            } else {
                return null;
            }
        } else {
            return null;
        }
    } else if(method == "regexp") {
        r = new RegExp(ref, "i");
        return r.exec(target);
    } else if(method == "str") {
        r = new RegExp(escapeRegexp(ref), "i");
        return r.exec(target);
    } else if(method == "str_head") {
        r = new RegExp("^" + escapeRegexp(ref), "i");
        return r.exec(target);
    } else if(method == "word") {
        var words = ref.split(/\s+/);

        if (words.every(function(element, index, array) {
            if(/[\u3040-\u309f\u30a0-\u30ff\u4e00-\u9fff]/.test(element)) {
                /* キーワードに日本語(ひらがなカタカナ漢字)が含まれている 
                   →単純に部分一致検索する */
                r = new RegExp(escapeRegexp(element), "i");
                return r.test(target);
                
            } else {
                /* キーワードに日本語を含まない
                   →単語単位で検索(\bSTR\b) */
                r = new RegExp('(\\b|^)' + escapeRegexp(element) + '(\\b|$)', "i");
                return r.test(target);
            }
        })) {
            return words;
        } else {
            return null;
        }
    } else {
        return null;
    }
}

function decodeURI_s(url) {
    /* decodeURI() にデコードできないURIを食わせると
       URIError: malformed URI sequence
       となるので例外処理する */
    var url_decoded;
    try {
        url_decoded = decodeURI(url);
    }
    catch (e) {
        if (e instanceof URIError) {
            url_decoded = url;
        }
    }
    return url_decoded;
}

function check_rule(rule) {
    /* ルールが正常かどうかチェック */
    var target_allow = ["url", "description", "title", "suggest"];
    var type_allow = ["domain", "str", "str_head", "word", "regexp"];
    var action_allow = ["hide_absolutely", "hide", "hide_description_warn", "warn", "hide_description", "info", "allow"];
    
    return (typeof rule == "object" &&
            typeof rule.criteria == "string" &&
            typeof rule.target == "string" &&
            typeof rule.type == "string" &&
            typeof rule.action == "string" &&
            typeof rule.level == "number" &&
            !isNaN(rule.level) &&
            typeof rule.comment == "string" &&
            typeof rule.enabled == "boolean" &&
            target_allow.some(function(e) {
                return e === rule.target;
            }) &&
            type_allow.some(function(e) {
                return e === rule.type;
            }) &&
            action_allow.some(function(e) {
                return e === rule.action;
            })
           );
}

function check_config(config_json) {
    /* オブジェクト config_json を設定が格納されたオブジェクトとして
       正常なデータかどうかチェックする。
       同時に実装済だがconfig_jsonには定義されていない設定値については
       デフォルト値を設定する。
       (config_jsonの内容は変化する) */
    var valid = true;
    valid = (typeof config_json.config == "object" &&
             typeof config_json.rulesets == "object");
    if(valid) {
        Object.keys(config_default.config).forEach(function (k) {
            if (config_json.config[k] === null || config_json.config[k] === undefined) {
                console.log('config_json.' + k + ' defaults to ' + config_default.config[k]);
                config_json.config[k] = config_default.config[k];
            }
        });
        jQuery.each(config_json.rulesets, function(id) {
            if(valid &&
               typeof this.name == "string" &&
               typeof this.enabled == "boolean" &&
               typeof this.rules == "object" &&
               jQuery.isArray(this.rules)){
                if(valid) {
                    jQuery.each(this.rules, function() {
                        if(!check_rule(this)) {
                            valid = false;
                            return false;
                        }
                    });
                }
            }
        });
    }
    return valid;
}

/* Utility functions (for UI) */
function gso_rseditor_update_selection() {
    /* ボタンの有効・無効、各入力フィールドの更新を行う
       (ルールセット内のルールの選択状態を変更したら呼び出すこと) */
    var rule = {};
    var selection_size = $("#gso_ruleset_table table tbody tr.gso_rule_selected").size();
    if(selection_size === 0) {
        $("#gso_rule_add").text(cat[config.config.gso_lang].full.msg.addLast);
        $("#gso_rule_overwrite").prop("disabled", true);
        $("#gso_rule_remove").prop("disabled", true);
        $("#gso_rule_moveup").prop("disabled", true);
        $("#gso_rule_movedown").prop("disabled", true);
        $("#gso_rule_criteria").attr("placeholder", "");
        $("#gso_rule_comment").attr("placeholder", "");
        /* 入力フィールドはそのままにする */
    } else if (selection_size == 1) {
        rule = config.rulesets[$("#gso_ruleset_select").val()]
            .rules[Number($("#gso_ruleset_table table tbody tr.gso_rule_selected:first").attr('data-idx'))];
        $("#gso_rule_add").text(cat[config.config.gso_lang].full.msg.addNextToSelection);
        $("#gso_rule_overwrite").prop("disabled", false);
        $("#gso_rule_remove").prop("disabled", false);
        $("#gso_rule_moveup").prop("disabled", false);
        $("#gso_rule_movedown").prop("disabled", false);

        $("#gso_rule_criteria").attr("placeholder", "");
        $("#gso_rule_comment").attr("placeholder", "");
        $("#gso_rule_target").val(rule.target);
        $("#gso_rule_type").val(rule.type);
        $("#gso_rule_action").val(rule.action);
        $("#gso_rule_level").val(rule.level);
        $("#gso_rule_criteria").val(rule.criteria);
        $("#gso_rule_comment").val(rule.comment);
        $("#gso_rule_enabled").prop("checked", rule.enabled);
    } else {
        $("#gso_rule_add").text(cat[config.config.gso_lang].full.msg.addLast);
        $("#gso_rule_overwrite").prop("disabled", false);
        $("#gso_rule_remove").prop("disabled", false);
        $("#gso_rule_moveup").prop("disabled", true);
        $("#gso_rule_movedown").prop("disabled", true);
        $("#gso_rule_criteria").attr("placeholder", "(" + cat[config.config.gso_lang].full.msg.noChange + ")");
        $("#gso_rule_comment").attr("placeholder", "(" + cat[config.config.gso_lang].full.msg.noChange + ")");


        var data = {};

        $("#gso_ruleset_table table tbody tr.gso_rule_selected").each(function() {
            rule = config.rulesets[$("#gso_ruleset_select").val()].rules[Number($(this).attr('data-idx'))];
            Object.keys(rule).forEach(function (k) {
                if (data[k] === undefined) {
                    data[k] = rule[k];
                } else if (data[k] != rule[k]) {
                    data[k] = "";
                }
            });
        });

        $("#gso_rule_target").val(data.target);
        $("#gso_rule_type").val(data.type);
        $("#gso_rule_action").val(data.action);
        $("#gso_rule_level").val(data.level);
        $("#gso_rule_criteria").val(data.criteria);
        $("#gso_rule_comment").val(data.comment);

    }
    if(selection_size === 0) {
        $("#gso_rule_count").text($("#gso_ruleset_table table tbody tr").size() +
                                  " " + cat[config.config.gso_lang].full.msg.rules);
    } else {
        $("#gso_rule_count").text($("#gso_ruleset_table table tbody tr").size() +
                                  " " + cat[config.config.gso_lang].full.msg.rules +
                                  " (" + selection_size + " " +
                                  cat[config.config.gso_lang].full.msg.rules_selected + ")");
    }
}

function gso_rseditor_update_rslist() {
    /* ルールセットのリストを更新 */
    var id = $("#gso_ruleset_select").val();
    var ruleset = config.rulesets[id];
    $("#gso_ruleset_select option[value='"+ id +"']")
        .text(gso_rseditor_rslist_str(id, ruleset.name, ruleset.enabled));
}

function gso_rseditor_rslist_str(key, name, enabled) {
    return name + ' [' + key + ']' + (enabled ? '' : '[' + cat[config.config.gso_lang].full.msg.disabled + ']');
}

function gso_log_append(type, target, matched, title, url, ruleset, action, action_effective, override) {
    /* ログに表示 */
    var table = $("#gso_log_table table tbody");
    var row_style = 0;
    
    if(table.find("tr").size() >= 1) {
        if(table.find("tr:last").attr("data-last-rule") === undefined) {
            if(table.find("tr:last").hasClass("gso_log_a")){            
                row_style = 0;
            } else {
                row_style = 1;
            }
        } else {
            if(table.find("tr:last").hasClass("gso_log_a")){            
                row_style = 1;
            } else {
                row_style = 0;
            }
        }
    } else {
        row_style = 0;        
    }
    table.append(
        "<tr><td>" + cat[config.config.gso_lang].full.results[type] +
            "</td><td>" +
            "</td><td>" +
            "</td><td>" +
            "</td><td>" +
            "</td><td>" +
            "</td><td>" +
            "</td></tr>");
    if(row_style === 0) {
        table.find("tr:last").addClass("gso_log_a");
    } else {
        table.find("tr:last").addClass("gso_log_b");
    }

    if(override) {
        table.find("tr:last").addClass("gso_log_overridden");
    }
    if(action_effective == "hide_absolutely") {
        if(ruleset !== null) {
            table.find("tr:last td:eq(2)")
                .append("<div style='width: 100%; color: silver;' title='"+
                        cat[config.config.gso_lang].full.msg.hidAbsolutely +"'>" +
                        cat[config.config.gso_lang].full.msg.hidAbsolutelyB + "</div>");
        }
        if(ruleset !== null) {
            table.find("tr:last td:eq(5)")
                .append("<div title='[" + ruleset + "]" + config.rulesets[ruleset].name +
                        "' style='width: 100%; background-color: silver; text-align: center;'>…</div>");
        }
        if(action !== null) {
            table.find("tr:last td:eq(6)")
                .append("&gt;&gt; " + cat[config.config.gso_lang].full.action[action]);
        }
        table.find("tr:last td:eq(4)").remove();
        table.find("tr:last td:eq(3)").remove();
        table.find("tr:last td:eq(2)").attr("colspan", "3");

    } else {
        if(matched !== null) {
            table.find("tr:last td:eq(2)").text(matched);
        }
        if(title !== null) {
            table.find("tr:last td:eq(3)")
                .append("<div title='" + title + "' style='width: 100%; background-color: silver; text-align: center;'>…</div>");
        }
        if(url !== null) {
            table.find("tr:last td:eq(4)")
                .append("<div title='" + decodeURI_s(url) + "' style='width: 100%; background-color: silver; text-align: center;'>…</div>");
        }
        if(ruleset !== null) {
            table.find("tr:last td:eq(5)")
                .append("<div title='[" + ruleset + "]" + config.rulesets[ruleset].name +
                        "' style='width: 100%; background-color: silver; text-align: center;'>…</div>");
        }
        if(action !== null) {
            table.find("tr:last td:eq(6)")
                .append("&gt;&gt; " + cat[config.config.gso_lang].full.action[action]);
        }
    }

    if(target === null) {
        table.find("tr:last td:eq(1)").remove();
        table.find("tr:last td:first").attr("colspan", "2");
    } else {
        table.find("tr:last td:eq(1)").text(cat[config.config.gso_lang].abbrev.target[target]);
    }
}
function gso_log_setBoundary() {
    /*
      ログにおける検索結果の境界を設定
      複数のルールに合致した際などにどのルールがどの検索結果に合致したか
      わかりやすくする
    */
    $("#gso_log_table table tbody").find("tr:last").attr("data-last-rule", "last-rule");
}
function gso_rseditor_toggle() {
    $("#gso_config").toggle();
}

/* GM_setValue / GM_getValue */
function gso_save() {
    /* GM_setValue で現在の設定値(config)を保存する */
    Object.keys(config_default.config).forEach(function (k) {
        GM_setValue(k, config.config[k]);
    });
    GM_setValue("version", GM_info.script.version);

    GM_setValue("rulesets", JSON.stringify(config.rulesets));
    console.log("saved configuration");
    console.log(JSON.parse(GM_getValue("rulesets")));
}

function gso_load() {
    /* GM_getValue で設定を読み込む
       値が設定されていなかったらデフォルト値を読み込む */
    console.log("loading configuration");
    config = {};
    config.config = {};
    config.rulesets = {};
    
    Object.keys(config_default.config).forEach(function (k) {
        config.config[k] = GM_getValue(k, config_default.config[k]);
    });

    config.rulesets = JSON.parse(GM_getValue("rulesets", '{"default":{"name":"既定のルールセット","enabled":true,"rules":[{"action":"hide","comment":"","criteria":"example.com","enabled":false,"level":0,"target":"url","type":"domain"}]}}'));
    console.log("loaded configuration");
    console.log(config);
    
    jQuery.fx.off = !config.config.animation;
}

/* Import / Export */
function gso_import_ruleset(rules, key) {
    if(typeof rules == "object" &&
       jQuery.isArray(rules)){
        var imported_count = 0;
        var malformed_entry = null;
        jQuery.each(rules, function() {
            if(check_rule(this)) {
                imported_count++;
                config.rulesets[key].rules.push(jQuery.extend(true, {}, this));
            } else {
                malformed_entry = this;
            }
        });
        if (imported_count == rules.length) {
            console.log("imported:", imported_count);
        } else if(imported_count == rules.length - 1) {
            console.log("imported:", imported_count);
            console.log("malformed:", JSON.stringify(malformed_entry));
        } else {
            console.log("imported:", imported_count);
            console.log("malformed:", JSON.stringify(malformed_entry));
        }
    } else {
        console.log("import failed (invalid format)");
    }
}


/* Initialization functions */

function gso_config_rseditor_init() {
    $("#gso_ruleset_select option").remove();
    jQuery.each(config.rulesets, function(id) {
        $("#gso_ruleset_select").append(
            '<option value="' + id + '">' +
                gso_rseditor_rslist_str(id, this.name, this.enabled) +
                '</option>');
    });
    
    $("#gso_ruleset_select").change();

    $("#gso_ruleset_remove").prop("disabled", $("#gso_ruleset_select option").size() <= 1);
    $("#gso_rule_enabled").prop("checked", true);
}

function gso_config_init() {
    gso_config_rseditor_init();
    $("#gso_config_misc input").each(function() {
        $(this).prop("checked", config.config[$(this).val()]);
    });
    $("#gso_config_misc select").each(function() {
        $(this).val(config.config[this.name]);
    });
}

(function() {
    console.log("Google Search Cleaner " + GM_info.script.version + " started.");
    gso_load(); /* 設定を読み込む */

    GM_registerMenuCommand(cat[config.config.gso_lang].full.msg.GscConfigMenu, gso_rseditor_toggle);
    GM_addStyle("span.gso_killed_serp_msg { color: silver; margin: 0 0; }");
    GM_addStyle("*.gso_killed_serpimg_warn { display: block; position: absolute; width: 100%; height: 100%; z-index: 100; font-size: 0.60em; top: 0px; left: 0px;}");
    GM_addStyle("*.gso_killed_img_mask_serp {background-color: #ffffff;}");
    GM_addStyle("*.gso_killed_img_mask_isch {background-color: #f1f1f1;}");
    GM_addStyle("span.gso_killed_kw_bad { color: silver; text-decoration: line-through; white-space: nowrap;}");
    GM_addStyle("span.gso_killed_kw_placeholder {border: 1px solid; white-space: nowrap;}");
    GM_addStyle("li.gso_killed_kw_autocomplete { display: none !important;}");
    GM_addStyle("span.gso_killed_url { font-size: 0.60em; text-decoration:line-through;}");
    GM_addStyle("#gso_control { left: 0px; z-index: 999; width: 120px; background-color: white; border: 1px solid black; text-align: center; }");
    GM_addStyle("#gso_results_msg_eff { position: absolute; left: 0px; top: 0px; width: 100%; height: 100%; background-color: pink; display: none; }");
    GM_addStyle("#gso_config { right: 0px; z-index: 999; width: 480px; background-color: white; border: 1px solid black; display: none; -moz-user-select: none; font-size: x-small;}");
    GM_addStyle("*.gso_control_msg {font-size: 0.80em;}");
    GM_addStyle("*.gso_control_buttons {font-size: inherit;}");
    GM_addStyle("*.gso_quick_block {font-size: smaller;}");
    //GM_addStyle("div.gso_killed_count { display: inline-block; padding: 8px; }");
    GM_addStyle("span.gso_ignored_kw { font-weight: bold; }");
    GM_addStyle("*.gso_float { position: fixed; top: 0px; }");
    GM_addStyle("*.gso_control_embedded { position: absolute; top: 60px; }");
    GM_addStyle("*.gso_config_embedded { position: absolute; top: 0px; }");
    GM_addStyle("*.gso_serp_description_info { display: block; background: lightgray; }");
    GM_addStyle("*.gso_serp_description_warning { display: block; color: darkred;}");
    GM_addStyle("tr.gso_rule_selected {background-color: pink;}");
    GM_addStyle("tr.gso_rule_disabled {text-decoration: line-through;}");
    GM_addStyle("tr.gso_log_a {background-color: inherit;}");
    GM_addStyle("tr.gso_log_b {background-color: whitesmoke;}");
    GM_addStyle("*.gso_log_overridden {text-decoration: line-through; color: silver;}");

    var selector_SERP =
        "div.rc:has(h3.r > a)," +
        "li.g:has(a._Dk)," +
        "div.g:has(a._Dk)," +
        "div._lnc div._cnc," +
        "div._lnc div._hnc," +
        "div._lnc div._Xmc," +
        "div.sld:has(h3.r > a.l)," +
        "div._knc:has(h3.r > a.l)," +
        "a._rQb, " +
        "div._lnc > a.top, " +
        "div.dbsr, " +       /* 2016/11仕様変更 */
        "ul._vio > li._sio"; /* 同上 */
    /*
      サイト内検索: div.rc:has(h3.r > a)
      ニューストピック: li.g:has(a._Dk), div.g:has(a._Dk)
      トップニュース(2016/11): (div._NId div._Bfp) div.dbsr
      > a (URL)
      > div._qlp cite._NCp (From)
      トップニュース(2016/11横並び): (div._NId) ul._vio > li._sio
      a (URL)
      p._NRj._ORj.f._xRj > cite (from)
    */

    var selector_IMG = 
        "div.img-brk li.rg_el, " +
        "div.img-brk div.rg_el";
    /*
      画像検索結果(旧): div.img-brk li.rg_el
      画像検索結果: div.img-brk div.rg_el
    */
    
    var selector_IMGLIST =
        "div#isr_mc div.rg_el";

    var selector_KW = "div#trev a, div#brs p._e4b > a";
    /* 関連する検索キーワード */

    var status = {
        "show_serp": false,
        "show_img": false,
        "show_kw": false
    };
    
    function check(url, description, title, keyword, temp_rulesets) {
        /* ルールセットに合致するかどうかチェック
           優先度ごとに最初に合致したルールと内包するルールセットを
           表すオブジェクトの配列を返す。
           そうでなければ 空の配列 を返す。*/
        var query = null;
        var rulesets = null;

        var matched_rules = [];
        var last_level = -1;

        if(temp_rulesets === null) {
            rulesets = config.rulesets;
        } else {
            rulesets = temp_rulesets;
        }

        jQuery.each(rulesets, function(id) {
            /* for each ruleset in rulesets */
            if(this.enabled) { /* this ruleset is enabled */
                jQuery.each(this.rules, function() {
                    /* for each rule in ruleset */
                    if(this.enabled && this.level - last_level <= 1) {
                        if(this.target == "suggest") {
                            query = keyword;
                        } else if(this.target == "url") {
                            query = decodeURI_s(url);
                        } else if(this.target == "title") {
                            query = title;
                        } else if(this.target == "description") {
                            query = description;
                        }
                        if(query !== null) {
                            var matched = chk_str(query, this.criteria, this.type);
                            if (matched !== null) {
                                if(this.level <= last_level) {
                                    matched_rules.push(matched_rules[matched_rules.length-1].filter(function(e) {
                                        return e.rule.level < this.level;
                                    }));
                                }
                                if(last_level < 0) {
                                    matched_rules.push([]);
                                }
                                matched_rules[matched_rules.length-1].push({
                                    "ruleset_id": id,
                                    "rule": this,
                                    "matched": matched.join(" ")
                                });

                                last_level = this.level;
                            }
                        }
                    }
                });
            }
        });
        return matched_rules;
    }
    
    function get_most_significant_rule(matched_rules) {
        /* 複数のルール(のツリー)に合致した場合、最も厳しい処理を適用する */
        var context = null;
        var action = action_priority.undef;
        matched_rules.forEach(function(element) {
            if(action < action_priority[element[element.length - 1].rule.action]) {
                if(context !== null) {
                    delete context.effective;
                }
                context = element[element.length - 1];
                action = action_priority[element[element.length - 1].rule.action];
                context.effective = true;
            }
        });
        return context;
    }

    if($("#gso_config").size() === 0) {
        /* 設定画面 */
        var cfg_elem = $('<form id="gso_config" class="gso_config_embedded" lang="'+
                         config.config.gso_lang + '"></form>');
        cfg_elem.append('<span style="display: block; text-align: right"><button type="button" id="gso_config_close" class="gso_control_buttons" title="' + cat[config.config.gso_lang].full.msg.close + '">×</button></span>');
        
        var fieldset = $('<fieldset></fieldset>');
        fieldset.append('<legend><button type="button" id="gso_ruleset_editor_toggle" class="gso_control_buttons">▲</button>' + cat[config.config.gso_lang].full.msg.rulesetEdit + '</legend>');
        fieldset.append('<div id="gso_ruleset_editor"></div>');
        fieldset.find('#gso_ruleset_editor')
            .append('<label for="gso_ruleset_select">'+ cat[config.config.gso_lang].full.msg.ruleset +':</label>')
            .append('<select id="gso_ruleset_select" name="gso_ruleset_select"></select>')
            .append('<input id="gso_ruleset_enabled" type="checkbox" value="gso_ruleset_enabled">' + cat[config.config.gso_lang].full.msg.enabled + '<hr>')
            .append('<div></div>');
        fieldset.find('#gso_ruleset_editor > div:last')
            .append('<table style="width:440px; border-spacing: 0px 2px;"></table>')
            .append('<div id="gso_ruleset_table" style="height: 100px; width: 100%; overflow-y: scroll; overflow-x: hidden;"></div>')
            .append('<button type="button" id="gso_ruleset_selectAll" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.selectAll + '</button>')
            .append('<button type="button" id="gso_ruleset_unselectAll" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.unselectAll + '</button>')
            .append('<button type="button" id="gso_ruleset_selectToggle" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.toggleSelection + '</button>')
            .append('<span id="gso_rule_count"></span><br>')
            .append(cat[config.config.gso_lang].full.msg.moveSelected)
            .append('<button type="button" id="gso_rule_moveup" class="gso_control_buttons">▲</button>')
            .append('<button type="button" id="gso_rule_movedown" class="gso_control_buttons">▼</button> ')
            .append('<span style="display: block; text-align: right"><button type="button" id="gso_rule_remove" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.deleteSelected + '</button></span>')
            .append('<hr>')
            .append('<div style="width:100%;"></div>')
            .append('<button type="button" id="gso_rule_add" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.addLast + '</button>')
            .append('<button type="button" id="gso_rule_overwrite" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.overwrite + '</button>');
        fieldset.find('#gso_ruleset_editor > div:last > table:first')
            .append('<colgroup>' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 4.5em; min-width: 4.5em;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 2em; min-width: 2em;">' +
                    '<col style="width: 248px;">' +
                    '<col style="width: 1em; min-width: 1em;">' +
                    '</colgroup>')
            .append('<thead><tr style="font-weight: bold; background-color: lightgray;">' +
                    '<td>' + cat[config.config.gso_lang].abbrev.table.target + '</td>' +
                    '<td>' + cat[config.config.gso_lang].abbrev.table.type + '</td>' +
                    '<td>' + cat[config.config.gso_lang].abbrev.table.action + '</td>' +
                    '<td>Lv</td>' +
                    '<td>' + cat[config.config.gso_lang].abbrev.table.criteria + '</td>' +
                    '<td>C</td>' +
                    '</tr></thead>');
        fieldset.find('#gso_ruleset_table')
            .append('<table style="width:440px; border-spacing: 0px 2px;">' +
                    '<colgroup>' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 4.5em; min-width: 4.5em;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 2em; min-width: 2em;">' +
                    '<col style="width: 248px;">' +
                    '<col style="width: 1em; min-width: 1em;">' +
                    '</colgroup>' +
                    '<tbody></tbody>' +
                    '</table>');
        fieldset.find('#gso_ruleset_editor > div:last > div:last')
            .append('<div style="display: inline-block;">' +
                    cat[config.config.gso_lang].full.msg.enabled + '<br>' +
                    '<input id="gso_rule_enabled" type="checkbox" value="gso_rule_enabled">' +
                    '</div>')
            .append('<div style="display: inline-block;">' +
                    cat[config.config.gso_lang].full.msg.target + ':<br>' +
                    '<select id="gso_rule_target" name="gso_rule_target">' +
                    '<option value="url">' + cat[config.config.gso_lang].full.target.url + '</option>' +
                    '<option value="description">' + cat[config.config.gso_lang].full.target.description + '</option>' +
                    '<option value="title">' + cat[config.config.gso_lang].full.target.title + '</option>' +
                    '<option value="suggest">' + cat[config.config.gso_lang].full.target.suggest + '</option>' +
                    '</select>' +
                    '</div>')
            .append('<div style="display: inline-block;">' +
                    cat[config.config.gso_lang].full.msg.type + ':<br>' +
                    '<select id="gso_rule_type" name="gso_rule_type">' +
                    '<option value="domain">' + cat[config.config.gso_lang].full.type.domain + '</option>' +
                    '<option value="str">' + cat[config.config.gso_lang].full.type.str + '</option>' +
                    '<option value="str_head">' + cat[config.config.gso_lang].full.type.str_head + '</option>' +
                    '<option value="word">' + cat[config.config.gso_lang].full.type.word + '</option>' +
                    '<option value="regexp">' + cat[config.config.gso_lang].full.type.regexp + '</option>' +
                    '</select>' +
                    '</div>')
            .append('<div style="display: inline-block;">' +
                    cat[config.config.gso_lang].full.msg.action + ':<br>' +
                    '<select id="gso_rule_action" name="gso_rule_action">' +
                    '<option value="hide_absolutely">' + cat[config.config.gso_lang].full.action.hide_absolutely + '</option>' +
                    '<option value="hide">' + cat[config.config.gso_lang].full.action.hide + '</option>' +
                    '<option value="hide_description_warn">' + cat[config.config.gso_lang].full.action.hide_description_warn + '</option> ' +
                    '<option value="hide_description">' + cat[config.config.gso_lang].full.action.hide_description + '</option>' +
                    '<option value="warn">' + cat[config.config.gso_lang].full.action.warn + '</option>' +
                    '<option value="info">' + cat[config.config.gso_lang].full.action.info + '</option>' +
                    '<option value="allow">' + cat[config.config.gso_lang].full.action.allow + '</option>' +
                    '</select>' +
                    '</div>')
            .append('<div style="display: inline-block;">' +
                    cat[config.config.gso_lang].full.msg.level + ':<br>' +
                    '<input type="text" id="gso_rule_level" name="gso_rule_level" placeholder="0" style="width: 2em;">' +
                    '</div>')
            .append('<div style="display: inline-block;">' +
                    cat[config.config.gso_lang].full.msg.criteria + ':<br>' +
                    '<textarea type="text" id="gso_rule_criteria" name="gso_rule_criteria" placeholder="" style="width: 210px; font-size: inherit;"></textarea>' +
                    '</div>')
            .append('<div style="display: inline-block;">' +
                    cat[config.config.gso_lang].full.msg.comment + ':<br>' +
                    '<textarea type="text" id="gso_rule_comment" name="gso_rule_comment" placeholder="" style="width: 210px; font-size: inherit;"></textarea>' +
                    '</div>');
        fieldset.appendTo(cfg_elem);

        fieldset = $('<fieldset></fieldset>');
        fieldset.append('<legend><button type="button" id="gso_ruleset_manager_toggle" class="gso_control_buttons">▼</button>' + cat[config.config.gso_lang].full.msg.manageRuleset + '</legend>');
        fieldset.append('<div id="gso_ruleset_manager" style="display: none;"></div>');
        fieldset.find('#gso_ruleset_manager')
            .append(cat[config.config.gso_lang].full.msg.friendlyNameForCurrentRuleset + ':<input type="text" id="gso_ruleset_name" name="gso_ruleset_name" placeholder="' + cat[config.config.gso_lang].full.msg.friendlyName + '" style="width: auto;"><br>')
            .append('<button type="button" id="gso_ruleset_remove" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.deleteCurrentRuleset + '</button><br>')
            .append('<input type="text" id="gso_new_ruleset_key" name="gso_new_ruleset_key" placeholder="' + cat[config.config.gso_lang].full.msg.key + '" style="width: 6em;">')
            .append('<button type="button" id="gso_ruleset_add" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.addRuleset + '</button><hr>')
            .append(cat[config.config.gso_lang].full.msg.importFromFile)
            .append('<input type="file" id="gso_ruleset_importJSON" name="rulesetJSON[]"><br>')
            .append(cat[config.config.gso_lang].full.msg.exportSelected + '<button type="button" id="gso_ruleset_exportJSON" class="gso_control_buttons">JSON</button>')
            .append('<button type="button" id="gso_ruleset_exportURL" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.urlList + '</button>')
            .append('<a id="gso_ruleset_export_dllink" style="display: none;">.</a>');
        fieldset.appendTo(cfg_elem);

        fieldset = $('<fieldset></fieldset>');
        fieldset.append('<legend><button type="button" id="gso_log_toggle" class="gso_control_buttons">▼</button>' + cat[config.config.gso_lang].full.msg.activityLog + '</legend>');
        fieldset.append('<div id="gso_log" style="display: none;"></div>');
        fieldset.find('#gso_log')
            .append('<div></div>')
            .append('<button type="button" id="gso_log_clear" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.clear + '</button><br>')
            .append(cat[config.config.gso_lang].full.msg.pointForDetails + '<br>')
            .append(cat[config.config.gso_lang].full.msg.indicateOverride + '<br>');
        fieldset.find('#gso_log > div:first')
            .append('<table style="width:440px; border-spacing: 0px 2px;"></table>')
            .append('<div id="gso_log_table" style="height: 100px; width: 100%; overflow-y: scroll; overflow-x: hidden;"></div>');
        fieldset.find('#gso_log > div:first > table')
            .append('<colgroup>' +
                    '<col style="width: 4em; min-width: 4em;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 175px;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 11em; min-width: 11em;">' +
                    '</colgroup>')
            .append('<thead><tr style="font-weight: bold; background-color: lightgray;">' +
                    '<td>' + cat[config.config.gso_lang].abbrev.table.contentType + '</td>' +
                    '<td>' + cat[config.config.gso_lang].abbrev.table.target + '</td>' +
                    '<td>' + cat[config.config.gso_lang].abbrev.table.matchedString + '</td>' +
                    '<td>T/KW</td>' +
                    '<td>URL</td>' +
                    '<td>RS</td>' +
                    '<td>' + cat[config.config.gso_lang].abbrev.table.action + '</td>' +
                    '</tr></thead>');
        fieldset.find('#gso_log_table')
            .append('<table style="width:440px; border-spacing: 0px 0px;">' +
                    '<colgroup>' +
                    '<col style="width: 4em; min-width: 4em;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 175px;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 3em; min-width: 3em;">' +
                    '<col style="width: 11em; min-width: 11em;">' +
                    '</colgroup>' +
                    '<tbody>' +
                    '</tbody>');
        fieldset.appendTo(cfg_elem);

        fieldset = $('<fieldset></fieldset>');
        fieldset.append('<legend><button type="button" id="gso_config_misc_toggle" class="gso_control_buttons">▼</button>' + cat[config.config.gso_lang].full.msg.configMisc + '</legend>');
        fieldset.append('<div id="gso_config_misc" style="display: none;"></div>');
        fieldset.find('#gso_config_misc')
            .append(cat[config.config.gso_lang].full.msg.configLang +
                    (config.config.gso_lang == 'en' ? '' : ' / Language') + ': <select id="gso_lang" name="gso_lang"></select><br>')
            .append(cat[config.config.gso_lang].full.msg.configMessageLocation + ': <select id="message_location" name="message_location"></select><br>')
            .append('<input type="checkbox" value="quick_block">' + cat[config.config.gso_lang].full.msg.configQuickBlock + '<br>')
            .append('<input type="checkbox" value="check_for_image">' + cat[config.config.gso_lang].full.msg.configCheckForImage + '<br>')
            .append('<input type="checkbox" value="ruleset_name_with_comment">' + cat[config.config.gso_lang].full.msg.configRSNameWithComment + '<br>')
            .append('(' + cat[config.config.gso_lang].full.msg.configRSNameWithCommentB + ')<br>')
            .append('<input type="checkbox" value="fix_missing">' + cat[config.config.gso_lang].full.msg.configFixMissing + '<br>')
            .append('<input type="checkbox" value="hide_moshikashite">' + cat[config.config.gso_lang].full.msg.configHideMoshikashite + '<br>')
            .append('<input type="checkbox" value="force_keyword_exclusion_on_suggestion">' + cat[config.config.gso_lang].full.msg.configForceKWExcl + '<br>')
            .append('<input type="checkbox" value="always_log_checked_entries">' + cat[config.config.gso_lang].full.msg.configAlwaysLog + '<br>')
            .append('<input type="checkbox" value="float">' + cat[config.config.gso_lang].full.msg.configFloat + '<br>')
            .append('<input type="checkbox" value="animation">' + cat[config.config.gso_lang].full.msg.configAnimation + '<br>');
        Object.keys(cat).forEach(function (k) {
            fieldset.find('#gso_lang').append(
                '<option value="' + k + '">' +
                    cat[k].langName[k] +
                    (config.config.gso_lang == k ? '' : ' [' + cat[k].langName[config.config.gso_lang] + ']') +
                    '</option>');
        });

        fieldset.find('#message_location')
            .append('<option value="page">' + cat[config.config.gso_lang].full.msg.configMessageLocationPage + '</option>')
            .append('<option value="config">' + cat[config.config.gso_lang].full.msg.configMessageLocationConfig + '</option>');
        fieldset.appendTo(cfg_elem);

        fieldset = $('<fieldset></fieldset>');
        fieldset.append('<legend><button type="button" id="gso_backup_toggle" class="gso_control_buttons">▼</button>' + cat[config.config.gso_lang].full.msg.backupRestoreInit + '</legend>');
        fieldset.append('<div id="gso_backup" style="display: none;"></div>');
        fieldset.find('#gso_backup')
            .append('<button type="button" id="gso_exportAllJSON" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.backupToFile + '</button><hr>')
            .append(cat[config.config.gso_lang].full.msg.restoreAll + '<br>')
            .append('<input type="file" id="gso_importAllJSON" name="rulesetJSON[]"><br><br>')
            .append('<div style="text-align: right;"></div>');
        fieldset.find('#gso_backup > div:first')
            .append(cat[config.config.gso_lang].full.msg.init + 
                    '<button type="button" id="gso_resetAll" class="gso_control_buttons" data-phase="0">' +
                    cat[config.config.gso_lang].full.msg.initButton + '</button>');
        fieldset.appendTo(cfg_elem);
        
        fieldset = $('<fieldset></fieldset>');
        fieldset.append('<legend><button type="button" id="gso_about_toggle" class="gso_control_buttons">▼</button>' + cat[config.config.gso_lang].full.msg.about + '</legend>');
        fieldset.append('<div id="gso_about" style="display: none;"></div>');
        fieldset.find("#gso_about")
            .append('Google掃除機(仮称) Google Search Cleaner ' + GM_info.script.version + '<br>')
            .append(cat[config.config.gso_lang].full.msg.aboutAuthor + ': たかだか。(TakaDaka.) <a href="https://twitter.com/djtkdk_086969" target="_blank">Twitter</a> <a href="https://greasyfork.org/users/29445" target="_blank">Greasy Fork</a> <a href="https://github.com/djtkdk-086969" target="_blank">GitHub</a><br>')
            .append(cat[config.config.gso_lang].full.msg.aboutLicense + ': GPL v3<br>')
            .append(cat[config.config.gso_lang].full.msg.aboutJQ);
        fieldset.appendTo(cfg_elem);
        
        cfg_elem
            .append('<button type="button" id="gso_save" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.saveChange + '</button>')
            .append('<button type="button" id="gso_revert" class="gso_control_buttons">' + cat[config.config.gso_lang].full.msg.discardChange + '</button><br>')
            .append('<span id="gso_status">' + cat[config.config.gso_lang].full.msg.changeNotSaved + '</span>');

        cfg_elem.prependTo("body");
        /* 結果表示 */
        if(config.config.message_location == "page") {
            var msg_elem = $('<div id="gso_control" class="gso_control_msg gso_control_embedded" style="display: none;" lang="' +
                             config.config.gso_lang + '"></div>');
            msg_elem.append('<em>GSC</em>');
            msg_elem.append('<div id="gso_results_msg_eff"></div>');
            msg_elem.append('<div id="gso_results_msg_top"></div>');
            msg_elem.append('<ul style="list-style-type: none;"></ul>');
            msg_elem.find("ul").append('<li style="display:none"><button type="button" id="gso_killed_count_s" class="gso_control_buttons">R</button></li>');
            msg_elem.find("ul").append('<li style="display:none"><button type="button" id="gso_killed_count_si" class="gso_control_buttons">I</button></li>');
            msg_elem.find("ul").append('<li style="display:none"><button type="button" id="gso_killed_count_k" class="gso_control_buttons">S</button></li>');
            msg_elem.find("ul").append('<li id="gso_count_ik" style="display:none">' + cat[config.config.gso_lang].full.msg.ctlmsgMIS + '</li>');
            msg_elem.prependTo("body");
        } else {
            $("#gso_config fieldset:first").before('<div id="gso_results_msg_top"></div>');
            $("#gso_results_msg_top").after('<ul style="list-style-type: none; display: inline-flex;"></ul>');
            $("#gso_results_msg_top + ul")
                .append('<li style="display:none"><button type="button" id="gso_killed_count_s" class="gso_control_buttons">R</button></li>')
                .append('<li style="display:none"><button type="button" id="gso_killed_count_si" class="gso_control_buttons">I</button></li>')
                .append('<li style="display:none"><button type="button" id="gso_killed_count_k" class="gso_control_buttons">S</button></li>')
                .append('<li id="gso_count_ik" style="display:none">' + cat[config.config.gso_lang].full.msg.ctlmsgMIS + '</li>');

        }
        /* Event handlers */
        $("#gso_killed_count_s").click(function () {
            if(status.show_serp) {
                status.show_serp = false;
            } else {
                status.show_serp = true;
            }
            update_serp();
        });
        $("#gso_killed_count_si").click(function () {
            if(status.show_img) {
                status.show_img = false;
            } else {
                status.show_img = true;
            }
            update_img();
        });
        $("#gso_killed_count_k").click(function () {
            if(status.show_kw) {
                status.show_kw = false;
            } else {
                status.show_kw = true;
            }
            update_kw();
        });

        $("#gso_config_close").click(function () {
            $("#gso_config").toggle();
        });

        $("#gso_new_ruleset_key").change(function () {
            var key = $(this).val();
            var already_exists = false;
            if(key === null || key === "") {
                $("#gso_ruleset_add").prop("disabled", true);
            } else {
                already_exists = Object.keys(config.rulesets).some(function(e) {
                    return e === key;
                });
                $("#gso_ruleset_add").prop("disabled", already_exists);
            }
        });

        $("#gso_ruleset_remove").click(function () {
            if(Object.keys(config.rulesets).length > 1) {
                delete config.rulesets[$("#gso_ruleset_select").val()];
                gso_config_rseditor_init();
            } else {
                $("#gso_status").text(cat[config.config.gso_lang].full.msg.lastRulesetCannotBeDeleted);
            }
        });

        $("#gso_ruleset_add").click(function () {
            var key = $("#gso_new_ruleset_key").val();
            var already_exists = false;
            if(key === null || key === "") {
                /* no key or name is given */
                return;
            }
            already_exists = Object.keys(config.rulesets).some(function(e) {
                return e === key;
            });
            if(!already_exists) {
                /* add new ruleset */
                config.rulesets[key] = {
                    "name": "(無題)",
                    "enabled": true,
                    "rules": [
                    ]};
                gso_config_rseditor_init();
            }
        });


        $("#gso_ruleset_select").change(function () {
            $("#gso_config button").removeAttr("disabled");
            $("#gso_ruleset_table table tbody tr").remove();
            var ruleset = config.rulesets[$("#gso_ruleset_select").val()];
            jQuery.each(ruleset.rules, function(i) {
                var table = $("#gso_ruleset_table table tbody").append(
                    "<tr data-idx='" + i +"'>" +
                        "<td>" + cat[config.config.gso_lang].abbrev.target[this.target] +
                        "</td><td>" + cat[config.config.gso_lang].abbrev.type[this.type] +
                        "</td><td><div title='" + cat[config.config.gso_lang].full.action[this.action] +
                        "' style='width: 100%;'>" + cat[config.config.gso_lang].abbrev.action[this.action] +
                        "</div></td><td>" + this.level +
                        "</td><td style='overflow: hidden; white-space: nowrap; width: 248px; max-width: 248px; text-overflow: ellipsis;'>" + this.criteria +
                        "</td><td>" +
                        "</td></tr>");
                if(this.comment !== "") {
                    table.find("tr:last td:eq(5)")
                        .append("<div title='" + this.comment + "' style='width: 100%; background-color: silver;'>…</div>");
                }
                if(!this.enabled) {
                    table.find("tr:last").addClass("gso_rule_disabled");
                }
            });
            $("#gso_ruleset_enabled").prop("checked", ruleset.enabled);
            $("#gso_ruleset_name").val(ruleset.name);

            $("#gso_ruleset_table table tbody tr").click(function () {
                $(this).toggleClass("gso_rule_selected");
                gso_rseditor_update_selection();
            });
            gso_rseditor_update_selection();
        });

        $("#gso_ruleset_name").change(function () {
            var ruleset = config.rulesets[$("#gso_ruleset_select").val()];
            ruleset.name = $(this).val();
            gso_rseditor_update_rslist();
        });
        $("#gso_ruleset_enabled").change(function () {
            var ruleset = config.rulesets[$("#gso_ruleset_select").val()];
            ruleset.enabled = $(this).prop("checked");
            gso_rseditor_update_rslist();
        });

        $("#gso_ruleset_exportJSON").click(function () {
            var rules = [];
            $("#gso_ruleset_table table tbody tr.gso_rule_selected").each(function() {
                rules.push(config.rulesets[$("#gso_ruleset_select").val()].rules[Number($(this).attr('data-idx'))]);
            });
            var export_json = JSON.stringify(rules);
            var blob = new Blob([export_json], {'type': 'application/json'});
            var dl_link = document.getElementById('gso_ruleset_export_dllink');
            dl_link.target = '_blank';
            dl_link.download = "rules.json";
            dl_link.href = (window.URL || window.webkitURL).createObjectURL(blob);
            dl_link.click();
            dl_link.href = "#";
        });
        $("#gso_ruleset_exportURL").click(function () {
            var text = [];
            var rule;
            $("#gso_ruleset_table table tbody tr.gso_rule_selected").each(function() {
                rule = config.rulesets[$("#gso_ruleset_select").val()].rules[Number($(this).attr('data-idx'))];
                if(rule.type == "str" || rule.type == "domain" || rule.type == "word") {
                    text.push(rule.criteria);
                } else if(rule.type == "str_head") {
                    text.push("/^" + rule.criteria + "/i");
                } else if(rule.type == "regexp") {
                    text.push("/" + rule.criteria + "/i");
                }
            });
            var export_text = text.join("\n");
            var blob = new Blob([export_text], {'type': 'text/plain'});
            var dl_link = document.getElementById('gso_ruleset_export_dllink');
            dl_link.target = '_blank';
            dl_link.download = "rules.txt";
            dl_link.href = (window.URL || window.webkitURL).createObjectURL(blob);
            dl_link.click();
            dl_link.href = "#";
        });
        $("#gso_ruleset_importJSON").change(function (event) {
            var rules_json;
            var key = $("#gso_ruleset_select").val();
            if (key === "") {
                return false;
            } else {
                var file = event.target.files[0];
                /* MacではOKだが、Windowsだとfile.typeは空になる
                   if(file.type != 'application/json' && file.type != 'text/plain') {
                   return true; // continue
                   }
                */                
                var reader = new FileReader();
                reader.onload = (function(file) {
                    return function(evt) {
                        try {
                            rules_json = JSON.parse(evt.target.result);
                        }
                        catch (e) {
                            if (e instanceof SyntaxError) {
                                /* JSONの構文が正しくない 単純リスト形式でのインポートを試みる */
                                console.log("Invalid JSON syntax. Attempting to read as a simple list.");
                                var text = evt.target.result.split(/\r\n|\r|\n/);
                                rules_json = [];
                                text.forEach(function(element) {
                                    if(/^([a-zA-Z0-9][a-zA-Z0-9\-]*\.)+[a-zA-Z]+$/.test(element)) {
                                        /* ドメイン名 */
                                        rules_json.push({
                                            "target": "url",
                                            "type": "domain",
                                            "action": "hide",
                                            "level": 0,
                                            "criteria": element,
                                            "comment": "",
                                            "enabled": true
                                        });
                                    } else {
                                        /* そうでない場合は対象「URL」、文字列単純検索を仮定する */
                                        rules_json.push({
                                            "target": "url",
                                            "type": "str",
                                            "action": "hide",
                                            "level": 0,
                                            "criteria": element,
                                            "comment": "",
                                            "enabled": true
                                        });
                                    }
                                });
                            }
                        }
                        gso_import_ruleset(rules_json, key);
                        gso_config_rseditor_init();
                    };
                })(file);
                reader.readAsText(file);
            }
        });
        $("#gso_exportAllJSON").click(function () {
            var export_json = JSON.stringify(config);
            var blob = new Blob([export_json], {'type': 'application/json'});
            var dl_link = document.getElementById('gso_ruleset_export_dllink');
            dl_link.target = '_blank';
            dl_link.download = "config.json";
            dl_link.href = (window.URL || window.webkitURL).createObjectURL(blob);
            dl_link.click();
            dl_link.href = "#";
        });
        $("#gso_importAllJSON").change(function (event) {
            var file = event.target.files[0];
            /* MacではOKだが、Windowsだとfile.typeは空になる
               if(file.type != 'application/json' && file.type != 'text/plain') {
               return true; // continue
               }
            */
            var reader = new FileReader();
            reader.onload = (function(file) {        
                return function(evt) {
                    var config_json;
                    try {
                        config_json = JSON.parse(evt.target.result);
                    }
                    catch (e) {
                        console.log(e.message);
                        if (e instanceof SyntaxError) {
                            $("#gso_status").text(cat[config.config.gso_lang].full.msg.invalidJSON);
                            return;
                        }
                    }
                    if(check_config(config_json)) {
                        config = config_json;
                        gso_config_rseditor_init();
                    } else {
                        console.log(config_json);
                        $("#gso_status").text(cat[config.config.gso_lang].full.msg.invalidConfig);
                    }
                };
            })(file);
            reader.readAsText(file);
        });
        $("#gso_resetAll").click(function () {
            var phase = Number($(this).attr('data-phase'));
            if (phase === 0) {
                $(this).text(cat[config.config.gso_lang].full.msg.initConfirm);
                $(this).attr('data-phase', "1");
            } else if(phase >= 1) {
                GM_deleteValue("quick_block");
                GM_deleteValue("check_for_image");
                GM_deleteValue("ruleset_name_with_comment");
                GM_deleteValue("fix_missing");
                GM_deleteValue("hide_moshikashite");
                GM_deleteValue("force_keyword_exclusion_on_suggestion");
                GM_deleteValue("always_log_checked_entries");
                GM_deleteValue("float");
                GM_deleteValue("animation");
                GM_deleteValue("verbose");

                GM_deleteValue("rulesets");
                
                gso_load();
                gso_config_init();
                $("#gso_status").text(cat[config.config.gso_lang].full.msg.initialized);
                $(this).text(cat[config.config.gso_lang].full.msg.initButton);
                $(this).attr('data-phase', "0");
            }
        });
        
        $("#gso_ruleset_selectAll").click(function () {
            $("#gso_ruleset_table table tbody tr").addClass("gso_rule_selected");
            gso_rseditor_update_selection();
        });
        $("#gso_ruleset_unselectAll").click(function () {
            $("#gso_ruleset_table table tbody tr").removeClass("gso_rule_selected");
            gso_rseditor_update_selection();
        });
        $("#gso_ruleset_selectToggle").click(function () {
            $("#gso_ruleset_table table tbody tr").toggleClass("gso_rule_selected");
            gso_rseditor_update_selection();
        });
        $("#gso_rule_moveup").click(function () {
            var idx;
            if($("#gso_ruleset_table table tbody tr.gso_rule_selected").size() == 1) {
                idx = Number($("#gso_ruleset_table table tbody tr.gso_rule_selected").attr('data-idx'));
                if(idx - 1 >= 0) {
                    config.rulesets[$("#gso_ruleset_select").val()].rules.splice(
                        idx - 1, 2,
                        config.rulesets[$("#gso_ruleset_select").val()].rules[idx],
                        config.rulesets[$("#gso_ruleset_select").val()].rules[idx - 1]);
                    $("#gso_ruleset_select").change();
                    $("#gso_ruleset_table table tbody tr:eq(" + (idx-1) + ")").click();
                }
            }
        });
        $("#gso_rule_movedown").click(function () {
            var idx;
            if($("#gso_ruleset_table table tbody tr.gso_rule_selected").size() == 1) {
                idx = Number($("#gso_ruleset_table table tbody tr.gso_rule_selected").attr('data-idx'));
                if(idx + 1 <= config.rulesets[$("#gso_ruleset_select").val()].rules.length - 1) {
                    config.rulesets[$("#gso_ruleset_select").val()].rules.splice(
                        idx, 2,
                        config.rulesets[$("#gso_ruleset_select").val()].rules[idx + 1],
                        config.rulesets[$("#gso_ruleset_select").val()].rules[idx]);
                    $("#gso_ruleset_select").change();
                    $("#gso_ruleset_table table tbody tr:eq(" + (idx+1) + ")").click();
                }
            }
        });

        $("#gso_rule_add").click(function () {
            var new_rule = {
                "target": $("#gso_rule_target").val(),
                "type": $("#gso_rule_type").val(),
                "action": $("#gso_rule_action").val(),
                "level": Number($("#gso_rule_level").val()),
                "criteria": $("#gso_rule_criteria").val(),
                "comment": $("#gso_rule_comment").val(),
                "enabled": $("#gso_rule_enabled").prop("checked")
            };
            if(!check_rule(new_rule)) return;
            if($("#gso_ruleset_table table tbody tr.gso_rule_selected").size() == 1) {
                config.rulesets[$("#gso_ruleset_select").val()].rules.splice(
                    Number($("#gso_ruleset_table table tbody tr.gso_rule_selected:last").attr('data-idx')) + 1,
                    0,
                    new_rule);
            } else {
                config.rulesets[$("#gso_ruleset_select").val()].rules.push(new_rule);
            }
            $("#gso_ruleset_select").change();
        });
        $("#gso_rule_overwrite").click(function () {
            /* 入力チェックが必要 */
            var new_rule = null;
            if($("#gso_ruleset_table table tbody tr.gso_rule_selected").size() == 1) {
                new_rule = {
                    "target": $("#gso_rule_target").val(),
                    "type": $("#gso_rule_type").val(),
                    "action": $("#gso_rule_action").val(),
                    "level": Number($("#gso_rule_level").val()),
                    "criteria": $("#gso_rule_criteria").val(),
                    "comment": $("#gso_rule_comment").val(),
                    "enabled": $("#gso_rule_enabled").prop("checked")
                };
                if(!check_rule(new_rule)) return;
                var index = Number($("#gso_ruleset_table table tbody tr.gso_rule_selected:first")
                    .attr('data-idx'));
                config.rulesets[$("#gso_ruleset_select").val()].rules[index] = new_rule;
                $("#gso_ruleset_select").change();
            } else if($("#gso_ruleset_table table tbody tr.gso_rule_selected").size() > 1) {
                /* 複数選択 */
                $("#gso_ruleset_table table tbody tr.gso_rule_selected").each(function() {
                    new_rule = {
                        "target": $("#gso_rule_target").val(),
                        "type": $("#gso_rule_type").val(),
                        "action": $("#gso_rule_action").val(),
                        "level": Number($("#gso_rule_level").val()),
                        "criteria": $("#gso_rule_criteria").val(),
                        "comment": $("#gso_rule_comment").val(),
                        "enabled": $("#gso_rule_enabled").prop("checked")
                    };
                    var rule = config.rulesets[$("#gso_ruleset_select").val()].rules[Number($(this).attr('data-idx'))];
                    Object.keys(new_rule).forEach(function (k) {
                        if (new_rule[k] === "" || new_rule[k] === null) {
                            new_rule[k] = rule[k];
                        }
                    });
                    console.log(new_rule);
                    if(check_rule(new_rule)) {
                        config.rulesets[$("#gso_ruleset_select").val()].rules[Number($(this).attr('data-idx'))] = new_rule;
                    }
                });
                $("#gso_ruleset_select").change();
            }
        });
        $("#gso_rule_remove").click(function () {
            $("#gso_ruleset_table table tbody tr.gso_rule_selected").each(function() {
                var index = Number($(this).attr('data-idx'));
                delete config.rulesets[$("#gso_ruleset_select").val()].rules[index];
            });
            config.rulesets[$("#gso_ruleset_select").val()].rules =
                config.rulesets[$("#gso_ruleset_select").val()].rules.filter(function(e) {
                    return e !== undefined;
                });
            $("#gso_ruleset_select").change();
        });

        $("#gso_config_misc input").change(function() {
            config.config[$(this).val()] = $(this).prop("checked");
        });
        $("#gso_config_misc select").change(function() {
            config.config[this.name] = $(this).val();
        });

        $("#gso_ruleset_editor_toggle").click(function() {
            if($("#gso_ruleset_editor").is(":visible")) {
                $("#gso_ruleset_editor_toggle").text("▼");
            } else {
                $("#gso_ruleset_editor_toggle").text("▲");
            }
            $("#gso_ruleset_editor").slideToggle();
        });
        $("#gso_log_toggle").click(function() {
            if($("#gso_log").is(":visible")) {
                $("#gso_log_toggle").text("▼");
            } else {
                $("#gso_log_toggle").text("▲");
            }
            $("#gso_log").slideToggle();
        });
        $("#gso_log_clear").click(function() {
            $("#gso_log_table tbody tr").remove();
        });
        $("#gso_ruleset_manager_toggle").click(function() {
            if($("#gso_ruleset_manager").is(":visible")) {
                $("#gso_ruleset_manager_toggle").text("▼");
            } else {
                $("#gso_ruleset_manager_toggle").text("▲");
            }
            $("#gso_ruleset_manager").slideToggle();
        });
        $("#gso_config_misc_toggle").click(function() {
            if($("#gso_config_misc").is(":visible")) {
                $(this).text("▼");
            } else {
                $(this).text("▲");
            }
            $("#gso_config_misc").slideToggle();
        });
        $("#gso_backup_toggle").click(function() {
            if($("#gso_backup").is(":visible")) {
                $(this).text("▼");
            } else {
                $(this).text("▲");
            }
            $("#gso_backup").slideToggle();
        });
        $("#gso_about_toggle").click(function() {
            if($("#gso_about").is(":visible")) {
                $(this).text("▼");
            } else {
                $(this).text("▲");
            }
            $("#gso_about").slideToggle();
        });

        $("#gso_save").click(function () {
            gso_save();
            $("#gso_status").text(cat[config.config.gso_lang].full.msg.reloadToTakeEffect);
        });

        $("#gso_revert").click(function () {
            gso_load();
            gso_config_init();
        });
        $(window).scroll(function () {
            /* 表示を追従させる */
            var ctl = $("#gso_control");
            var cfg = $("#gso_config");
            var minimum_top = 60;
            var minimum_top_cfg = 0;

            if(config.config.float) {
                if($(window).scrollTop() > minimum_top && ctl.hasClass("gso_control_embedded")) {
                    ctl.removeClass("gso_control_embedded");
                    ctl.addClass("gso_float");
                } else if($(window).scrollTop() <= minimum_top && cfg.hasClass("gso_float")) {
                    ctl.removeClass("gso_float");
                    ctl.addClass("gso_control_embedded");
                }
                if($(window).scrollTop() > minimum_top_cfg && cfg.hasClass("gso_config_embedded")) {
                    cfg.removeClass("gso_config_embedded");
                    cfg.addClass("gso_float");
                } else if($(window).scrollTop() <= minimum_top_cfg && cfg.hasClass("gso_float")) {
                    cfg.removeClass("gso_float");
                    cfg.addClass("gso_config_embedded");
                }
            } else {
                if(ctl.hasClass("gso_float")) {
                    ctl.removeClass("gso_float");
                    ctl.addClass("gso_control_embedded");
                }
                if(cfg.hasClass("gso_float")) {
                    cfg.removeClass("gso_float");
                    cfg.addClass("gso_config_embedded");
                }
            }
        });
        /* End of event handlers section */
    }
    /* Initialize Configuration */
    gso_config_init();
    $("#gso_ruleset_select").change();

    if($("#sbtc").size() > 0 || $("#search").size() > 0) {
        $("#gso_control").show();
    } else {
        $("#gso_control").hide();
    }
    
    var mo_autocomplete =
        new MutationObserver(function(mutationEventList){
            mutationEventList.forEach(function(mutationEvent) {
                var target = mutationEvent.target;
                if(target.id == "lst-ib" || target.id == "sbtc") {
                    check_autocomplete();
                }
            });
        });
    
    /* mo_serp: 検索結果を探す */
    var mo_serp = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            /* ノードが追加されたか? */
            if (mutation.addedNodes && (mutation.addedNodes.length > 0)) {
                /* その中に 'div#search'があるか? */
                var node_search = mutation.target.querySelector("div#search");
                if (!node_search) node_search = mutation.target.querySelector("div#rhs");
                /* 右側「他の人はこちらを検索」とか */
                if (node_search) {
                    /* 'div#search' が挿入された */
                    mo_serp.disconnect();

                    /* すでに存在する要素について直ちにチェック */
                    $(node_search).find(selector_KW).not("*.gso_checked").each( function() {
                        check_elem_kw(this);
                    });
                    if(location.href.search("&tbm=isch&") == -1){
                        $(node_search).find(selector_SERP).not("*.gso_checked").each( function() {
                            check_elem_serp(this);
                        });
                        $(node_search).find(selector_IMG).not("*.gso_checked").each( function() {
                            check_elem_img(this);
                        });
                    }
                    if(location.href.search("&tbm=isch&") >= 0 && config.config.check_for_image){
                        $(selector_IMGLIST).not("*.gso_checked").each(function() {
                            check_elem_imglist(this);
                        });
                    }
                    mo_link.observe(node_search, {childList: true, subtree: true});
                    mo_serp.observe(document.body, {childList: true, subtree: true});  
                }

                /* その中に 'div#topstuff'があるか? */
                var node_topstuff = mutation.target.querySelector("div#topstuff");
                if (node_topstuff) {
                    /* 'div#topstuff' が挿入された */
                    mo_serp.disconnect();

                    /* すでに存在する要素について直ちにチェック */
                    $(node_topstuff).find(selector_KW).not("*.gso_checked").each( function() {
                        check_elem_kw(this);
                    });
                    hide_moshikashite();
                    mo_link.observe(node_topstuff, {childList: true, subtree: true});
                    mo_serp.observe(document.body, {childList: true, subtree: true});  
                }

                /* その中に 'div#taw'があるか? */
                var node_taw = mutation.target.querySelector("div#taw");
                if (node_taw) {
                    /* 'div#taw' が挿入された */
                    mo_serp.disconnect();

                    /* すでに存在する要素について直ちにチェック */
                    $(node_taw).find(selector_KW).not("*.gso_checked").each( function() {
                        check_elem_kw(this);
                    });
                    hide_moshikashite();
                    mo_link.observe(node_taw, {childList: true, subtree: true});
                    mo_serp.observe(document.body, {childList: true, subtree: true});  
                }

                /* その中に 'div#extrares'があるか? */
                var node_extrares = mutation.target.querySelector("div#extrares");
                if (node_extrares) {
                    /* 'div#extrares' が挿入された */
                    mo_serp.disconnect();

                    /* すでに存在する要素について直ちにチェック */
                    $(node_extrares).find(selector_KW).not("*.gso_checked").each( function() {
                        check_elem_kw(this);
                    });
                    mo_link.observe(node_extrares, {childList: true, subtree: true});
                    mo_serp.observe(document.body, {childList: true, subtree: true});  
                }
            }
        });
    });
    
    /* mo_link: ある要素の中に別の要素が挿入されたか監視 */
    var mo_link = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.addedNodes) {
                [].slice.call(mutation.addedNodes).forEach(function(node) {
                    /* 最初に目に入る「関連キーワード」を最初に処理 */
                    $(node).find(selector_KW).not("*.gso_checked").each(function() {
                        check_elem_kw(this);
                    });
                    /* SERPを探して処理 */
                    if(location.href.search("&tbm=isch&") == -1){
                        $(node).find(selector_SERP).not("*.gso_checked").each(function() {
                            check_elem_serp(this);
                        });
                        $(node).find(selector_IMG).not("*.gso_checked").each(function() {
                            check_elem_img(this);
                        });
                    }
                    if(location.href.search("&tbm=isch&") >= 0 && config.config.check_for_image){
                        $(selector_IMGLIST).not("*.gso_checked").each(function() {
                            check_elem_imglist(this);
                        });
                    }
                    
                });
            }
        });
        hide_moshikashite();
    });

    $(document).ready(check_elem_first());

    /* div#searchの挿入を監視 */
    mo_serp.observe(document.body, {childList: true, subtree: true});    
    
    /* オートコンプリートの監視 */
    mo_autocomplete.observe(document.getElementById("sbtc"),
                            {attributes: true, childList: true, characterData: true, subtree: false});
    
    function check_elem_serp(node) {
        /* ---------- 検索結果 ---------- */
        if(!$(node).is(".gso_checked")) {
            /* ここでは検索して状況を記録するのみ
               書式の変更はまだ行わない */
            /* 各SERP(node)の状況を格納するオブジェクト */
            var context =
                {"element": $(node),
                 "title": null,
                 "target": null,
                 "description": null,
                 "matched_rules": null
                };
            var link = $(node).find("a:not(._T6c, .top, .ab_button, .fl)");
            if(link.size() === 0){
                link = $(node);
            }
            /* 状況記録部分 */
            /* ページのタイトル
               (実際のものからGoogleにより改竄される場合あり) */
            context.title = link.text();
            /* ページのURL */
            context.target = link.attr("href");

            /* ページの説明文または煽り文句 */
            try {
                context.description = $(node).find("span.st, div.st").text();
            }
            catch (e) {
                if (e instanceof TypeError) {
                    context.description = null;
                }
            }
            /* ルールに合致しているかチェック */
            context.matched_rules = check(context.target, context.description, context.title, null, null);
            if(context.matched_rules.length > 0) {
                var applied_rule = get_most_significant_rule(context.matched_rules);
                var ruleset_name = config.rulesets[applied_rule.ruleset_id].name;
                var original_desc = $(node).find("span.st, div.st");
                var new_desc = original_desc.clone();
                var msg = "";

                context.matched_rules.forEach(function(element, index, array) {
                    element.forEach(function (e) {
                        gso_log_append("page",
                                       e.rule.target,
                                       e.matched,
                                       context.title,
                                       context.target,
                                       e.ruleset_id,
                                       e.rule.action,
                                       applied_rule.rule.action,
                                       !(e.effective !== undefined && e.effective));
                    });
                });
                if(applied_rule.rule.action == "hide") {
                    $(node).hide();
                    $(node).find("div._S6c").insertAfter($(node)); /* 「詳細を見る」を外に出す */
                    if(applied_rule.rule.comment !== "" &&
                       !applied_rule.rule.comment.startsWith("#") &&
                       config.config.ruleset_name_with_comment) {
                        msg = ruleset_name + " [" + applied_rule.rule.comment + "]";
                    } else {
                        msg = ruleset_name;
                    }

                    if($(node).is("a")) {
                        $(node).replaceWith('<span class="gso_killed_serp_msg gso_killed_serp gso_serp_description_b gso_ani">'+
                                            msg +': ' +
                                            '<span class="gso_killed_url">' +
                                            applied_rule.matched + '</span>' +
                                            '</span>');
                    } else {
                        $(node).html('<span class="gso_killed_serp_msg">'+ msg +': ' +
                                     '<span class="gso_killed_url">' +
                                     applied_rule.matched + '</span>' +
                                     '</span>');
                        $(node).addClass("gso_killed_serp gso_serp_description_b gso_ani");
                    }

                } else if(applied_rule.rule.action == "hide_absolutely") {
                    $(node).hide();
                    $(node).find("div._S6c").insertAfter($(node)); /* 「詳細を見る」を外に出す */
                    if(applied_rule.rule.comment !== "" &&
                       !applied_rule.rule.comment.startsWith("#") &&
                       config.config.ruleset_name_with_comment) {
                        msg = ruleset_name + " [" + applied_rule.rule.comment + "]";
                    } else {
                        msg = ruleset_name;
                    }

                    if($(node).is("a")) {
                        $(node).replaceWith('<span class="gso_killed_serp_msg">'+ msg +
                                            '</span>');
                    } else {
                        $(node).html('<span class="gso_killed_serp_msg">'+ msg +
                                     '</span>');
                    }
                    $(node).addClass("gso_killed_serp gso_serp_description_b gso_ani");

                } else if(applied_rule.rule.action == "hide_description") {
                    new_desc.addClass("gso_serp_description_info gso_serp_description_a");
                    if(applied_rule.rule.comment !== "" &&
                       !applied_rule.rule.comment.startsWith("#") &&
                       config.config.ruleset_name_with_comment) {
                        new_desc.text(ruleset_name + " [" + applied_rule.rule.comment + "]");
                    } else {
                        new_desc.text(ruleset_name);
                    }
                    new_desc.insertAfter(original_desc);
                    original_desc.addClass("gso_serp_description_b gso_ani");
                    original_desc.hide();
                    $(node).addClass("gso_titleonly_serp");
                } else if(applied_rule.rule.action == "hide_description_warn") {
                    new_desc.addClass("gso_serp_description_warning gso_serp_description_a");
                    if(applied_rule.rule.comment !== "" &&
                       !applied_rule.rule.comment.startsWith("#") &&
                       config.config.ruleset_name_with_comment) {
                        new_desc.html("&#x26A0; " + ruleset_name + " [" + applied_rule.rule.comment + "]");
                    } else {
                        new_desc.html("&#x26A0; " + ruleset_name);
                    }
                    new_desc.insertBefore(original_desc);
                    original_desc.addClass("gso_serp_description_b gso_ani");
                    original_desc.hide();
                    $(node).addClass("gso_titleonly_serp");
                } else if(applied_rule.rule.action == "warn") {
                    new_desc.addClass("gso_serp_description_warning");
                    if(applied_rule.rule.comment !== "" &&
                       !applied_rule.rule.comment.startsWith("#") &&
                       config.config.ruleset_name_with_comment) {
                        new_desc.html("&#x26A0; " + ruleset_name + " [" + applied_rule.rule.comment + "]");
                    } else {
                        new_desc.html("&#x26A0; " + ruleset_name);
                    }
                    new_desc.insertBefore(original_desc);
                    $(node).addClass("gso_titleonly_serp");
                } else if(applied_rule.rule.action == "info") {
                    new_desc.addClass("gso_serp_description_info");
                    if(applied_rule.rule.comment !== "" &&
                       !applied_rule.rule.comment.startsWith("#") &&
                       config.config.ruleset_name_with_comment) {
                        new_desc.text(ruleset_name + " [" + applied_rule.rule.comment + "]");
                    } else {
                        new_desc.text(ruleset_name);
                    }
                    new_desc.insertBefore(original_desc);
                }
            } else if(config.config.always_log_checked_entries) {
                gso_log_append("page",
                               null,
                               null,
                               context.title,
                               context.target,
                               null,
                               "allow",
                               "allow",
                               true);
            }
            /* ---------- 「未指定:○○○」を処理 ---------- */
            if(config.config.fix_missing) {
                $(node).find("div._Tib:has(s)").each(function() {
                    var missing_kw_list = $(this);
                    var missing_kw_list_ar = [];
                    var missing_kw_list_new_html = missing_kw_list.html()
                        .replace(/<span>未指定:<\/span>/gi, cat[config.config.gso_lang].full.msg.ignoredKeywords + ':')
                        .replace(/<span>Missing:<\/span>/gi, cat[config.config.gso_lang].full.msg.ignoredKeywords + ':')
                        .replace(/<s>/gi, '<span class="gso_ignored_kw">')
                        .replace(/<\/s>/gi, "</span>");
                    missing_kw_list.html(missing_kw_list_new_html);
                    
                    var new_url_all = decodeURI_s(location.href.replace(/&start=\d+/, ""));
                    missing_kw_list.children("span.gso_ignored_kw").each(function () {
                        var ignored_kw = $(this).text();
                        var re = new RegExp(escapeRegexp(ignored_kw), "gi");
                        var new_url = decodeURI_s(location.href)
                            .replace(/&start=\d+/, "")
                            .replace(re, '"' + ignored_kw + '"');
                        new_url_all = new_url_all.replace(re, '"' + ignored_kw + '"');
                        $(this).html('<a href="' + encodeURI(new_url) + '">' + ignored_kw + '</a>');
                        missing_kw_list_ar.push(ignored_kw);
                    });
                    gso_log_append("missing",
                                   null,
                                   missing_kw_list_ar.join(" "),
                                   context.title,
                                   context.target,
                                   null,
                                   null,
                                   null,
                                   false);
                    if(missing_kw_list.children("span.gso_ignored_kw").size() > 1) {
                        missing_kw_list.append(' <a href="' + encodeURI(new_url_all) + '">' +
                                               cat[config.config.gso_lang].full.msg.searchAllIncluded + '</a>');
                    }
                });
            }
            if(config.config.quick_block && !$(node).is("a")) {
                $(node).find("div._SWb")
                    .append("<span class='gso_quick_block' style='display: none;'>" +
                            cat[config.config.gso_lang].full.msg.block +
                            "<button type='button' class='gso_control_buttons'>URL</button><button type='button' class='gso_control_buttons'>" +
                            cat[config.config.gso_lang].full.msg.domain + "</button></span>");
                var qb = $(node).find("span.gso_quick_block > button:eq(0)");
                var qb2 = $(node).find("span.gso_quick_block > button:eq(1)");
                var domain = "";
                qb.attr("data-target", context.target);
                qb.click(function () {
                    $("#gso_rule_target").val("url");
                    $("#gso_rule_type").val("str_head");
                    $("#gso_rule_action").val("hide");
                    $("#gso_rule_criteria").val($(this).attr("data-target"));
                    $("#gso_rule_comment").val("");
                    $("#gso_rule_enabled").prop("checked", true);
                    $("#gso_config").show();
                });
                try {
                    domain = context.target.split("/")[2].split(":")[0];
                }
                catch (e) {
                    domain = "";
                }
                qb2.attr("data-domain", domain);
                qb2.click(function () {
                    if($(this).attr("data-domain") !== "") {
                        $("#gso_rule_target").val("url");
                        $("#gso_rule_type").val("domain");
                        $("#gso_rule_action").val("hide");
                        $("#gso_rule_criteria").val($(this).attr("data-domain"));
                        $("#gso_rule_comment").val("");
                        $("#gso_rule_enabled").prop("checked", true);
                        $("#gso_config").show();
                    }
                });
                $(node).hover(
                    function () {
                        /* IN */
                        $(this).find("*.gso_quick_block").show();
                    },
                    function () {
                        /* OUT */
                        $(this).find("*.gso_quick_block").hide();
                    }
                );
            }
        }
        $(node).addClass("gso_checked");
        
        gso_log_setBoundary();
        update_gso_control_msg();
        update_serp();

    }
    function check_elem_img(node) {
        /* ---------- 検索結果に現れる画像 ---------- */
        if(!$(node).is(".gso_checked")) {
            var context =
                {"element": $(node),
                 "target": null,
                 "description": null,
                 "from": null,
                 "matched_rules": null
                };
            var link;
            if ($(node).is("a")) {
                link = $(node);
            } else {
                link = $(node).find("a:first");
            }
            if(link.size() > 0) {
                /* 状況記録部分 */
                if(link.hasClass("uh_rl")) {
                    /* ***の画像検索結果 */
                    // ページのURL
                    context.target = link.find("img:first").attr("title");
                    // 代替テキスト
                    context.description = link.find("img:first").attr("alt"); 
                }
                context.matched_rules = check(context.target, context.description, null, null, null);
                
                if(context.matched_rules.length > 0) {
                    var applied_rule = get_most_significant_rule(context.matched_rules);
                    var ruleset_name = config.rulesets[applied_rule.ruleset_id].name;

                    var msg = "";

                    context.matched_rules.forEach(function(element, index, array) {
                        element.forEach(function (e) {
                            gso_log_append("img",
                                           e.rule.target,
                                           e.matched,
                                           null,
                                           context.target,
                                           e.ruleset_id,
                                           e.rule.action,
                                           applied_rule.rule.action,
                                           !(e.effective !== undefined && e.effective));
                        });
                    });


                    if(applied_rule.rule.comment !== "" &&
                       !applied_rule.rule.comment.startsWith("#") &&
                       config.config.ruleset_name_with_comment) {
                        msg = ruleset_name + " [" + applied_rule.rule.comment + "]";
                    } else {
                        msg = ruleset_name;
                    }

                    if(applied_rule.rule.action != "allow" &&
                       applied_rule.rule.action != "hide_description" &&
                       applied_rule.rule.action != "info") {
                        $(node).addClass("gso_killed_serpimg");
                        $(node).append('<div class="gso_killed_serpimg_warn gso_killed_img_mask_serp"></div>');
                        if(applied_rule.rule.action == "hide") {
                            $(node).find("*.gso_killed_serpimg_warn").css("opacity", "0.8");
                            $(node).find("*.gso_killed_serpimg_warn").html("&#x26A0; " + msg);
                            $(node).append('<div class="gso_killed_serpimg_warn gso_killed_img_mask_serp gso_serp_description_a"></div>');
                        }
                        if(applied_rule.rule.action == "hide_description_warn" ||
                           applied_rule.rule.action == "warn") {
                            $(node).find("*.gso_killed_serpimg_warn").addClass("gso_serp_description_a");
                            $(node).find("*.gso_killed_serpimg_warn").css("opacity", "0.8");
                            $(node).find("*.gso_killed_serpimg_warn").html("&#x26A0; " + msg);
                        }
                        if(applied_rule.rule.action == "hide_absolutely") {
                            $(node).find("*.gso_killed_serpimg_warn").css("opacity", "1");
                        }
                    }
                } else if(config.config.always_log_checked_entries) {
                    gso_log_append("img",
                                   null,
                                   null,
                                   null,
                                   context.target,
                                   null,
                                   "allow",
                                   "allow",
                                   true);
                }
            }
            $(node).addClass("gso_checked");
            gso_log_setBoundary();
            update_gso_control_msg();
        }
        update_img();
    }
    function check_elem_imglist(node) {
        /* ---------- 画像検索 ---------- */
        if(!$(node).is(".gso_checked")) {
            var context =
                {"element": $(node),
                 "title": null,
                 "target": null,
                 "imgsrc": null,
                 "description": null,
                 "from": null,
                 "matched_rules_target": null,
                 "matched_rules_imgsrc": null
                };
            /* 状況記録部分 */
            var link = $(node).find("a.rg_l");
            /* console.log($(node).find("a.rg_l")[0].href)
               では、なぜか undefined が返される */
            var metadata = null;
            /* $(SELECTOR_IMGLIST) 直下の div.rg_meta (非表示)に
               画像のメタデータがJSON形式で格納されている */
            try {
                metadata = JSON.parse($(node).find("div.rg_meta").text());
                context.title = metadata.pt;
                context.description = metadata.s;
                context.from = metadata.isu;
                context.target = metadata.ru;
                context.imgsrc = metadata.ou;
            }
            catch (e) {
                /* bad metadata */
                context.title = null;
                context.description = null;
                context.from = null;
            }
            if(link.size() > 0) {
                context.matched_rules_target = check(context.target, context.description, context.title, null, null);
                context.matched_rules_imgsrc = check(context.imgsrc, null, null, null, null);
                var matched_rules = context.matched_rules_target.concat(context.matched_rules_imgsrc);

                if(matched_rules.length > 0) {
                    var applied_rule = get_most_significant_rule(matched_rules);
                    var ruleset_name = config.rulesets[applied_rule.ruleset_id].name;

                    var msg = "";
                    matched_rules.forEach(function(element, index, array) {
                        element.forEach(function (e) {
                            gso_log_append("img",
                                           e.rule.target,
                                           e.matched,
                                           context.title,
                                           context.target,
                                           e.ruleset_id,
                                           e.rule.action,
                                           applied_rule.rule.action,
                                           !(e.effective !== undefined && e.effective));
                        });

                    });

                    if(applied_rule.rule.comment !== "" &&
                       !applied_rule.rule.comment.startsWith("#") &&
                       config.config.ruleset_name_with_comment) {
                        msg = ruleset_name + " [" + applied_rule.rule.comment + "]";
                    } else {
                        msg = ruleset_name;
                    }

                    if(applied_rule.rule.action == "hide" ||
                       applied_rule.rule.action == "hide_description_warn" ||
                       applied_rule.rule.action == "warn" ||
                       applied_rule.rule.action == "hide_absolutely"
                      ) {
                        $(node).addClass("gso_killed_serpimg");
                        $(node).append('<div class="gso_killed_serpimg_warn gso_killed_img_mask_isch"></div>');

                        if(applied_rule.rule.action == "hide") {
                            $(node).find("*.gso_killed_serpimg_warn").css("opacity", "0.8");
                            $(node).find("*.gso_killed_serpimg_warn").html("&#x26A0; " + msg);
                            $(node).append('<div class="gso_killed_serpimg_warn gso_killed_img_mask_isch gso_serp_description_a"></div>');
                        }
                        if(applied_rule.rule.action == "hide_description_warn" ||
                           applied_rule.rule.action == "warn") {
                            $(node).find("*.gso_killed_serpimg_warn").css("opacity", "0.8");
                            $(node).find("*.gso_killed_serpimg_warn").html("&#x26A0; " + msg);
                            $(node).find("*.gso_killed_serpimg_warn").addClass("gso_serp_description_a");
                        }
                        if(applied_rule.rule.action == "hide_absolutely") {
                            $(node).find("*.gso_killed_serpimg_warn").css("opacity", "1");
                        }
                    }
                } else if(config.config.always_log_checked_entries) {
                    gso_log_append("img",
                                   null,
                                   null,
                                   context.title,
                                   context.target,
                                   null,
                                   "allow",
                                   "allow",
                                   true);
                }
            }
            $(node).addClass("gso_checked");
            gso_log_setBoundary();
        }
        update_gso_control_msg();
    }
    function check_elem_kw(node) {
        /* ---------- 関連語句 ----------  */
        if(!$(node).is(".gso_checked")) {
            $(node).addClass("gso_checked");
            var context =
                {
                    "related_kw": $(node).text(),
                    "matched_rules": null
                };
            context.matched_rules = check(null, null, null, context.related_kw, null);
            
            if(context.matched_rules.length > 0) {
                var applied_rule = get_most_significant_rule(context.matched_rules);
                context.matched_rules.forEach(function(element, index, array) {
                    element.forEach(function (e) {
                        gso_log_append("suggest",
                                       null,
                                       e.matched,
                                       context.related_kw,
                                       null,
                                       e.ruleset_id,
                                       e.rule.action,
                                       applied_rule.rule.action,
                                       !(e.effective !== undefined && e.effective));
                    });
                });
                if(applied_rule.rule.action == "hide") {
                    $(node).replaceWith('<span class="gso_killed_kw">' +
                                        '<span class="gso_killed_kw_bad gso_serp_description_b">' +
                                        context.related_kw +
                                        ' <span style="background-color: silver; color: dimgray;" title="' +
                                        config.rulesets[applied_rule.ruleset_id].name + '">×</span>' +
                                        '</span>' +
                                        '<span class="gso_serp_description_a" style="opacity: 0;">***</span></span>');
                } else if(applied_rule.rule.action == "warn") {
                    $(node).after(' <span style="background-color: silver; color: dimgray;" title="' +
                                        config.rulesets[applied_rule.ruleset_id].name + '">&#x26A0;</span>');
                } else if(applied_rule.rule.action == "hide_absolutely") {
                    $(node).replaceWith('<span class="gso_killed_kw">' +
                                        '<span class="gso_killed_kw_bad gso_serp_description_a" style="opacity: 0;">***</span>' +
                                        '<span class="gso_killed_kw_placeholder gso_killed_kw_bad gso_serp_description_b">' +
                                        config.rulesets[applied_rule.ruleset_id].name +
                                        '</span></span>');
                }
                gso_log_setBoundary();
                update_kw();
                update_gso_control_msg();
                return (applied_rule.rule.action != "allow");
            } else if(config.config.always_log_checked_entries) {
                gso_log_append("suggest",
                               null,
                               null,
                               context.related_kw,
                               null,
                               null,
                               "allow",
                               "allow",
                               true);
            }
            if(config.config.force_keyword_exclusion_on_suggestion) {
                var keywords = [];
                var params_raw = "";
                var params = {};
                var temp_rulesets = {
                    "MINUS": {
                        "name": cat[config.config.gso_lang].full.msg.excludedKeyword,
                        "enabled": true,
                        "rules": []
                    }
                };

                if(location.href.search("#") >= 0) {
                    params_raw = location.href.split("#")[1];
                    params_raw.split("&").forEach(function(element, index, array) {
                        params[element.split("=")[0]] = element.split("=")[1];
                    });
                } else {
                    params_raw = location.href.split("?")[1];
                    params_raw.split("&").forEach(function(element, index, array) {
                        params[element.split("=")[0]] = element.split("=")[1];
                    });
                }
                if(params.q !== undefined) {
                    keywords = params.q.split("+").filter(function(element) {
                        return element.startsWith("-");
                    });
                }
                keywords.forEach(function(element) {
                    temp_rulesets.MINUS.rules.push({
                        "target": "suggest",
                        "type": "word",
                        "action": "hide",
                        "level": 0,
                        "criteria": decodeURI_s(element.substr(1)),
                        "comment": "",
                        "enabled": true
                    });
                });
                var matched_rules_exclusion = check(null, null, null, context.related_kw, temp_rulesets);
                matched_rules_exclusion.forEach(function(element, index, array) {
                    element.forEach(function (e) {
                        gso_log_append("suggest_excl",
                                       null,
                                       e.matched,
                                       context.related_kw,
                                       null,
                                       null,
                                       e.rule.action,
                                       e.rule.action,
                                       false);
                    });
                });
                if(matched_rules_exclusion.length > 0) {
                    gso_log_setBoundary();
                    $(node).hide();
                }
            }
        } else {
            return false;
        }
    }
    
    function check_autocomplete() {
        /* 検索語句入力欄のオートコンプリートをチェック
           (関連語句のみ) */
        mo_autocomplete.disconnect();
        $("#sbtc li.gsfs").each(function() {
            
            var context =
                {
                    "autocomplete" : $(this).find("div.sbqs_c").text(),
                    "matched_rules": null
                }; 
            context.matched_rules = check(null, null, null, context.autocomplete, null);

            if(context.matched_rules.length > 0) {
                var applied_rule = get_most_significant_rule(context.matched_rules);
                context.matched_rules.forEach(function(element, index, array) {
                    element.forEach(function (e) {
                        gso_log_append("autocomplete",
                                       null,
                                       e.matched,
                                       context.autocomplete,
                                       null,
                                       e.ruleset_id,
                                       e.rule.action,
                                       applied_rule.rule.action,
                                       !(e.effective !== undefined && e.effective));
                    });
                });
                if(applied_rule.rule.action != "allow") {
                    $(this).addClass("gso_killed_kw_autocomplete");
                    $("#gso_results_msg_eff").show().fadeOut("slow");
                } else {
                    $(this).removeClass("gso_killed_kw_autocomplete");
                }
            } else {
                $(this).removeClass("gso_killed_kw_autocomplete");
            }
        });
        gso_log_setBoundary();
        
        mo_autocomplete.observe(document.getElementById("sbtc"),
                                {attributes: true, childList: true, characterData: true, subtree: true});
        
    }
    
    function update_serp() {
        if(status.show_serp) {
            $("*.gso_serp_description_a:not(*.gso_ani,*.gso_killed_kw_bad,*.gso_killed_kw_placeholder,*.gso_killed_serpimg_warn)").hide();
            $("*.gso_serp_description_b:not(*.gso_ani,*.gso_killed_kw_bad,*.gso_killed_kw_placeholder,*.gso_killed_serpimg_warn)").show();
            $("*.gso_serp_description_a.gso_ani:not(*.gso_killed_kw_bad,*.gso_killed_kw_placeholder,*.gso_killed_serpimg_warn)").slideUp("fast");
            $("*.gso_serp_description_b.gso_ani:not(*.gso_killed_kw_bad,*.gso_killed_kw_placeholder,*.gso_killed_serpimg_warn)").slideDown("fast");
        } else {
            $("*.gso_serp_description_a:not(*.gso_ani,*.gso_killed_kw_bad,*.gso_killed_kw_placeholder,*.gso_killed_serpimg_warn)").show();
            $("*.gso_serp_description_b:not(*.gso_ani,*.gso_killed_kw_bad,*.gso_killed_kw_placeholder,*.gso_killed_serpimg_warn)").hide();
            $("*.gso_serp_description_a.gso_ani:not(*.gso_killed_kw_bad,*.gso_killed_kw_placeholder,*.gso_killed_serpimg_warn)").slideDown("fast");
            $("*.gso_serp_description_b.gso_ani:not(*.gso_killed_kw_bad,*.gso_killed_kw_placeholder,*.gso_killed_serpimg_warn)").slideUp("fast");
        }
    }
    function update_img() {
        if(status.show_img) {
            $("*.gso_killed_serpimg_warn.gso_serp_description_b").show();
            $("*.gso_killed_serpimg_warn.gso_serp_description_a").hide();
        } else {
            $("*.gso_killed_serpimg_warn.gso_serp_description_a").show();
            $("*.gso_killed_serpimg_warn.gso_serp_description_b").hide();
        }
        /* slideToggle ではなんかバグる */
    }
    function update_kw() {
        if(status.show_kw) {
            $("span.gso_killed_kw span.gso_serp_description_a").hide();
            $("span.gso_killed_kw span.gso_serp_description_b").show();
        } else {
            $("span.gso_killed_kw span.gso_serp_description_a").show();
            $("span.gso_killed_kw span.gso_serp_description_b").hide();
        }
    }

    function update_gso_control_msg() {
        /* 結果表示 */
        var count_totalSERP = $("*.gso_killed_serp").size();
        var count_totalSERPdesc = $("*.gso_titleonly_serp").size();
        var count_totalSERPimg = $("*.gso_killed_serpimg").size();
        var count_totalKW = $("*.gso_killed_kw").size();
        var count_totalIK = $("span.gso_ignored_kw:visible").size();

        $("#gso_killed_count_s").html(cat[config.config.gso_lang].full.msg.ctlmsgSERP + ": " + (count_totalSERP + count_totalSERPdesc));
        $("#gso_killed_count_si").html(cat[config.config.gso_lang].full.msg.ctlmsgIMG + ": " + count_totalSERPimg);
        $("#gso_killed_count_k").html(cat[config.config.gso_lang].full.msg.ctlmsgKW + ": " + count_totalKW);

        if(count_totalSERP > 0 || count_totalSERPdesc > 0) {
            $("#gso_killed_count_s").parent().show();
        } else {
            $("#gso_killed_count_s").parent().hide();
        }
        if(count_totalSERPimg > 0) {
            $("#gso_killed_count_si").parent().show();
        } else {
            $("#gso_killed_count_si").parent().hide();
        }
        if(count_totalKW > 0) {
            $("#gso_killed_count_k").parent().show();
        } else {
            $("#gso_killed_count_k").parent().hide();
        }
        if(count_totalIK > 0) {
            if(config.config.message_location == "page") {
                $("#gso_count_ik").html(cat[config.config.gso_lang].full.msg.ctlmsgMIS + " [" + count_totalIK + "]<br>" +
                                        "<a href='" + location.href + "&tbs=li:1'>" +
                                        cat[config.config.gso_lang].full.msg.ctlmsgVerbatim + "</a>");
            } else {
                $("#gso_count_ik").html(cat[config.config.gso_lang].full.msg.ctlmsgMIS + " [" + count_totalIK + "] " +
                                        "<a href='" + location.href + "&tbs=li:1'>" +
                                        cat[config.config.gso_lang].full.msg.ctlmsgVerbatim + "</a>");
            }
            $("#gso_count_ik").show();
        } else {
            $("#gso_count_ik").hide();
        }
        if(count_totalSERP > 0 ||
           count_totalSERPdesc > 0 ||
           count_totalSERPimg > 0 ||
           count_totalKW > 0) {
            if(config.config.message_location == "page") {
                $("#gso_results_msg_top").html(cat[config.config.gso_lang].full.msg.ctlmsgBad + "<br>" + cat[config.config.gso_lang].full.msg.ctlmsgBadB);
            } else {
                $("#gso_results_msg_top").html(cat[config.config.gso_lang].full.msg.ctlmsgBad + cat[config.config.gso_lang].full.msg.ctlmsgBadB);
            }
        } else if(count_totalIK > 0) {
            $("#gso_results_msg_top").html(cat[config.config.gso_lang].full.msg.ctlmsgBad);
        } else {
            $("#gso_results_msg_top").html(cat[config.config.gso_lang].full.msg.ctlmsgGood);
        }
    }

    function hide_moshikashite() {
        if(config.config.hide_moshikashite && $("p.ssp").size() > 0 && location.href.match("start=") !== null) {
            $("p.ssp").hide();
        }
    }
    function check_elem_first() {
        check_elem_all();
    }

    function check_elem_all() {
        if(location.href.search("&tbm=isch&") == -1){
            $(selector_SERP).not("*.gso_checked").each(function () {
                check_elem_serp(this);
            });

            /* ---------- 画像検索結果(リンク外す) ---------- */
            /* 隠す対象となりうる要素 */
            var entries = $(selector_IMG).not("*.gso_checked");
            entries.each(function() {
                check_elem_img(this);
            });
        }
        /* ---------- 関連キーワード ---------- */
        
        /* top: div#trev > div > a.nobr
           bottom: div.brs_col > p._e4b > a */

        $(selector_KW).not("*.gso_checked").each(function() {
            check_elem_kw(this);
        });

        /* ---------- [画像]モードの検索結果 ---------- */
        if(location.href.search("&tbm=isch&") >= 0 && config.config.check_for_image){
            $(selector_IMGLIST).not("*.gso_checked").each(function() {
                check_elem_imglist(this);
            });
        }
        hide_moshikashite();
    }
    
    
})();