Я разрабатываю расширение Chrome для быстрого доступа к билетам JIRA.Таблица с текстовыми элементами «случайно» очень медленно реагирует на события ввода / страницы, такие как on hover
, focus
и text input
, иногда для ответа требуется НЕСКОЛЬКО секунд.
IЯ бы предпочел не выпускать это, не понимая, что происходит и масштаб воздействия.Я действительно просто хочу понять, что происходит.
Это ТОЛЬКО происходит, когда я нахожусь на внешнем дисплее (MacBook Pro 13 дюймов, 2017 г., 8 ГБ через концентратор USB-C).
Я заметил: 1) Один и тот же код на более мощных машинах работает нормально, используя те же периферийные устройства. 2) Все работает нормально, когда я перетаскиваю окно браузера на дисплей ноутбука. 3) Это происходит на первый взгляд случайным образом в зависимости отположение ввода в всплывающем окне.Изменение размера верхнего и нижнего полей часто помогает решить проблему или переместить ее в другой ряд.
Я создал функцию для изменения размера верхнего и нижнего полей в зависимости от количества элементов, но это только смягчает проблему и не полностью решает ее.
FWIW, это не похоже наиспользуется ужасное количество памяти, и система обнаруживает входные данные, когда замечает ключевые события.Текстовые поля с проблемами не занимают больше места на процессоре / памяти, чем те, которые этого не делают.
popup.html
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<link href="https://fonts.googleapis.com/css?family=IBM+Plex+Mono:400,400i|IBM+Plex+Sans+Condensed:400,400i|IBM+Plex+Sans:100,100i,400,400i,700,700i|IBM+Plex+Serif:400,400i" rel="stylesheet">
<link rel="stylesheet" href="popup-style.css">
<style type="text/css"> </style>
<meta charset="utf-8">
<title>Quick Ticket For JIRA</title>
</head>
<body>
<div class="NewJIRAForm">
<div class="header">
<h1>QUICK TICKET FOR JIRA</h1>
<div class="QuickJIRAcontrols">
<input type="button" id='optionsButton' value="Settings" class="controlButtons">
<input type="button" id='toggleAddNewButton' value="Add New" class="controlButtons">
</div>
</div>
<div class="addNewForm" id="addNewForm">
<div class="addFirstRowForm">
<input type="text" name="newJIRABoardName" placeholder="Name of your New JIRA board" class="newJIRABoardNameInputField" id="newJIRABoardName">
<input type="text" name="newJIRABoardKey" placeholder="Project Key" class="newJIRABoardKeyInputField" id="newJIRAProjectKey" >
</div>
<div class="addSecondRowForm">
<input type="text" name="newJIRABaseURL" placeholder="Base URL of your JIRA board" class="newJIRABaseURLField" id='newJIRABaseURL'>
<input type="button" id='addNewButton' value="Add">
</div>
</div>
</div>
<div id='CurrentQuickJIRAs' class="CurrentQuickJIRAs">
</div>
</body>
<script src="popup.js"></script>
</html>
popup.js
document.addEventListener('DOMContentLoaded', documentEvents , false);
// using local array to avoice async issues;
var localQuickJiraArray = [];
var addNewQuickJIRAIsShown;
var currentNewJIRABoardNameTextInput;
var currentNewJIRABoardURLTextInput;
var currentNewJIRABoardProjectKeyTextInput;
function documentEvents() {
document.getElementById('addNewButton').addEventListener('click',
function() { addNewQuickJIRA(document.getElementById('newJIRABaseURL') , document.getElementById('newJIRABoardName'), document.getElementById('newJIRAProjectKey'));
});
document.getElementById('toggleAddNewButton').addEventListener('click',
function() { toggleAddNewQuickJIRA();
});
document.getElementById('optionsButton').addEventListener('click',
function() { chrome.tabs.create({'url': "/options.html" } );
});
document.getElementById("newJIRABoardName").addEventListener("keyup", saveNewFormData);
document.getElementById("newJIRAProjectKey").addEventListener("keyup", saveNewFormData);
document.getElementById("newJIRABaseURL").addEventListener("keyup", saveNewFormData);
//sync local array of QuickJIRAs with Chrome Storage
chrome.storage.sync.get(function(data) {
if (Object.keys(data).length > 0 && data.QuickJIRAs ) {
localQuickJiraArray = data.QuickJIRAs;
}
chrome.storage.sync.set(data, function() {
drawCurrentQuickJIRATable();
// logStorage();
});
});
}
function saveNewFormData() {
currentNewJIRABoardNameTextInput = document.getElementById("newJIRABoardName").value;
currentNewJIRABoardURLTextInput = document.getElementById("newJIRABaseURL").value;
currentNewJIRABoardProjectKeyTextInput = document.getElementById("newJIRAProjectKey").value;
if ( currentNewJIRABoardNameTextInput == "" && currentNewJIRABoardURLTextInput == "" && currentNewJIRABoardProjectKeyTextInput == "" ) {
isDataToPersist = false;
} else {
isDataToPersist = true;
}
chrome.runtime.sendMessage({
currentNewJIRABoardNameTextInput: currentNewJIRABoardNameTextInput,
currentNewJIRABoardURLTextInput: currentNewJIRABoardURLTextInput,
currentNewJIRABoardProjectKeyTextInput: currentNewJIRABoardProjectKeyTextInput,
isDataToPersist: isDataToPersist
},
function(response) {
if (response == "saved") {
} else {
throw new Error("Error Persisting data while adding Quick Ticket");
}
}
);
}
function drawCurrentQuickJIRATable() {
chrome.storage.sync.get(function(data){
// logStorage();
if (Object.keys(data).length> 0 && data.QuickJIRAs != undefined && data.QuickJIRAs.length > 0) {
// Clear current jiras
document.getElementById('CurrentQuickJIRAs').innerHTML = '' ;
//Draw New Table
//**could improve by just adding the last one and not clearing
for (var i = 0; i < data.QuickJIRAs.length; i++) {
var JIRABoardID = data.QuickJIRAs[i].JIRABoardName;
var JIRABoardBaseURL = data.QuickJIRAs[i].JIRABaseURL;
document.getElementById('CurrentQuickJIRAs').innerHTML += '<div class="quickJIRARow">' +
'<a class="goNewLink" href="' + data.QuickJIRAs[i].JIRABaseURL + "/" + data.QuickJIRAs[i].JIRAProjectKey + ' "> ' + data.QuickJIRAs[i].JIRABoardName + '</a>' +
'<input type="text" placeholder="Ticket Number" id="inputFieldBoardID_' + i + '" ' + ' class="goToInput">' +
'<div class="quickJIRARowButtons">' +
'<button type="button" id="goNewButton_' + i + '" ' + ' class="goNewButton">New Tab</button>' +
'<button type="button" id="goCurrentButton_' + i + '" ' + ' class="goCurrentButton">Current Tab</button>' +
'<button type="button" id="removeButton_' + i + '" ' + ' class="removeButton">Remove</button>' +
'</div>'
'</div>' ;
}
//add click function to link;
var jiraBoardLinks = document.getElementsByTagName("a");
for (var i = 0; i < jiraBoardLinks.length; i++) {
(function () {
var link = jiraBoardLinks[i];
var url = link.href;
link.onclick = function () {
chrome.tabs.create({active: true, url: url});
};
})();
}
// Set Go New Button Click Event
var goButtonArray = document.getElementsByClassName('goNewButton');
for (var x = 0; x < goButtonArray.length; x++) {
var elementID = "goNewButton_" + String(x);
document.getElementById(elementID).addEventListener('click', function () {
var newTab = true;
navigateToQuickJIRA(this, newTab);
});
}
// Set Go Current Button Click Event
var goButtonArray = document.getElementsByClassName('goCurrentButton');
for (var x = 0; x < goButtonArray.length; x++) {
var elementID = "goCurrentButton_" + String(x);
document.getElementById(elementID).addEventListener('click', function () {
var newTab = false;
navigateToQuickJIRA(this, newTab);
});
}
// Set Go Remove Button Click Event
var goButtonArray = document.getElementsByClassName('removeButton');
for (var x = 0; x < goButtonArray.length; x++) {
var elementID = "removeButton_" + String(x);
document.getElementById(elementID).addEventListener('click', function () {
removeJIRA(this);
});
}
// Set Go To JIRA in current Tab on Return and
var goToInputArray = document.getElementsByClassName('goToInput');
for (var x = 0; x < goToInputArray.length; x++) {
var elementID = "inputFieldBoardID_" + String(x);
document.getElementById(elementID).addEventListener("keyup", function (event) {
if (event.key === "Enter") {
var newTab = false;
navigateToQuickJIRA(this, newTab);
this.value = "";
}
});
}
//To-Do: Add Options to Keep Add Window open
toggleAddNewQuickJIRA(false);
} else {
document.getElementById('CurrentQuickJIRAs').innerHTML = '' ;
toggleAddNewQuickJIRA(true);
}
checkIfExistingAddNewFields()
});
}
function handleInputFromGoToInput(textfield) {
}
//Check to see if user is mid adding a new JIRA
function checkIfExistingAddNewFields() {
// Send message to background to get current input values if user was mid input and closed the popup
chrome.runtime.sendMessage({
checkingForData : true
},
function(response) {
if (response.currentNewJIRABoardNameTextInput != undefined && response.currentNewJIRABoardURLTextInput != undefined && response.currentNewJIRABoardProjectKeyTextInput != undefined) {
document.getElementById("newJIRABoardName").value = response.currentNewJIRABoardNameTextInput;
document.getElementById("newJIRABaseURL").value = response.currentNewJIRABoardURLTextInput;
document.getElementById("newJIRAProjectKey").value = response.currentNewJIRABoardProjectKeyTextInput;
toggleAddNewQuickJIRA(true);
}
}
);
}
function navigateToQuickJIRA (button, newTab) {
// Get the value of button Id to use to map to coresponding textfield
var idValue = String(button.id).split("_").pop();
var goToInputId = "inputFieldBoardID_" + idValue;
var goToInputIdValue = document.getElementById(goToInputId).value;
//Get base URL value from Storage
// ** Change to local Array
var projectKey = localQuickJiraArray[idValue].JIRAProjectKey;
var goToInputIdValue= validateTicketNumberInput(goToInputIdValue, projectKey);
chrome.storage.sync.get(function(data){
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
} else {
console.log(data);
var jiraBoardID = data.QuickJIRAs[idValue].JIRABaseURL;
var jiraURL = jiraBoardID + "/"+ goToInputIdValue;
//Need function to avoid race-condition, could probably use callback in the future
goToInputIdValue.value = "";
gotoNewURL(jiraURL, newTab);
}
});
}
function validateTicketNumberInput(goToInputIdValue, projectKey){
if (goToInputIdValue.length > projectKey.length) {
var inputToCheckForProjectKey = goToInputIdValue.substr(0, projectKey.length+1);
}
//Check if input is just numbers - if yes, add the project key to the front and dash - it's valid
//Check if input is contains valid project key in the right location with a dash - if yes - it's valid
//else - not a valid input - throw error
if (/^\d+$/.test(goToInputIdValue)) {
var sanitizedInput = projectKey + "-" + goToInputIdValue;
return sanitizedInput;
} else if (inputToCheckForProjectKey !== 'undefined' && inputToCheckForProjectKey == projectKey + "-") {
return goToInputIdValue;
} else {
alert("It looks like your ticket number isn't valid for this Project. Please enter a valid ticket number.");
throw new Error("Invald Ticket Number Entered");
}
}
function gotoNewURL(jiraURL, newTab) {
if (newTab == false) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var tab = tabs[0];
chrome.tabs.update(tab.id, { url : jiraURL });
});
} else {
chrome.tabs.create({ url : jiraURL });
}
}
function removeJIRA(button) {
var doesWantToDelete = confirm("Do you really want to Delete that link?");
if (doesWantToDelete == true ) {
var idValue = String(button.id).split("_").pop();
//Get base URL value from Storage
chrome.storage.sync.get(function(data){
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
} else {
data.QuickJIRAs.splice(idValue, 1);
localQuickJiraArray.splice(idValue, 1);
}
chrome.storage.sync.set(data, function() {
//Race condition - use callback in the future
drawCurrentQuickJIRATable();
});
});
}
}
function addNewQuickJIRA(baseURLInput , boardNameInput, projectKeyInput) {
if (baseURLInput.value == "" || boardNameInput.value == "" || projectKeyInput.value == "") {
alert("Looks like some fields are missing. Please fill in all fields before adding a new Quick JIRA");
throw new Error("missing fields upon Quick JIRA submission");
}
var baseURL = baseURLInput.value.replace(/\/$/, '');
baseURL = validateBaseURL(baseURL);
var boardName = validateBoardName(boardNameInput.value.trim());
var projectKey = validateProjectKey(projectKeyInput.value.trim());
var doesExist = checkIfInputDataDoesExist(baseURL, boardName, projectKey);
if (doesExist == false) {
chrome.storage.sync.get(function(data) {
var objectToAdd = {'JIRABaseURL' : baseURL , 'JIRABoardName' : boardName , 'JIRAProjectKey' : projectKey};
if (Object.keys(data).length > 0 && data.QuickJIRAs ) {
data.QuickJIRAs.push(objectToAdd);
localQuickJiraArray.push(objectToAdd);
} else {
data.QuickJIRAs = [objectToAdd];
localQuickJiraArray = [objectToAdd];
}
baseURLInput.value = ""
boardNameInput.value = ""
projectKeyInput.value = ""
saveNewFormData();
chrome.storage.sync.set(data, function() {
drawCurrentQuickJIRATable();
// logStorage();
});
});
}
}
function checkIfInputDataDoesExist(baseURL, boardName, projectKey) {
if (localQuickJiraArray.length != 0) {
//check for existing baseURL and board name; set doesExist to true if one does
//Removed checking for Base URL's since it's possible there are multiple projects on the same URLs
for (var i = 0; i < localQuickJiraArray.length; i++) {
var existingJiraBoardID = localQuickJiraArray[i].JIRABoardName;
var existingJiraBoardBaseURL = localQuickJiraArray[i].JIRABaseURL;
var existingJiraProjectKey = localQuickJiraArray[i].JIRAProjectKey;
if (boardName == existingJiraBoardID) {
alert("Looks like you already a QuickJIRA with that Name. \n \n Please try adding a your Quick JIRA with a new name");
return Boolean(true);
} else if (projectKey == existingJiraProjectKey) {
alert("Looks like you already a QuickJIRA with that Project Key. \n \n Please try adding a your Quick JIRA with a newJIRAProjectKey");
return Boolean(true);
}else {
return Boolean(false);
}
}
} else {
return Boolean(false);
}
}
function validateBaseURL(baseURLInput) {
var regexQuery = "^(https?://)?(www\\.)?([-a-z0-9]{1,63}\\.)*?[a-z0-9][-a-z0-9]{0,61}[a-z0-9]\\.[a-z]{2,6}(/[-\\w@\\+\\.~#\\?&/=%]*)?$";
var regexTest = new RegExp(regexQuery, 'i');
var isValidURL = regexTest.test(baseURLInput);
if (isValidURL == true) {
var arrayOfMatches = baseURLInput.match(/.*\/(.*)$/);
if (arrayOfMatches.length < 2 || arrayOfMatches[1].toLowerCase() != "browse") {
var confirmURL = confirm("Are you sure you want to Add this URL? \n \n Quick Ticket works best when the last part of the URL is `/browse` !");
if (confirmURL == true) {
return baseURLInput;
} else {
throw new Error("User did not want to add this URL");
}
} else {
return baseURLInput;
}
} else {
alert("That doesn't look like valid JIRA URL. \n \n Please enter a valid URL \n \n URLs should not contain Project Keys");
throw new Error("invalid JIRA entered");
}
}
function validateBoardName(boardNameInput) {
if (boardNameInput.length < 50) {
var regexQuery = "^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$";
var regexTest = new RegExp(regexQuery, 'i');
var isValidBoardNameInput = regexTest.test(boardNameInput);
if (isValidBoardNameInput == true) {
return boardNameInput;
} else {
alert("That doesn't look like valid Board Name. \n \n Project Keys may only contain letters and numbers");
throw new Error("invalid Board Name Add Attempt");
}
} else {
alert("That doesn't look like valid Board Name. \n \n Please enter a Board Name less than 50 characters");
throw new Error("invalid Board Name Add Attempt");
}
}
function validateProjectKey(projectKeyInput) {
if (projectKeyInput.length < 50) {
var regexQuery = "^[A-Z][A-Z_0-9]+";
var regexTest = new RegExp(regexQuery);
var isValidProjectKeyInput = regexTest.test(projectKeyInput);
if (isValidProjectKeyInput == true) {
return projectKeyInput;
} else {
alert("That doesn't look like valid Project Key. \n \n Project Keys may only contain Uppercase letters, numbers, and '_' ");
throw new Error("invalid Project Key Add Attempt");
}
} else {
alert("That doesn't look like valid Project Key. \n \n Please enter a Project Key less than 50 characters");
throw new Error("invalid Project Key Add Attempt");
}
}
function toggleAddNewQuickJIRA(shouldDisplay) {
var newFormDiv = document.getElementById("addNewForm");
var newFormButton = document.getElementById("toggleAddNewButton");
if (shouldDisplay == false) {
newFormDiv.style.display = "none";
addNewQuickJIRAIsShown = false;
newFormButton.value="Add New"
} else if (shouldDisplay == true) {
newFormDiv.style.display = "block";
addNewQuickJIRAIsShown = true;
newFormButton.value="Hide"
} else {
if (newFormDiv.style.display == "block") {
newFormDiv.style.display = "none";
addNewQuickJIRAIsShown = false;
newFormButton.value="Add New"
} else {
newFormDiv.style.display = "block";
addNewQuickJIRAIsShown = true;
newFormButton.value="Hide"
}
}
}
// Log Storage for debugging
function logStorage() {
if(chrome.storage) {
chrome.storage.local.get(function(data){
console.log("chrome.storage.local:");
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
} else {
console.log(data);
}
chrome.storage.sync.get(function(data){
console.log("chrome.storage.sync:");
if(chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
} else {
console.log(data);
}
});
});
} else {
console.warn("chrome.storage is not accessible, check permissions");
}
}
popup-css
h1 {
margin: 0px;
font-size: 12pt;
}
body {
width: 600px;
margin: 0px;
z-index: 1;
}
.header {
display: inline-block;
min-width: 570px;
}
.header h1{
margin: 0;
display: inline-block;
float: left;
}
.header .QuickJIRAcontrols {
float: right;
width: 275px;
margin-right: -5px;
text-align: center;
}
#toggleAddNewButton, #optionsButton {
display: inline-block;
margin: 0;
padding: 0;
height: 19px;
width: 130px;
}
#optionsButton {
float: left;
color: black;
}
#optionsButton:hover {
background-color: #7B7B7B;
color: #FFFFFF;
}
#toggleAddNewButton {
float: right;
}
.addNewForm {
display: block;
}
.NewJIRAForm {
min-width: 100%;
background-color: #D8D8D8;
padding: 15px;
padding-top: 20px;
}
.NewJIRAForm input[type=text] {
width: 375px;
}
.NewJIRAForm input[type=button] {
padding: 0px;
width: 170px;
margin-left: 15px;
color: #7ED321;
}
.NewJIRAForm input[type=button]:hover {
color: #FFFFFF;
background-color: #7ED321;
}
input[type=button]:focus, button:focus, input[type=text]:focus {
outline: none;
}
#newJIRAProjectKey {
padding-left: 10px;
width: 160px;
margin-left: 15px;
}
input[type=button], button {
height: 38px;
border-radius: 6px;
border-style: none;
font-size: 12px;
font-weight: bold;
}
.newJIRABoardNameInputField, .newJIRABaseURLField, .newJIRABoardKeyInputField {
height: 36px;
border-radius: 6px;
border-style: none;
padding-left: 10px;
}
.goToInput {
height: 36px;
border-radius: 6px;
padding-left: 10px;
width: 130px;
border-style: solid;
margin-right: 15px;
}
.addFirstRowForm, .addSecondRowForm {
margin-top: 15px;
}
.goNewButton, .goCurrentButton {
margin-right: 15px;
background-color: #7ED321;
color: white;
width: 90px;
}
.goNewButton:hover, .goCurrentButton:hover {
background-color: #417505;
}
.removeButton {
background-color: #D0021B;
color: white;
width: 65px;
}
.removeButton:hover {
background-color: #961424;
}
.removeButton:focus, .goNewButton:focus, .goCurrentButton:focus {
outline: 0;
}
.quickJIRARow {
margin-left: 15px;
margin-top: 7.5px;
z-index: 2;
display: inline-block;
}
.CurrentQuickJIRAs .quickJIRARow:last-child {
margin-bottom: 7.5px;
}
.quickJIRARowButtons {
display: inline-block;
}
.CurrentQuickJIRAs {
max-height:255px;
overflow-y:scroll;
}
.quickJIRARow a {
text-decoration: none;
color: #000;
text-align: right;
display: inline-block;
white-space: nowrap;
width: 130px;
margin-right: 10px;
font-weight: bold;
font-size: 12px;
z-index: 2;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
position: relative;
}
.quickJIRARow a:hover {
color: blue;
}
Ссылка на репозиторий (текущий выпуск, воспроизводимый в мастере): https://github.com/bzellman/quickJira