Files
tubestation/waterfox/browser/components/sidebar/experiments/waterfoxBridge.js
2025-11-06 14:13:52 +00:00

888 lines
31 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const HTML = 'http://www.w3.org/1999/xhtml';
const XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
const TYPE_TREE = 'application/x-ws-tree';
const TST_ID = 'treestyletab@piro.sakura.ne.jp';
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AddonManager: 'resource://gre/modules/AddonManager.sys.mjs',
CustomizableUI: 'resource:///modules/CustomizableUI.sys.mjs',
ExtensionPermissions: 'resource://gre/modules/ExtensionPermissions.sys.mjs',
PageThumbs: 'resource://gre/modules/PageThumbs.sys.mjs',
PlacesUtils: 'resource://gre/modules/PlacesUtils.sys.mjs',
});
// Range.createContextualFragment() unexpectedly drops XUL elements.
// Moreover, the security mechanism of the browser rejects adoptation of elements
// created by DOMParser(). Thus we need to create elements manually...
function element(document, NS, localName, attributes, children) {
if (Array.isArray(attributes)) {
children = attributes;
attributes = {};
}
const element = document.createElementNS(NS, localName);
if (attributes) {
for (const [name, value] of Object.entries(attributes)) {
element.setAttribute(name, value);
}
}
if (children) {
for (const child of children) {
if (typeof child == 'string')
element.appendChild(document.createTextNode(child));
else
element.appendChild(child);
}
}
return element;
}
const BrowserWindowWatcher = {
WATCHING_URLS: [
'chrome://browser/content/browser.xhtml',
],
BASE_URL: null, // this need to be replaced with "moz-extension://..../"
BASE_PREF: 'browser.sidebar.', // null,
locale: null, // this need to be replaced with a map
loadingForbiddenURLs: [],
autoplayBlockedListeners: new Set(),
autoplayUnblockedListeners: new Set(),
visibilityChangedListeners: new Set(),
menuCommandListeners: new Set(),
sidebarShownListeners: new Set(),
sidebarHiddenListeners: new Set(),
lastTransferredFiles: new Map(),
handleWindow(win) {
if (!win ||
!win.location)
return false;
const document = win.document;
if (!document)
return false;
if (win.location.href.startsWith('chrome://browser/content/browser.xhtml')) {
const installed = this.installTabsSidebar(win);
if (installed) {
win.addEventListener('DOMAudioPlaybackBlockStarted', this, { capture: true });
win.addEventListener('DOMAudioPlaybackBlockStopped', this, { capture: true });
win.addEventListener('visibilitychange', this);
win.addEventListener('TreeVerticalTabsShown', this);
win.addEventListener('TreeVerticalTabsHidden', this);
}
return installed;
}
return true;
},
unhandleWindow(win) {
if (!win ||
!win.location)
return;
const document = win.document;
if (!document)
return;
if (win.location.href.startsWith('chrome://browser/content/browser.xhtml')) {
this.uninstallTabsSidebar(win);
try {
win.removeEventListener('DOMAudioPlaybackBlockStarted', this, { capture: true });
win.removeEventListener('DOMAudioPlaybackBlockStopped', this, { capture: true });
win.removeEventListener('visibilitychange', this);
win.removeEventListener('TreeVerticalTabsShown', this);
win.removeEventListener('TreeVerticalTabsHidden', this);
}
catch(_error) {
}
}
},
installTabsSidebar(win) {
const document = win.document;
const tabsSidebarElement = document.querySelector('#tree-vertical-tabs-box');
if (tabsSidebarElement?.getAttribute('initialized') == 'true')
return true;
if (tabsSidebarElement) {
tabsSidebarElement.setAttribute('initialized', 'true');
tabsSidebarElement.addEventListener('dragover', this, { capture: true });
} else {
console.error('WaterfoxBridge: #tree-vertical-tabs element not found. Cannot attach event listeners or load panel.');
}
document.addEventListener('command', this);
document.addEventListener('customizationchange', this, { capture: true });
this.updateToggleButton(document);
return true;
},
getKeyFromFile(file) {
if (!file)
return '';
return `${file.name}?lastModified=${file.lastModified}&size=${file.size}&type=${file.type}`;
},
getFileURL(file) {
if (!file)
return '';
return this.lastTransferredFiles[this.getKeyFromFile(file)];
},
uninstallTabsSidebar(win) {
const document = win.document;
document.removeEventListener('command', this);
document.removeEventListener('customizationchange', this, { capture: true });
const tabsSidebarElement = document.querySelector('#tree-vertical-tabs-box');
if (tabsSidebarElement?.getAttribute('initialized') == 'true') {
tabsSidebarElement.removeAttribute('initialized');
tabsSidebarElement.removeEventListener('dragover', this, { capture: true });
}
},
updateToggleButton(document, button) {
button = button || document.querySelector('#toggle-tree-vertical-tabs');
if (!button)
return;
button.removeAttribute('disabled');
},
*iterateTargetWindows() {
const browserWindows = Services.wm.getEnumerator('navigator:browser');
while (browserWindows.hasMoreElements()) {
const win = browserWindows.getNext()/*.QueryInterface(Components.interfaces.nsIDOMWindow)*/
yield win;
}
return;
},
openOptions(win, full = false) {
const url = full ? `${this.BASE_URL}options/options.html#!` : 'about:preferences#tabsSidebar';
const windows = Services.wm.getEnumerator('navigator:browser');
while (windows.hasMoreElements()) {
const win = windows.getNext()/*.QueryInterface(Components.interfaces.nsIDOMWindow)*/;
if (!win.gBrowser)
continue;
for (const tab of win.gBrowser.tabs) {
if (tab.linkedBrowser.currentURI.spec != url)
continue;
win.gBrowser.selectedTab = tab;
return;
}
}
(win || Services.wm.getMostRecentBrowserWindow())
.openLinkIn(url, 'tab', {
allowThirdPartyFixup: false,
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
inBackground: false,
});
},
handleEvent(event) {
const win = event.target.ownerDocument?.defaultView || event.target.defaultView;
switch (event.type) {
case 'command':
switch (event.target.id) {
case 'toggle-tree-vertical-tabs':
case 'toggle-tree-vertical-tabs-command':
case 'viewmenu-toggle-tree-vertical-tabs':
this.updateToggleButton(event.target.ownerDocument);
break;
}
break;
case 'customizationchange':
this.updateToggleButton(event.target.ownerDocument);
break;
case 'DOMAudioPlaybackBlockStarted': {
const gBrowser = event.target.ownerDocument.defaultView.gBrowser;
const tab = gBrowser.getTabForBrowser(event.target);
for (const listener of this.autoplayBlockedListeners) {
listener(tab);
}
}; break;
case 'DOMAudioPlaybackBlockStopped': {
const gBrowser = event.target.ownerDocument.defaultView.gBrowser;
const tab = gBrowser.getTabForBrowser(event.target);
for (const listener of this.autoplayUnblockedListeners) {
listener(tab);
}
}; break;
case 'visibilitychange':
for (const listener of this.visibilityChangedListeners) {
listener(event.currentTarget);
}
break;
case 'TreeVerticalTabsShown':
for (const listener of this.sidebarShownListeners) {
listener(event.target.ownerDocument.defaultView);
}
break;
case 'TreeVerticalTabsHidden':
for (const listener of this.sidebarHiddenListeners) {
listener(event.target.ownerDocument.defaultView);
}
break;
case 'dragover': {
const tabsSidebarElement = event.currentTarget;
this.lastTransferredFiles.clear();
for (const file of event.dataTransfer.files) {
const fileInternal = Cc['@mozilla.org/file/local;1']
.createInstance(Components.interfaces.nsIFile);
fileInternal.initWithPath(file.mozFullPath);
const url = Services.io.getProtocolHandler('file')
.QueryInterface(Components.interfaces.nsIFileProtocolHandler)
.getURLSpecFromActualFile(fileInternal);
this.lastTransferredFiles[this.getKeyFromFile(file)] = url;
}
}; break;
}
},
tryHidePopup(event) {
if (event.target.closest)
event.target.closest('panel')?.hidePopup();
},
// as an XPCOM component...
classDescription: 'Waterfox Chrome Window Watcher for Browser Windows',
contractID: '@waterfox.net/chrome-window-watche-browser-windows;1',
classID: Components.ID('{8d25e5cc-1d67-4556-819e-e25bd37c79c5}'),
QueryInterface: ChromeUtils.generateQI([
'nsIContentPolicy',
'nsIObserver',
'nsISupportsWeakReference',
]),
// nsIContentPolicy
shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
const FORBIDDEN_URL_MATCHER = /^about:blank\?forbidden-url=/;
if (FORBIDDEN_URL_MATCHER.test(contentLocation.spec)) {
const url = contentLocation.spec.replace(FORBIDDEN_URL_MATCHER, '');
const index = this.loadingForbiddenURLs.indexOf(url);
if (index > -1) {
this.loadingForbiddenURLs.splice(index, 1);
const browser = loadInfo.browsingContext.embedderElement;
browser.loadURI(Services.io.newURI(url), {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
});
return Components.interfaces.nsIContentPolicy.REJECT_REQUEST;
}
}
if (this.WATCHING_URLS.some(url => contentLocation.spec.startsWith(url))) {
const startAt = Date.now();
const topWin = loadInfo.browsingContext.topChromeWindow;
const timer = topWin.setInterval(() => {
if (Date.now() - startAt > 1000) {
// timeout
topWin.clearInterval(timer);
return;
}
const win = loadInfo.browsingContext.window;
if (!win)
return;
try {
if (this.handleWindow(win))
topWin.clearInterval(timer);
}
catch(_error) {
}
}, 250);
}
return Components.interfaces.nsIContentPolicy.ACCEPT;
},
shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
return Components.interfaces.nsIContentPolicy.ACCEPT;
},
// nsIObserver
observe(subject, topic, data) {
switch (topic) {
case 'domwindowopened':
subject
//.QueryInterface(Components.interfaces.nsIDOMWindow)
.addEventListener('DOMContentLoaded', () => {
this.handleWindow(subject);
}, { once: true });
break;
}
},
createInstance(iid) {
return this.QueryInterface(iid);
},
// AddonManager listener callbacks
async tryConfirmUsingTST() {
const ignorePrefKey = `${this.BASE_PREF}.ignoreConflictionWithTST`;
if (Services.prefs.getBoolPref(ignorePrefKey, false))
return;
const nsIPrompt = Components.interfaces.nsIPrompt;
const shouldAsk = { value: true };
const result = Services.prompt.confirmEx(
Services.wm.getMostRecentBrowserWindow(),
this.locale.get('tryConfirmUsingTST_title'),
this.locale.get('tryConfirmUsingTST_message'),
(nsIPrompt.BUTTON_TITLE_IS_STRING * nsIPrompt.BUTTON_POS_0 |
nsIPrompt.BUTTON_TITLE_IS_STRING * nsIPrompt.BUTTON_POS_1 |
nsIPrompt.BUTTON_TITLE_IS_STRING * nsIPrompt.BUTTON_POS_2),
this.locale.get('tryConfirmUsingTST_WS'),
this.locale.get('tryConfirmUsingTST_both'),
this.locale.get('tryConfirmUsingTST_TST'),
this.locale.get('tryConfirmUsingTST_ask'),
shouldAsk
);
if (result > -1 &&
!shouldAsk.value)
Services.prefs.setBoolPref(ignorePrefKey, true);
switch (result) {
case 0: {
const addon = await lazy.AddonManager.getAddonByID(TST_ID);
addon.disable();
}; return;
case 2:
Services.prefs.setBoolPref('browser.sidebar.enabled', false);
return;
default:
return;
}
},
// install listener callbacks
onNewInstall(_install) {},
onInstallCancelled(_install) {},
onInstallPostponed(_install) {},
onInstallFailed(_install) {},
onInstallEnded(install) {
if (install.addon.id == TST_ID)
this.tryConfirmUsingTST();
},
onDownloadStarted(_install) {},
onDownloadCancelled(_install) {},
onDownloadEnded(_install) {},
onDownloadFailed(_install) {},
// addon listener callbacks
onUninstalled(_addon) {},
onEnabled(addon) {
if (addon.id == TST_ID)
this.tryConfirmUsingTST();
},
onDisabled(_addon) {},
};
this.waterfoxBridge = class extends ExtensionAPI {
getAPI(context) {
const EventManager = ExtensionCommon.EventManager;
return {
waterfoxBridge: {
async initUI() {
BrowserWindowWatcher.EXTENSION_ID = context.extension.id;
BrowserWindowWatcher.BASE_URL = context.extension.baseURL;
//BrowserWindowWatcher.BASE_PREF = `extensions.${context.extension.id.split('@')[0]}.`;
BrowserWindowWatcher.locale = {
get(key) {
key = key.toLowerCase();
if (this.selected.has(key))
return this.selected.get(key);
return this.default.get(key) || key;
},
default: context.extension.localeData.messages.get(context.extension.localeData.defaultLocale),
selected: context.extension.localeData.messages.get(context.extension.localeData.selectedLocale),
};
//const resourceURI = Services.io.newURI('resources', null, context.extension.rootURI);
//const handler = Cc['@mozilla.org/network/protocol;1?name=resource'].getService(Components.interfaces.nsISubstitutingProtocolHandler);
//handler.setSubstitution('waterfox-bridge', resourceURI);
// watch loading of about:preferences in subframes
const registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
registrar.registerFactory(
BrowserWindowWatcher.classID,
BrowserWindowWatcher.classDescription,
BrowserWindowWatcher.contractID,
BrowserWindowWatcher
);
Services.catMan.addCategoryEntry(
'content-policy',
BrowserWindowWatcher.contractID,
BrowserWindowWatcher.contractID,
false,
true
);
// handle loading of browser windows
Services.ww.registerNotification(BrowserWindowWatcher);
// handle already opened browser windows
const windows = BrowserWindowWatcher.iterateTargetWindows();
while (true) {
const win = windows.next();
if (win.done)
break;
BrowserWindowWatcher.handleWindow(win.value);
}
// grant special permissions by default
if (!Services.prefs.getBoolPref(`${BrowserWindowWatcher.BASE_PREF}permissionsGranted`, false)) {
lazy.ExtensionPermissions.add(context.extension.id, {
origins: ['<all_urls>'],
permissions: ['internal:privateBrowsingAllowed'],
}, true);
Services.prefs.setBoolPref(`${BrowserWindowWatcher.BASE_PREF}permissionsGranted`, true);
}
// auto detection and warning for TST
lazy.AddonManager.addInstallListener(BrowserWindowWatcher);
lazy.AddonManager.addAddonListener(BrowserWindowWatcher);
const installedTST = await lazy.AddonManager.getAddonByID(TST_ID);
if (installedTST?.isActive)
BrowserWindowWatcher.tryConfirmUsingTST();
},
async reserveToLoadForbiddenURL(url) {
BrowserWindowWatcher.loadingForbiddenURLs.push(url);
},
async getFileURL(file) {
return BrowserWindowWatcher.getFileURL(file);
},
async getTabPreview(tabId) {
const info = {
url: null,
found: false,
};
const tab = context.extension.tabManager.get(tabId);
if (!tab)
return info;
const nativeTab = tab.nativeTab;
const window = nativeTab.ownerDocument.defaultView;
try {
const canvas = await window.tabPreviews.get(nativeTab);
/*
// We can get a URL like "https%3A%2F%2Fwww.example.com.org%2F&revision=0000"
// but Firefox does not allow loading of such a special internal URL from
// addon's sidebar page.
const image = await window.tabPreviews.get(nativeTab);
return image.src;
*/
if (canvas) {
info.url = canvas.toDataURL('image/png');
info.found = true;
return info;
}
}
catch (_error) { // tabPreviews.capture() raises error if the tab is discarded.
// console.error('waterfoxBridge: failed to take a tab preview: ', tabId, error);
}
// simulate default preview
// see also: https://searchfox.org/mozilla-esr115/rev/d0623081f317c92e0c7bc2a8b1b138687bdb23f5/browser/themes/shared/ctrlTab.css#85-94
const canvas = lazy.PageThumbs.createCanvas(window);
try {
// TODO: we should change the fill color to "CanvasText"...
const image = new window.Image();
await new Promise((resolve, reject) => {
image.addEventListener('load', resolve, { once: true });
image.addEventListener('error', reject, { once: true });
image.src = 'chrome://global/skin/icons/defaultFavicon.svg';
});
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'Canvas';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const iconSize = canvas.width * 0.2;
ctx.drawImage(
image,
0,
0,
image.width,
image.height,
(canvas.width - iconSize) / 2,
(canvas.height - iconSize) / 2,
iconSize,
iconSize
);
}
catch (_error) {
}
info.url = canvas.toDataURL('image/png');
return info;
},
async showPreviewPanel(tabId, top) {
const tab = tabId && context.extension.tabManager.get(tabId);
if (!tab)
return;
const document = tab.nativeTab.ownerDocument;
const tabbrowserTabs = document.getElementById('tabbrowser-tabs');
if (!tabbrowserTabs)
return;
if (!tabbrowserTabs.previewPanel) {
// load the tab preview component
const TabHoverPreviewPanel = ChromeUtils.importESModule(
'chrome://browser/content/tabbrowser/tab-hover-preview.mjs'
).default;
tabbrowserTabs.previewPanel = new TabHoverPreviewPanel(
document.getElementById('tab-preview-panel')
);
}
tabbrowserTabs.previewPanel.__ws__top = top;
tabbrowserTabs.previewPanel.activate(tab.nativeTab);
},
async hidePreviewPanel(windowId) {
const win = windowId && context.extension.windowManager.get(windowId);
if (!win || !win.window)
return;
try {
// Access the document through the window object
const document = win.window.document;
const tabPreview = document.getElementById('tabbrowser-tabs')?.previewPanel;
if (!tabPreview)
return;
tabPreview.__ws__top = null;
} catch (error) {
console.log("Error in hidePreviewPanel:", error);
}
},
async openPreferences() {
BrowserWindowWatcher.openOptions();
},
onWindowVisibilityChanged: new EventManager({
context,
name: 'waterfoxBridge.onWindowVisibilityChanged',
register: (fire) => {
const onChanged = win => {
const wrappedWindow = context.extension.windowManager.getWrapper(win);
if (wrappedWindow)
fire.async(wrappedWindow.id, win.document.visibilityState).catch(() => {}); // ignore Message Manager disconnects
};
BrowserWindowWatcher.visibilityChangedListeners.add(onChanged);
return () => {
BrowserWindowWatcher.visibilityChangedListeners.delete(onChanged);
};
},
}).api(),
onMenuCommand: new EventManager({
context,
name: 'waterfoxBridge.onMenuCommand',
register: (fire) => {
const onCommand = event => {
fire.async({
itemId: event.target.id,
detail: event.detail,
button: event.button,
altKey: event.altKey,
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
shiftKey: event.shiftKey,
}).catch(() => {}); // ignore Message Manager disconnects
};
BrowserWindowWatcher.menuCommandListeners.add(onCommand);
return () => {
BrowserWindowWatcher.menuCommandListeners.delete(onCommand);
};
},
}).api(),
onSidebarShown: new EventManager({
context,
name: 'waterfoxBridge.onSidebarShown',
register: (fire) => {
const onShown = win => {
const wrappedWindow = context.extension.windowManager.getWrapper(win);
if (wrappedWindow)
fire.async(wrappedWindow.id).catch(() => {}); // ignore Message Manager disconnects
};
BrowserWindowWatcher.sidebarShownListeners.add(onShown);
return () => {
BrowserWindowWatcher.sidebarShownListeners.delete(onShown);
};
},
}).api(),
onSidebarHidden: new EventManager({
context,
name: 'waterfoxBridge.onSidebarHidden',
register: (fire) => {
const onHidden = win => {
const wrappedWindow = context.extension.windowManager.getWrapper(win);
if (wrappedWindow)
fire.async(wrappedWindow.id).catch(() => {}); // ignore Message Manager disconnects
};
BrowserWindowWatcher.sidebarHiddenListeners.add(onHidden);
return () => {
BrowserWindowWatcher.sidebarHiddenListeners.delete(onHidden);
};
},
}).api(),
async listSyncDevices() {
const devices = [];
const targets = Services.wm.getMostRecentBrowserWindow().gSync.getSendTabTargets();
for (const target of targets) {
devices.push({
id: target.id,
name: target.name,
type: target.type,
});
}
return devices;
},
async sendToDevice(tabIds, deviceId) {
if (!Array.isArray(tabIds))
tabIds = [tabIds];
const gSync = Services.wm.getMostRecentBrowserWindow().gSync;
const tabs = tabIds.map(id => context.extension.tabManager.get(id));
const targets = gSync.getSendTabTargets().filter(target => !deviceId || target.id == deviceId);
for (const tab of tabs) {
gSync.sendTabToDevice(
tab.nativeTab.linkedBrowser.currentURI.spec,
targets,
tab.nativeTab.linkedBrowser.contentTitle
);
}
},
async openSyncDeviceSettings(windowId) {
let DOMWin = null;
try {
const win = windowId && context.extension.windowManager.get(windowId)
DOMWin = win?.window;
}
catch (_error) {
}
(DOMWin || Services.wm.getMostRecentBrowserWindow()).gSync.openDevicesManagementPage('sendtab');
},
async listSharingServices(tabId) {
const tab = tabId && context.extension.tabManager.get(tabId);
const services = [];
const win = Services.wm.getMostRecentBrowserWindow();
const sharingService = win.gBrowser.MacSharingService;
if (!sharingService)
return services;
const uri = win.gURLBar.makeURIReadable(
tab?.nativeTab.linkedBrowser.currentURI ||
Services.io.newURI('https://waterfox.net/', null, null)
).displaySpec;
for (const service of sharingService.getSharingProviders(uri)) {
services.push({
name: service.name,
title: service.menuItemTitle,
image: service.image,
});
}
return services;
},
async share(tabIds, shareName) {
if (!Array.isArray(tabIds))
tabIds = [tabIds];
const tabs = tabIds.map(id => context.extension.tabManager.get(id));
// currently we can share only one URL at a time...
const tab = tabs[0];
const win = Services.wm.getMostRecentBrowserWindow();
const uri = win.gURLBar.makeURIReadable(tab.nativeTab.linkedBrowser.currentURI).displaySpec;
if (AppConstants.platform == 'win') {
win.WindowsUIUtils.shareUrl(uri, tab.nativeTab.linkedBrowser.contentTitle);
return;
}
if (shareName) { // for macOS
win.gBrowser.MacSharingService.shareUrl(shareName, uri, tab.nativeTab.linkedBrowser.contentTitle);
return;
}
},
async openSharingPreferences() {
Services.wm.getMostRecentBrowserWindow().gBrowser.MacSharingService.openSharingPreferences();
},
async listAutoplayBlockedTabs(windowId) {
const tabs = new Set();
const windows = windowId ?
[context.extension.windowManager.get(windowId)] :
context.extension.windowManager.getAll();
for (const win of windows) {
if (!win.window.gBrowser)
continue;
for (const tab of win.window.document.querySelectorAll('tab[activemedia-blocked="true"]')) {
const wrappedTab = context.extension.tabManager.getWrapper(tab);
if (wrappedTab)
tabs.add(wrappedTab.convert());
}
}
return [...tabs].sort((a, b) => a.index - b.index);
},
async isAutoplayBlockedTab(tabId) {
const tab = context.extension.tabManager.get(tabId);
if (!tab)
return false;
return tab.nativeTab.getAttribute('activemedia-blocked') == 'true';
},
async unblockAutoplay(tabIds) {
if (!Array.isArray(tabIds))
tabIds = [tabIds];
const tabs = tabIds.map(id => context.extension.tabManager.get(id));
for (const tab of tabs) {
tab.nativeTab.linkedBrowser.resumeMedia();
}
},
onAutoplayBlocked: new EventManager({
context,
name: 'waterfoxBridge.onAutoplayBlocked',
register: (fire) => {
const onBlocked = tab => {
const wrappedTab = context.extension.tabManager.getWrapper(tab);
if (wrappedTab)
fire.async(wrappedTab.convert()).catch(() => {}); // ignore Message Manager disconnects
};
BrowserWindowWatcher.autoplayBlockedListeners.add(onBlocked);
return () => {
BrowserWindowWatcher.autoplayBlockedListeners.delete(onBlocked);
};
},
}).api(),
onAutoplayUnblocked: new EventManager({
context,
name: 'waterfoxBridge.onAutoplayUnblocked',
register: (fire) => {
const onUnblocked = tab => {
const wrappedTab = context.extension.tabManager.getWrapper(tab);
if (wrappedTab)
fire.async(wrappedTab.convert()).catch(() => {}); // ignore Message Manager disconnects
};
BrowserWindowWatcher.autoplayUnblockedListeners.add(onUnblocked);
return () => {
BrowserWindowWatcher.autoplayUnblockedListeners.delete(onUnblocked);
};
},
}).api(),
async isSelectionClipboardAvailable() {
try {
return Services.clipboard.isClipboardTypeSupported(Services.clipboard.kSelectionClipboard);
}
catch(_error) {
return false;
}
},
async getSelectionClipboardContents() {
try {
const transferable = Components.classes['@mozilla.org/widget/transferable;1']
.createInstance(Components.interfaces.nsITransferable);
const loadContext = Services.wm.getMostRecentBrowserWindow()
.docShell.QueryInterface(Components.interfaces.nsILoadContext);
transferable.init(loadContext);
transferable.addDataFlavor('text/plain');
Services.clipboard.getData(transferable, Services.clipboard.kSelectionClipboard);
const data = {};
transferable.getTransferData('text/plain', data);
if (data) {
data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
return data.data;
}
}
catch(_error) {
return '';
}
},
},
};
}
onShutdown(isAppShutdown) {
if (isAppShutdown)
return;
lazy.AddonManager.removeInstallListener(BrowserWindowWatcher);
lazy.AddonManager.removeAddonListener(BrowserWindowWatcher);
if (lazy.PlacesUtils.__ws_orig__unwrapNodes) {
lazy.PlacesUtils.unwrapNodes = lazy.PlacesUtils.__ws_orig__unwrapNodes;
lazy.PlacesUtils.__ws_orig__unwrapNodes = null;
}
const registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
registrar.unregisterFactory(
BrowserWindowWatcher.classID,
BrowserWindowWatcher
);
Services.catMan.deleteCategoryEntry(
'content-policy',
BrowserWindowWatcher.contractID,
false
);
Services.ww.unregisterNotification(BrowserWindowWatcher);
const windows = BrowserWindowWatcher.iterateTargetWindows();
while (true) {
const win = windows.next();
if (win.done)
break;
BrowserWindowWatcher.unhandleWindow(win.value);
}
//const handler = Cc['@mozilla.org/network/protocol;1?name=resource'].getService(Components.interfaces.nsISubstitutingProtocolHandler);
//handler.setSubstitution('waterfox-bridge', null);
Services.prefs.removeObserver('', BrowserWindowWatcher);
}
};