Я пишу расширение Google Chrome для внутренних пользователей нашего веб-продукта, где они могут управлять своими флагами функций. Это простой пользовательский интерфейс, который включает в себя уведомление, которое необходимо принять, текстовую область, где люди могут вводить новые флаги функций для себя, и список этих флагов с кнопкой удаления и флажком. Затем расширение автоматически добавляет флаги к параметрам запроса при каждом посещении продукта, чтобы они были включены.
Это прекрасно работает! По крайней мере, в большинстве случаев ... Я пока не смог понять, почему, но есть случаи, когда пользователи нажимают на значок, открывается всплывающее окно, а HTML-всплывающее окно не отвечает должным образом. Нажатие на переключатели не снимает флажок, нажатие кнопки удаления не удаляет флаг объекта, а при фокусировке на текстовом поле не появляется курсор или какой-либо текст после того, как вы начнете печатать. Однако если вы внесете изменения, закроете и снова откроете всплывающее окно, изменения будут сохранены.
Любая помощь будет высоко ценится.
Ниже приведен код. Нерелевантные части были заменены на SNIP
.
manifest.json
{
"name": "SNIP",
"version": "1.0",
"description": "SNIP",
"browser_action": {
"default_title": "SNIP",
"default_icon": "SNIP.png",
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"],
"persistent": false
},
"manifest_version": 2,
"permissions": [
"storage",
"webNavigation",
"tabs"
]
}
popup.html
<html>
<head>
<style>
SNIP
</style>
</head>
<body>
<h3>Important notice:</h3>
<ol>
SNIP (just some li's with text in here)
</ol>
<button id="accept-notice">Accept</button>
<div id="feature-flag-container" class="hidden">
<h3>Feature flags:</h3>
<div id="feature-flag-list"></div>
<input id="new-feature-flag" type="text" />
</div>
<script type="text/javascript" src="popup.js"></script>
</body>
</html>
popup.js
const acceptNoticeButton = document.getElementById("accept-notice");
const featureFlagContainer = document.getElementById("feature-flag-container");
const featureFlagList = document.getElementById("feature-flag-list");
const newFeatureFlagInput = document.getElementById("new-feature-flag");
let existingFlags = [];
chrome.runtime.sendMessage({ msg: "open_popup" });
chrome.runtime.onMessage.addListener(request => {
Object.keys(request.featureFlags).forEach(flag => {
addFeatureFlagNode(flag, !!request.featureFlags[flag]);
});
if (request.acceptedNotice) {
acceptNoticeButton.click();
}
});
acceptNoticeButton.onclick = () => {
chrome.runtime.sendMessage({ msg: "accept_notice" });
acceptNoticeButton.classList.add("hidden");
featureFlagContainer.classList.remove("hidden");
}
function removeFeatureFlag(e) {
const parent = e.target.parentNode;
const flag = e.target.parentNode.children[1].innerText;
existingFlags = existingFlags.filter(f => f !== flag);
parent.remove();
chrome.runtime.sendMessage({ msg: "remove_flag", flag });
}
function toggleFeatureFlag(e) {
const flag = e.target.parentNode.children[1].innerText;
const value = e.target.checked === true;
chrome.runtime.sendMessage({ msg: "toggle_flag", flag, value });
}
function addFeatureFlagNode(flag, checked) {
existingFlags.push(flag);
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = checked === false ? false : true;
checkbox.onchange = toggleFeatureFlag;
const text = document.createElement("p");
text.innerText = flag;
const del = document.createElement("button");
del.innerText = "Delete";
del.onclick = removeFeatureFlag;
const container = document.createElement("div");
container.appendChild(checkbox);
container.appendChild(text);
container.appendChild(del);
featureFlagList.appendChild(container);
}
newFeatureFlagInput.onkeydown = e => {
const flag = e.target.value;
if (e.key === "Enter" && flag && !existingFlags.includes(flag)) {
chrome.runtime.sendMessage({ msg: "add_flag", flag });
addFeatureFlagNode(flag);
e.target.value = "";
}
}
background.js
const urls = [SNIP];
const environments = [SNIP];
const worksOn = [SNIP];
const options = [];
urls.forEach(url => {
environments.forEach(environment => {
worksOn.forEach(link => {
options.push(`https://${url}${environment}.SNIP.com/${link}/`);
});
});
});
const state = { acceptedNotice: false, featureFlags: {} };
chrome.storage.sync.get(["acceptedNotice", "featureFlags"], result => {
state.acceptedNotice = result.acceptedNotice;
state.featureFlags = result.featureFlags || {};
});
chrome.runtime.onMessage.addListener(({ msg, flag, value }) => {
if (msg === "open_popup") {
chrome.runtime.sendMessage(state);
return
}
if (msg === "accept_notice") {
state.acceptedNotice = true;
} else if (msg === "add_flag") {
state.featureFlags[flag] = true;
} else if (msg === "remove_flag") {
delete state.featureFlags[flag];
} else if (msg === "toggle_flag") {
state.featureFlags[flag] = value;
}
chrome.storage.sync.set(state);
});
chrome.webNavigation.onBeforeNavigate.addListener(event => {
if (options.find(option => event.url.indexOf(option) === 0)) {
const flags = Object.keys(state.featureFlags).filter(flag => state.featureFlags[flag] === true)
const url = event.url.split("?");
const queryParams = (url[1] || "").split("&").filter(param => !!param).map(param => {
const split = param.split("=");
return { key: split[0], value: split[1] };
});
const initialLength = queryParams.length;
flags.forEach(flag => {
if (!queryParams.find(param => param.key === flag)) {
queryParams.push({ key: flag });
}
});
if (queryParams.length !== initialLength) {
chrome.tabs.update(event.tabId, {
url: `${url[0]}?${queryParams.map(param =>
param.value ? `${param.key}=${param.value}` : param.key
).join("&")}`
});
}
}
});