SOURCE

console 命令行工具 X clear

                    
>
console
/* 补丁测试 1 :那么仿造的事件能用来选择吗? */
(function(){
    var shiftDown = false;
    var needHook = true;
    function cloneAndShift(e){
        var obj = {};
        for(var i in e){
            if(i[0] != i[0].toLowerCase()) continue;
            obj[i] = e[i]
        }
        obj.target = e.target;
        obj.shiftKey = true;
        debugger;
        return obj;
    }
    function keyHook(e){
        if(!(e instanceof KeyboardEvent)) return;
        if(!(e instanceof KeyboardEvent)) throw "Fuck!";
        if(!e.isTrusted){return;}
        if(e.target.matches(".nofix, .nofix *")) return;
        if(e.key.includes("Shift")){
            if(e.type == "keydown"){
                shiftDown = true;
            } else if(e.type == "keyup"){
                shiftDown = false;
            }
        } else {
            if(e.shiftKey == true && shiftDown == true){
                needHook = false;
                has_bug.innerHTML = "No!"
                document.removeEventListener("keydown", keyHook, true);
                document.removeEventListener("keyup", keyHook, true);
            }

            if(e.shiftKey == false && shiftDown == true){
                /* Hook! */
                var fakedEvent = new KeyboardEvent(e.type,cloneAndShift(e));
                e.stopImmediatePropagation();
                e.preventDefault();
                var res = e.target.dispatchEvent(fakedEvent);
                has_bug.innerHTML = "Yes!"
                /* 模仿默认行为 */
                if(!res) return;
                if(e.type != "keydown") return;
                var textarea = e.target;
                if(!(textarea instanceof HTMLTextAreaElement)) return;
                if(!(textarea instanceof HTMLTextAreaElement)) throw "";
                if(e.key == "ArrowLeft"){
                    if(textarea.selectionEnd == textarea.selectionStart){
                        textarea.setSelectionRange(Math.max(textarea.selectionStart-1,0),textarea.selectionEnd,"backward")
                    } else if(textarea.selectionDirection == "forward"){
                        textarea.setSelectionRange(textarea.selectionStart,textarea.selectionEnd-1,"forward")
                    } else {
                        textarea.setSelectionRange(Math.max(textarea.selectionStart-1,0),textarea.selectionEnd,"backward")
                    }
                } else if(e.key == "ArrowRight"){
                    if(textarea.selectionEnd == textarea.selectionStart){
                        textarea.setSelectionRange(textarea.selectionStart,textarea.selectionEnd+1,"forward")
                    } else if(textarea.selectionDirection == "forward"){
                        textarea.setSelectionRange(textarea.selectionStart,textarea.selectionEnd+1,"forward")
                    } else {
                        textarea.setSelectionRange(textarea.selectionStart+1,textarea.selectionEnd,"backward")
                    }
                }
            }
        }
    }
    document.addEventListener("keydown", keyHook, true);
    document.addEventListener("keyup", keyHook, true);
})();



/* 键盘事件测试程序 */
(function(){
    /**
     * htmlEncode 把一段文字用 HTML 实体编码。
     * @param {string} str 被编码的字符串
     * @return {string} 返回一个你可以放心放到 HTML 里面的字符串。
     */
    function htmlEncode(str){
        if(!htmlEncode.textarea){
            htmlEncode.textarea = document.createElement("div");
        }
        htmlEncode.textarea.textContent = str;
        return htmlEncode.textarea.innerHTML;
    }

    var select = document.querySelector("#key_test_col_select");
    var table  = document.querySelector("#key_test_result");
    var target = document.querySelector("#key_test_target");
    var cols   = new Set();
    var events = [];
    var init = function(){
        if(!(select instanceof HTMLSelectElement)) throw "Fuck 1";
        if(!(table instanceof HTMLTableElement)) throw "Fuck 2";
        cols.clear();
        cols.add("type");
        events = [];
        refreshSelect();
        refreshTable();
    }

    function refreshSelect(){
        if(!(select instanceof HTMLSelectElement)) throw "Fuck 1";
        var set2 = new Set(Array.from(select.options).map(function(option){
            return option.textContent;
        }));
        for(var i of cols){
            if(!set2.has(i)){
                var option2 = document.createElement("option");
                option2.textContent = i;
                select.appendChild(option2);
                set2.add(i);
            }
        }
    }
    var refreshTable = function(){
        if(!(select instanceof HTMLSelectElement)) throw "Fuck 1";
        var html = "";
        var selectedHeaders = Array.from(select.selectedOptions).map(function(option){
            return option.textContent;
        });
        if(selectedHeaders.length == 0){
            table.innerHTML = "";
            return;
        }
        html += "<tr>" + selectedHeaders.map(function(str){
            return "<th>" + htmlEncode(str) + "</th>";
        }).join("") + "</tr>";

        for(var event of events){
            html += "<tr>";
            for(var i of selectedHeaders){
                html += "<td>" + htmlRepresent(event[i]) + "</td>";
            }
            html += "</tr>"
        }
        table.innerHTML = html;
        if(window.hljs){
            document.querySelectorAll('code[class*=lang]').forEach((el) => {
                hljs.highlightElement(el);
            });
        }
    }
    function htmlRepresent(val){
        if(val === undefined) return "<i class='h'>(未定义)</i>";
        if(val === null)      return "<i class='h'>(null)</i>"
        if(typeof val == "function") return "<i class='h'>function(){}</i>";
        try{
        
            if(typeof val == "object" && Object.prototype.toString.call(val) != "[object Object]"){
                return "<i class='h'>(复杂对象)</i>"
            }
            return "<code class='language-javascript'>" + htmlEncode(JSON.stringify(val)) + "</code>";
        
        }catch(e){
            return "<i class='h'>出错</i>"
        };
        
    }

    function listener(e){
        if(!(e instanceof Event)) return;
        for(var i in e){
            if(e[i] !== undefined && i[0].toLowerCase() == i[0])
            cols.add(i);
        }
        events.push(e);
        refreshSelect();
        refreshTable();
    }

    select.onchange = refreshTable;
    target.addEventListener("keydown", listener);
    target.addEventListener("keyup", listener);
    init();
})();
<html lang="zh">

<head>
	<meta chatset="utf-8" />
	<title>输入法选择 Bug</title>
	<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">
</head>

<body>
	
	<h1>部分 Android 输入法与 Chrominum 浏览器存在不兼容问题</h1>
	<p>众所周知,这不是什么奇怪问题。手机市场,群妖乱舞。</p>
	<p>Bug 修复前:</p>
	<textarea class="nofix">没人拦你选择这些文字,但是,如果输入法上面的选择按钮失灵,那么神仙也救不了你。</textarea>
    <p>Bug 修复后:</p>
    <textarea>这个键盘的选择效果是由脚本模仿的,但是只模仿了左和右……</textarea>
    <p>检测到 Bug?(<span id="has_bug">?</span>)</p>
    <p>
        这个“修复”只限于
        <code class="language-html">&lt;textarea&gt;</code>
        并且很容易迁移到
        <code class="language-html">&lt;input/&gt;</code>
        。其他的与文本选择相关的大多数是由 DOM Selection API 控制,逻辑
        过于复杂,暂不考虑。
    </p>
    
    <hr>

    <p>万物元凶(除了他还有 Chrominum ,这一 Bug 不在其他 App 中出现):</p>
    <figure style="text-align: center">
        <img alt="百度输入法定制版选择面板" src="data:image/png;base64,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" />
        <figcaption>百度输入法定制版选择面板</figcaption>
    </figure>
    <p>它发送不正常的键盘事件。以向左选择为例:</p>
    <table>
        <tr><th>type</th><th>key</th><th>shiftKey</th></tr>
        <tr><td>keydown</td><td>Shift</td><td><mark>false</mark></td></td>
        <tr><td>keydown</td><td>ArrowLeft</td><td><mark>false</mark></td></td>
        <tr><td>keyup</td><td>ArrowLeft</td><td><mark>false</mark></td></td>
        <tr><td>keyup</td><td>Shift</td><td>false</td></td>
    </table>
    <p>
        这是一个典型的异常键盘事件序列,shiftKey标识错误。
    </p>
    <p>
        不同输入法的处理不同。在某些浏览器与某些输入法的组合中,<code>shiftKey</code>被正确设置,
        而没有 Shift 键的按下事件。
    </p>
    <p>
        这些差异导致千奇百怪的行为,让许多代码编辑器脚本以及部分新式可视化编辑器失效
        (它们一般自己处理键盘事件),而同时,
        这一现象可能揭示出来,即便是 Chrominum 也不能正确应对安卓千奇百怪的生态。
    </p>
    <hr>

    <h2>附:键盘事件测试器</h2>
    <button onclick="key_test_target.classList.toggle('nofix')">切换修复标记</button>
    <textarea id="key_test_target"  class="nofix">
        字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。
        字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。字数补丁。
    </textarea>
    <p></p>
    <label for="key_test_col_select">选择列:</label>
    <select id="key_test_col_select" multiple></select>
    <table id="key_test_result"></table>
    <!-- 样式补丁 -->
    <link href="https://cdn.bootcdn.net/ajax/libs/highlight.js/11.7.0/styles/github.min.css" rel="stylesheet">
    <script delay>
        function highlight(){
            
            document.querySelectorAll('code[class*=lang]').forEach((el) => {
                hljs.highlightElement(el);
            });
        }
        
    </script>
    <script async onload="highlight()" src="https://cdn.bootcdn.net/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
    
</body>
</html>
/* Not all styles are here */
p{text-align: justify;}
select{width: 100%;}
.h:not(:hover){opacity: 0.5}
.nofix{
    background: rgb(239,228,176)!important;
}
tr:first-child{
    position: sticky;
    top: 1em;
    height: 1.4em;
    background: rgb(153,217,234);
    
}