当值为 false 时,true 值上的复选框功能仍然执行

Checkbox function on true value still executes when value is false

在我的 Chrome 扩展中,我有一个拨动开关,它本质上是一个复选框。当我打开它并使选中的值变为真时,它执行某个功能,当我关闭它时它执行另一个功能但问题是当它关闭时该功能在它打开时仍然有效并且我想成为能够将其关闭。我做错了什么?

popup.js(where switch 检查真假)

const toggle = document.getElementById('switch');
const checkedAttr = document.createAttribute('checked');

toggle.addEventListener('click', () => {
    toggle.checked ? toggleChecked() : toggleUnchecked();
});

function toggleChecked() {
    chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
        chrome.tabs.sendMessage(tabs[0].id, {switchStatus: "on"}, function(response) {
            if(response.switchStatus === "on"){
                //alert("Success")
            }
        })
    })
}

function toggleUnchecked() {
    chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
        chrome.tabs.sendMessage(tabs[0].id, {switchStatus: "off"}, function(response) {
            if(response.switchStatus === "off"){
                //alert("Success")
            }
        })
    })
}

content.js(函数发生的地方)

// Receiving message from popup.js
chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.switchStatus === "on") {

        // Getting all text elements
        const allText = document.querySelectorAll('h1, h2, h3, h4, h5, p, li, td, caption, span, a');
        const colors = ['#ffadad', '#ffd6a5', '#fdffb6', '#caffbf', '#bdb2ff', '#9fdfff'];
        const findFontModal = 'findfont.html'

        // Loop through all text elements-
        // to be able to manipulate each individual one
        for (let i = 0; i < allText.length; i++) {

            //ON HOVER
            allText[i].addEventListener('mouseover', async () => {
                allText[i].style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
                allText[i].style.cursor = "pointer";
            });

            allText[i].addEventListener('mouseout', async () => {
                allText[i].style.backgroundColor = null;
            });

            //ON MOUSE CLICK
            allText[i].addEventListener('click', async (event) => {
              const showModal = async () => {
                const modal = document.createElement('dialog');
              
                modal.setAttribute(
                  "style",`
                  border: none;
                  border-radius:20px;
                  background-color:#fafafa;
                  position: absolute;
                  z-index:10000000000000;
                  box-shadow: rgba(0, 0, 0, 0.2) 0px 18px 50px -10px;
                  `);
              
                  modal.innerHTML = 
                  `
                  <iframe id="popup-content" scrolling="no" style="height:155px; width:347px; z-index:10000000000000;" frameBorder="0"></iframe>
                  `;

                  modal.style.top = event.pageY+"px";
                  modal.style.left = event.pageX+"px";
              
                  document.body.appendChild(modal);
                  const dialog = document.querySelector("dialog");
                  dialog.show();

                  document.body.addEventListener('click', () => {
                    document.body.removeChild(modal);
                    dialog.close();
                  });
              
                  const iframe = document.getElementById("popup-content");
                  iframe.src = chrome.extension.getURL("findfont.html")
              }

                // Load in HTML file
                fetch(chrome.runtime.getURL(findFontModal))
                  .then(r => r.text())
                  .then(html => {
                    showModal();
                });
            });
        }

        sendResponse({status: "done"});
        return;
      }

      // When toggle switch is unchecked
      else if (request.switchStatus === "off") {
        const allText = document.querySelectorAll('h1, h2, h3, h4, h5, p, li, td, caption, span, a');
        for (let i = 0; i < allText.length; i++) {
            allText[i].style.backgroundColor = null;
        }

        sendResponse({status: "done"});
      }
    }
);

切换状态应存储在 chrome storage 中,因此当重复弹出 opened/closed 时,复选框状态将保持不变。假设是这种情况,弹出窗口应该按给定的那样正确运行。

但是,我建议在内容脚本中解决两件事:

  1. mouseover/out/click 的事件处理程序目前正在(以及每次)收到消息时添加,但不会在“关闭”操作时删除,这就是为什么它们即使在“关闭”消息之后也会发生已收到。

解决此问题的一种方法是将事件侦听器注册与接收消息的时间点分开。我在下面添加了一个示例,其中我使用布尔变量作为了解事件是否应该执行(“打开”或“关闭”)的方法,然后根据收到的消息翻转此变量。事件侦听器只添加到每个目标元素一次,并且永远不会删除,但是当标志处于“关闭”状态时每个处理程序都不执行任何操作(将在下一个代码块中解释最后一部分)。

let toggleIsOn = false;
const elementIsReady = "special-flag-xyz-put-something-unique-here"

// Receiving message from popup.js
chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse) {
        // if there are other messages, review this
        toggleIsOn = request.switchStatus === "on";
    }
);

function registerEventHandlers() {
    const allText = document.querySelectorAll('h1, h2, h3, h4, h5, p, li, td, caption, span, a');

    for (let i = 0; i < allText.length; i++) {
        const element = allText[i];
        // mark each element with attribute to know the event
        // actions have been applied, to prevent registering
        // these handlers multiple times
        if (!element.hasAttribute(elementIsReady)) {
            element.addEventListener('mouseover', onMouseOver);
            element.addEventListener('mouseout', onMouseOut);
            element.addEventListener('click', onClick);
            element.setAttribute(elementIsReady, true);
        }
    }
}

请注意,在注册事件处理程序时,我还添加了一个特殊属性来指示已添加处理程序,以防止在同一元素上多次注册相同的处理程序。该值可以是任何足够独特的东西,不太可能自然地作为 DOM 节点属性出现。

在各自的函数中为每个事件指定事件行为。请注意,当切换处于“关闭”状态时,处理程序只是 returns,即“什么都不做”,而在“打开”的情况下,事件行为将执行:

async function onMouseOver(event) {
    if (!toggleIsOn) return;
    console.log(`mouse over: ${event.target.innerText}`);
}

async function onMouseOut(event) {
    if (!toggleIsOn) return;
    console.log(`mouse out: ${event.target.innerText}`);
}

async function onClick(event) {
    if (!toggleIsOn) return;
    console.log(`element click! ${event.target.innerText}`);
    // put the open modal logic here, etc.
}

内容脚本中要解决的第二点是何时查询页面的所有文本节点,因为它不再连接到接收消息的时间点:

  1. 如果目标是仅处理服务器端呈现的页面,则在页面加载时调用一次 registerEventHandlers() 就足够了。这种情况不太可能发生。对于动态修改 DOM 的单页应用程序,事件侦听器会在 DOM 更改时丢失,并且不会添加到页面加载后生成的新节点中,除非每当 registerEventHandlers() 再次调用DOM 变化。

解决这个问题的方法是使用MutationObserver,它允许观察DOM中的动态变化。这是一个例子:

// run once when script is loaded to setup the observer
// wrapping this in a "constructor" to illustrate that point
(function constructor() {
    const observer = new MutationObserver(registerEventHandlers);
    observer.observe(document.body, {subtree: true, childList: true});
}())

使用此策略将允许扩展程序也适用于单页应用程序。由于节点被标记为多次重新注册事件监听器,如前所述,即使使用变异观察器也不会发生这种情况:脚本迭代地查找还没有监听器的文本节点,并按原样添加一次发现了。