console
(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){
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(){
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"><textarea></code>
并且很容易迁移到
<code class="language-html"><input/></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>
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);
}