Is it possible to use Text Input Processor on a specific Tab of a Firefox window?
Do all the tabs share the same nsIDOMWindow object? If so, then is there any other solution?
Rephrasing my problem: I want to type text to a specific tab, and if there is more than one, then it might not be the current.
Below is an example code to write text on any Firefox window, but only for current tab:
function myTestFunc() {
// var windows = Services.wm.getEnumerator("navigation:browser");
var windows = require("sdk/windows");
for (let browserWindow of windows.browserWindows) {
//console.log(window.title);
var chromeWindow = viewFor(browserWindow);
console.log(chromeWindow.document.location.href);
var idomWindow = chromeWindow.QueryInterface(Ci.nsIDOMWindow);
var TIP = Cc["#mozilla.org/text-input-processor;1"].
createInstance(Ci.nsITextInputProcessor);
if(!TIP.beginInputTransaction(idomWindow, onNotifyImpl)) {
console.log("Error TIP can't start");
continue;
}
TIP.setPendingCompositionString("foo-bar-buzz");
if (!TIP.flushPendingComposition()) {
console.log("Failed to flush");
continue; // Composition is not started
}
}
}
nsIDOMWindow (+ various related interfaces like the docshell) is the XPCOM representation of regular window objects in the w3c specs. And since window objects can be nested (e.g. via iframes) so can be nsIDOMWindows. When you're accessing browser windows you're generally accessing the outer windows representing the browser chrome, not the content.
In principle you can access a tab's content window directly from its <browser> XUL element, but to be forward-compatible with e10s you should use framescripts instead.
Related
Is there a way in javascript to copy an html string (ie <b>xx<b>) into the clipboard as text/html, so that it can then be pasted into for example a gmail message with the formatting (ie, xx in bold)
There exists solutions to copy to the clipboard as text (text/plain) for example https://stackoverflow.com/a/30810322/460084 but not as text/html
I need a non flash, non jquery solution that will work at least on IE11 FF42 and Chrome.
Ideally I would like to store both text and html versions of the string in the clipboard so that the right one can be pasted depending if the target supports html or not.
Since this answer has gotten some attention, I have completely rewritten the messy original to be easier to grasp. If you want to look at the pre-revisioned version, you can find it here.
The boiled down question:
Can I use JavaScript to copy the formatted output of some HTML code to the users clipboard?
Answer:
Yes, with some limitations, you can.
Solution:
Below is a function that will do exactly that. I tested it with your required browsers, it works in all of them. However, IE 11 will ask for confirmation on that action.
Explanation how this works can be found below, you may interactively test the function out in this jsFiddle.
// This function expects an HTML string and copies it as rich text.
function copyFormatted (html) {
// Create container for the HTML
// [1]
var container = document.createElement('div')
container.innerHTML = html
// Hide element
// [2]
container.style.position = 'fixed'
container.style.pointerEvents = 'none'
container.style.opacity = 0
// Detect all style sheets of the page
var activeSheets = Array.prototype.slice.call(document.styleSheets)
.filter(function (sheet) {
return !sheet.disabled
})
// Mount the container to the DOM to make `contentWindow` available
// [3]
document.body.appendChild(container)
// Copy to clipboard
// [4]
window.getSelection().removeAllRanges()
var range = document.createRange()
range.selectNode(container)
window.getSelection().addRange(range)
// [5.1]
document.execCommand('copy')
// [5.2]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = true
// [5.3]
document.execCommand('copy')
// [5.4]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = false
// Remove the container
// [6]
document.body.removeChild(container)
}
Explanation:
Look into the comments in the code above to see where you currently are in the following process:
We create a container to put our HTML code into.
We style the container to be hidden and detect the page's active stylesheets. The reason will be explained shortly.
We put the container into the page's DOM.
We remove possibly existing selections and select the contents of our container.
We do the copying itself. This is actually a multi-step process:
Chrome will copy text as it sees it, with applied CSS styles, while other browsers will copy it with the browser's default styles.
Therefore we will disable all user styles before copying to get the most consistent result possible.
Before we do this, we prematurely execute the copy command.
This is a hack for IE11: In this browser, the copying must be manually confirmed once. Until the user clicked the "Confirm" button, IE users would see the page without any styles. To avoid this, we copy first, wait for confirmation, then disable the styles and copy again. That time we won't get a confirmation dialog since IE remembers our last choice.
We actually disable the page's styles.
Now we execute the copy command again.
We re-enable the stylesheets.
We remove the container from the page's DOM.
And we're done.
Caveats:
The formatted content will not be perfectly consistent across browsers.
As explained above, Chrome (i.e. the Blink engine) will use a different strategy than Firefox and IE: Chrome will copy the contents with their CSS styling, but omitting any styles that are not defined.
Firefox and IE on the other hand won't apply page-specific CSS, they will apply the browser's default styles. This also means they will have some weird styles applied to them, e.g. the default font (which is usually Times New Roman).
For security reasons, browsers will only allow the function to execute as an effect of a user interaction (e.g. a click, keypress etc.)
There is a much simpler solution. Copy a section of your page (element) than copying HTML.
With this simple function you can copy whatever you want (text, images, tables, etc.) on your page or the whole document to the clipboard.
The function receives the element id or the element itself.
function copyElementToClipboard(element) {
window.getSelection().removeAllRanges();
let range = document.createRange();
range.selectNode(typeof element === 'string' ? document.getElementById(element) : element);
window.getSelection().addRange(range);
document.execCommand('copy');
window.getSelection().removeAllRanges();
}
How to use:
copyElementToClipboard(document.body);
copyElementToClipboard('myImageId');
If you want to use the new Clipboard API, use the write method like below:
var type = "text/html";
var blob = new Blob([text], { type });
var data = [new ClipboardItem({ [type]: blob })];
navigator.clipboard.write(data).then(
function () {
/* success */
},
function () {
/* failure */
}
);
Currently(Sep 2021), The problem is that Firefox doesn't support this method.
I have done a few modifications on Loilo's answer above:
setting (and later restoring) the focus to the hidden div prevents FF going into endless recursion when copying from a textarea
setting the range to the inner children of the div prevents chrome inserting an extra <br> in the beginning
removeAllRanges on getSelection() prevents appending to existing selection (possibly not needed)
try/catch around execCommand
hiding the copy div better
On OSX this will not work. Safari does not support execCommand and chrome OSX has a known bug https://bugs.chromium.org/p/chromium/issues/detail?id=552975
code:
clipboardDiv = document.createElement('div');
clipboardDiv.style.fontSize = '12pt'; // Prevent zooming on iOS
// Reset box model
clipboardDiv.style.border = '0';
clipboardDiv.style.padding = '0';
clipboardDiv.style.margin = '0';
// Move element out of screen
clipboardDiv.style.position = 'fixed';
clipboardDiv.style['right'] = '-9999px';
clipboardDiv.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
// more hiding
clipboardDiv.setAttribute('readonly', '');
clipboardDiv.style.opacity = 0;
clipboardDiv.style.pointerEvents = 'none';
clipboardDiv.style.zIndex = -1;
clipboardDiv.setAttribute('tabindex', '0'); // so it can be focused
clipboardDiv.innerHTML = '';
document.body.appendChild(clipboardDiv);
function copyHtmlToClipboard(html) {
clipboardDiv.innerHTML=html;
var focused=document.activeElement;
clipboardDiv.focus();
window.getSelection().removeAllRanges();
var range = document.createRange();
range.setStartBefore(clipboardDiv.firstChild);
range.setEndAfter(clipboardDiv.lastChild);
window.getSelection().addRange(range);
var ok=false;
try {
if (document.execCommand('copy')) ok=true; else utils.log('execCommand returned false !');
} catch (err) {
utils.log('execCommand failed ! exception '+err);
}
focused.focus();
}
see jsfiddle where you can enter html segment into the textarea and copy to the clipboard with ctrl+c.
For those looking for a way to do this using ClipboardItem and cannot get it to work even with dom.events.asyncClipboard.clipboardItem set to true, the answer can be found at nikouusitalo.com.
And here is my working code (tested on Firefox 102).
const clipboardItem = new
ClipboardItem({'text/html': new Blob([html],
{type: 'text/html'}),
'text/plain': new Blob([html],
{type: 'text/plain'})});
navigator.clipboard.write([clipboardItem]).
then(_ => console.log("clipboard.write() Ok"),
error => alert(error));
Make sure you try pasting it into a rich text editor such as gmail and not to a plain text/markdown editor such as stackoverflow.
Is there a way in javascript to copy an html string (ie <b>xx<b>) into the clipboard as text/html, so that it can then be pasted into for example a gmail message with the formatting (ie, xx in bold)
There exists solutions to copy to the clipboard as text (text/plain) for example https://stackoverflow.com/a/30810322/460084 but not as text/html
I need a non flash, non jquery solution that will work at least on IE11 FF42 and Chrome.
Ideally I would like to store both text and html versions of the string in the clipboard so that the right one can be pasted depending if the target supports html or not.
Since this answer has gotten some attention, I have completely rewritten the messy original to be easier to grasp. If you want to look at the pre-revisioned version, you can find it here.
The boiled down question:
Can I use JavaScript to copy the formatted output of some HTML code to the users clipboard?
Answer:
Yes, with some limitations, you can.
Solution:
Below is a function that will do exactly that. I tested it with your required browsers, it works in all of them. However, IE 11 will ask for confirmation on that action.
Explanation how this works can be found below, you may interactively test the function out in this jsFiddle.
// This function expects an HTML string and copies it as rich text.
function copyFormatted (html) {
// Create container for the HTML
// [1]
var container = document.createElement('div')
container.innerHTML = html
// Hide element
// [2]
container.style.position = 'fixed'
container.style.pointerEvents = 'none'
container.style.opacity = 0
// Detect all style sheets of the page
var activeSheets = Array.prototype.slice.call(document.styleSheets)
.filter(function (sheet) {
return !sheet.disabled
})
// Mount the container to the DOM to make `contentWindow` available
// [3]
document.body.appendChild(container)
// Copy to clipboard
// [4]
window.getSelection().removeAllRanges()
var range = document.createRange()
range.selectNode(container)
window.getSelection().addRange(range)
// [5.1]
document.execCommand('copy')
// [5.2]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = true
// [5.3]
document.execCommand('copy')
// [5.4]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = false
// Remove the container
// [6]
document.body.removeChild(container)
}
Explanation:
Look into the comments in the code above to see where you currently are in the following process:
We create a container to put our HTML code into.
We style the container to be hidden and detect the page's active stylesheets. The reason will be explained shortly.
We put the container into the page's DOM.
We remove possibly existing selections and select the contents of our container.
We do the copying itself. This is actually a multi-step process:
Chrome will copy text as it sees it, with applied CSS styles, while other browsers will copy it with the browser's default styles.
Therefore we will disable all user styles before copying to get the most consistent result possible.
Before we do this, we prematurely execute the copy command.
This is a hack for IE11: In this browser, the copying must be manually confirmed once. Until the user clicked the "Confirm" button, IE users would see the page without any styles. To avoid this, we copy first, wait for confirmation, then disable the styles and copy again. That time we won't get a confirmation dialog since IE remembers our last choice.
We actually disable the page's styles.
Now we execute the copy command again.
We re-enable the stylesheets.
We remove the container from the page's DOM.
And we're done.
Caveats:
The formatted content will not be perfectly consistent across browsers.
As explained above, Chrome (i.e. the Blink engine) will use a different strategy than Firefox and IE: Chrome will copy the contents with their CSS styling, but omitting any styles that are not defined.
Firefox and IE on the other hand won't apply page-specific CSS, they will apply the browser's default styles. This also means they will have some weird styles applied to them, e.g. the default font (which is usually Times New Roman).
For security reasons, browsers will only allow the function to execute as an effect of a user interaction (e.g. a click, keypress etc.)
There is a much simpler solution. Copy a section of your page (element) than copying HTML.
With this simple function you can copy whatever you want (text, images, tables, etc.) on your page or the whole document to the clipboard.
The function receives the element id or the element itself.
function copyElementToClipboard(element) {
window.getSelection().removeAllRanges();
let range = document.createRange();
range.selectNode(typeof element === 'string' ? document.getElementById(element) : element);
window.getSelection().addRange(range);
document.execCommand('copy');
window.getSelection().removeAllRanges();
}
How to use:
copyElementToClipboard(document.body);
copyElementToClipboard('myImageId');
If you want to use the new Clipboard API, use the write method like below:
var type = "text/html";
var blob = new Blob([text], { type });
var data = [new ClipboardItem({ [type]: blob })];
navigator.clipboard.write(data).then(
function () {
/* success */
},
function () {
/* failure */
}
);
Currently(Sep 2021), The problem is that Firefox doesn't support this method.
I have done a few modifications on Loilo's answer above:
setting (and later restoring) the focus to the hidden div prevents FF going into endless recursion when copying from a textarea
setting the range to the inner children of the div prevents chrome inserting an extra <br> in the beginning
removeAllRanges on getSelection() prevents appending to existing selection (possibly not needed)
try/catch around execCommand
hiding the copy div better
On OSX this will not work. Safari does not support execCommand and chrome OSX has a known bug https://bugs.chromium.org/p/chromium/issues/detail?id=552975
code:
clipboardDiv = document.createElement('div');
clipboardDiv.style.fontSize = '12pt'; // Prevent zooming on iOS
// Reset box model
clipboardDiv.style.border = '0';
clipboardDiv.style.padding = '0';
clipboardDiv.style.margin = '0';
// Move element out of screen
clipboardDiv.style.position = 'fixed';
clipboardDiv.style['right'] = '-9999px';
clipboardDiv.style.top = (window.pageYOffset || document.documentElement.scrollTop) + 'px';
// more hiding
clipboardDiv.setAttribute('readonly', '');
clipboardDiv.style.opacity = 0;
clipboardDiv.style.pointerEvents = 'none';
clipboardDiv.style.zIndex = -1;
clipboardDiv.setAttribute('tabindex', '0'); // so it can be focused
clipboardDiv.innerHTML = '';
document.body.appendChild(clipboardDiv);
function copyHtmlToClipboard(html) {
clipboardDiv.innerHTML=html;
var focused=document.activeElement;
clipboardDiv.focus();
window.getSelection().removeAllRanges();
var range = document.createRange();
range.setStartBefore(clipboardDiv.firstChild);
range.setEndAfter(clipboardDiv.lastChild);
window.getSelection().addRange(range);
var ok=false;
try {
if (document.execCommand('copy')) ok=true; else utils.log('execCommand returned false !');
} catch (err) {
utils.log('execCommand failed ! exception '+err);
}
focused.focus();
}
see jsfiddle where you can enter html segment into the textarea and copy to the clipboard with ctrl+c.
For those looking for a way to do this using ClipboardItem and cannot get it to work even with dom.events.asyncClipboard.clipboardItem set to true, the answer can be found at nikouusitalo.com.
And here is my working code (tested on Firefox 102).
const clipboardItem = new
ClipboardItem({'text/html': new Blob([html],
{type: 'text/html'}),
'text/plain': new Blob([html],
{type: 'text/plain'})});
navigator.clipboard.write([clipboardItem]).
then(_ => console.log("clipboard.write() Ok"),
error => alert(error));
Make sure you try pasting it into a rich text editor such as gmail and not to a plain text/markdown editor such as stackoverflow.
Let me start off by saying I am new to add-on development. Using the Add-on SDK, I am trying to create a simple Firefox add-on that, when a button is pressed, acts like pressing the Ctrl-S hotkey, or following the file -> save page as to get the save page pop up window. I have looked at similar questions on here, but they seem to be going around the built in save functions, and not utilizing the "save page as" window.
The end goal is to run other functions before the save call is made. The user will only see the save page window as normal.
I am unaware of ways to send hotkey signals, or accessing the file drop down menu from within an add-on.
One way to do this is to invoke the Save As dialog exactly as if the user had clicked on the "Save Page As..." menu item (id="menu_savePage"). You can do this by executing the doCommand() method of that menu item. The following assumes that the event passed in is the command event for the button that the user clicked.
function launchSaveAsFromButton(event) {
var window = event.view;
//Create some common variables if they do not exist.
// This should work from any Firefox context.
// Depending on the context in which the function is being run,
// this could be simplified.
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
//* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
if (typeof document === "undefined") {
//If there is no document defined, get it
var document = window.content.document;
}
if (typeof gBrowser === "undefined") {
//If there is no gBrowser defined, get it
var gBrowser = window.gBrowser;
}
let menuSavePage = gBrowser.ownerDocument.getElementById("menu_savePage");
menuSavePage.doCommand();
}
Finding out the ID for the "Save Page As..." dialog is made easier by using the DOM Inspector in combination with the add-on Element Inspector.
I've written a single-page application using Google Maps in conjunction with AngularJS.
In short I have three columns - one filled with tools, one with a table of data, and the final is the geographical representation of the table's data.
The data is updated every minute.
The users all have multiple monitors, and it's been requested that adding the ability to have the map on one window, and table on the other - so that the map can have more real estate and we can add more columns to the table.
The solution that I really want is to create a new window using JS, and move the map's DIV to this window.
This works in chrome and it works exactly as I expected - moving the div doesn't break google map's or Angular's ties to the DOM, as the DIV is never deleted nor cloned, just moved within the DOM.
However IE11 treats the new window as an entirely separate document, and disallows the use of appendChild (throwing heirachy exceptions).
I have tried using adoptNode to move the node to the new window's document, but IE throws a 'No such interface supported' error on the adoptNode call, however IE allows you to use adoptNode to move the node between iframes, see here: http://jsfiddle.net/nx9u4y9w/9/
This works in FF/Chrome.
var f1 = window.f1,
f2 = window.f2,
pop = window.open(f2.location.href, 'pop'),
x = 0;
window.fx = function(o) {
var source = f1,
target = f2,
map;
if (x++ == 1) {
source = f2;
target = pop;
o.disabled = true;
}
map = source.document.getElementById('map-canvas');
var adopted = target.document.adoptNode(map);
target.document.body.appendChild(adopted);
}
Is there a simple way to get this working in IE11?
I wanted to avoid creating a new HTML page to load into the window, as it'd cause a serious break in the UX whilst the map reloads, plus it'll there's the code overhead of having to create an intra-browser messaging using postMessage, etc.
I'm trying to dynamically change the style of a tab using the add-on SDK. How can I do this?
Here's what I have tried:
I can access tab objects like this:
var tabs=require('sdk/tabs');
tabs.on('ready',function(tab){
console.log('url is: '+tab.url); //-> url is http://www.google.com
console.log('stlye is: '+tab.url); //-> style is null
});
But the style attribute is null and none of the following work:
tab.setAttribute('style','background-color:blue'); // the method doesn't exist
tab.style.backgroundColor='blue'; // type error because style is null
tab.style='background-color:blue'; // has no effect
So how can I change the style of a tab dynamically? Another thing I have tried is converting the tab to a XUL object using code from the docs:
var { modelFor } = require("sdk/model/core");
var { viewFor } = require("sdk/view/core");
var tabs = require("sdk/tabs");
var tab_utils = require("sdk/tabs/utils");
function mapHighLevelToLowLevel(tab) {
// get the XUL tab that corresponds to this high-level tab
var lowLevelTab = viewFor(tab);
// now we can, for example, access the tab's content directly
var browser = tab_utils.getBrowserForTab(lowLevelTab);
console.log(browser.contentDocument.body.innerHTML);
// get the high-level tab back from the XUL tab
var highLevelTab = modelFor(lowLevelTab);
console.log(highLevelTab.url);
}
tabs.on("ready", mapHighLevelToLowLevel);
But the code throws an error: Module 'sdk/model/core' is not found at resource://gre/modules/commonjs/sdk/model/core.js
even though I followed the directions and created the core.js file. Also, I don't understand the what the curly braces are doing in the var { modelFor}= syntax.
You need to access the xul tab element.
Try this:
var tabsLib = require("tabs/tab.js");
var tab = tabsLib.getTabForWindow(htmlWindow);
tab.style.color = 'red'; //makes the tab text red
pass htmlWindow as the topmost window of the html document. So like document.defaultView.top. document.defaultView is the window of the document
if that doesnt work then just get the browser window, this example gets the most recent browser window (keeep in mind there may be multiple browser windows open (browser window is a firefox window that has tabs, [and maybe popup windows- im not sure in sdk])
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
var aDOMWindow = getMostRecentBrowserWindow();
if (aDOMWindow.gBrowser && aDOMWindow.gBrowser.tabContainer) {
var tabs = aDOMWindow.gBrowser.tabContainer.childNodes;
for (var i=0; i<tabs.length; i++) {
tabs[i].style.color = 'red'; //makes the tab text red;
}
}