Skip to content

Instantly share code, notes, and snippets.

@lgg
Forked from Gozala/tab-events.js
Created June 30, 2022 12:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lgg/b4f8f13d318d8659dd125221afc55bf8 to your computer and use it in GitHub Desktop.
Save lgg/b4f8f13d318d8659dd125221afc55bf8 to your computer and use it in GitHub Desktop.
"use strict";
const { addObserver, notifyObservers } = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
const { getEnumerator } = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator);
const isBrowser = window => {
try {
return window.document.documentElement.getAttribute("windowtype") === "navigator:browser";
}
catch (e) {
return false;
}
}
const isInteractive = window =>
window.document.readyState === "interactive" ||
window.document.readyState === "complete";
const onWindowOpened = subject => {
const window = subject.QueryInterface(Ci.nsIDOMWindow);
if (isBrowser(window)) {
window.addEventListener("DOMContentLoaded", onWindowReady, true);
}
}
const onWindowReady = event => {
// event.target is document rather than window.
const window = event.target.defaultView;
// Remove listener.
window.removeEventListener("DOMContentLoaded", onWindowReady, true);
// There are no open event for inital tabs so we emulate one.
onInitialTab(window);
window.addEventListener("TabOpen", onTabOpen);
window.addEventListener("TabClose", onTabClose);
window.addEventListener("TabPinned", onTabPinned);
window.addEventListener("TabUnpinned", onTabUnpinned);
window.addEventListener("TabMove", onTabMove);
window.addEventListener("TabShow", onTabShow);
window.addEventListener("TabHide", onTabShow);
// And more complicated ones.
window.addEventListener("TabAttrModified", onTabAttributeModified);
window.addEventListener("TabSelect", onTabSelect);
}
const makeNotifier = type => event => notifyObservers(event.target, type, null);
const onTabClose = makeNotifier("tab-close")
const onTabPinned = makeNotifier("tab-pinned")
const onTabUnpinned = makeNotifier("tab-unpinned")
const onTabMove = makeNotifier("tab-move")
const onTabShow = makeNotifier("tab-show")
const onTabHide = makeNotifier("tab-hide")
// Since we can't know which attribute is modified on tab we'll have
// to make guess based on state. There for we have weak map of tab
// attribute states
const states = new WeakMap();
const readState(tab) => ({
busy: tab.getAttribute("busy"),
icon: tab.getAttribute("image"),
title: tab.label
});
// Since there is no TabUnselect and TabSelect is dispatched post factum
// we need to keep track and synchronize state ourselfs. We're going to
// use weak map for window to previously selected tab associations.
const selectedTabToWindow = new WeakMap();
const onInitialTab = window => {
const tab = window.gBrowser.selectedTab;
// sync state.
selectedTabToWindow.set(window, tab);
states.set(tab, readState(tab));
// initially dispatch different notification for initial tabs since
// platform clearly considers these special. This would let users decide
// weather they handle them differently or same as regular open.
notifyObservers(tab, "initial-tab-open", null);
}
const onTabOpen = event => {
const tab = event.target;
states.set(tab, readState(tab));
notifyObservers(tab, "tab-open", null);
}
const onTabSelect = event => {
const tab = event.target;
const window = tab.ownerWindow;
// since we can event is dispatched after selectedTab is modified
// we can't really know which tab was unselected without synchronizing
// state manually :(
let selectedTab = selectedTabToWindow.get(window);
if (!selectedTab)
Cu.reportError(new Error("Some tab should have being selected"));
else
notifyObservers(tab, "tab-unselect", null);
notifyObservers(tab, "tab-select", null);
}
const onTabAttributeModified = event => {
const tab = event.target;
const past = states.get(tab);
const current = readState(tab);
// Note: Update state before dispatching notification, since observer
// may update state in side effect which will end up calling this function
// and comparing to wrong state.
states.set(tab, current);
if (past.busy !== current.busy) {
notifyObservers(tab, current.busy ? "tab-busy" : "tab-unbusy", null);
}
else if (past.icon !== current.icon) {
notifyObservers(tab, "tab-icon-change", null);
}
else if (past.title !== current.title) {
notifyObservers(tab, "tab-title-change", null);
}
}
// React to windows that will get opened.
addObserver({ observe: onWindowOpened }, "domwindowopened", false);
// Also register listeners to windows that are already opened.
const opened = getEnumerator("browser:navigator");
while (opened.hasMoreElements()) {
let window = opened.getNext().QueryInterface(Ci.nsIDOMWindow);
if (isInteractive(window))
onWindowReady(window)
else
onWindowOpened(window)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment