2018-01-19 03:59:58 +08:00
|
|
|
|
/* 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";
|
|
|
|
|
|
|
|
|
|
|
|
module.metadata = {
|
|
|
|
|
|
"stability": "stable",
|
|
|
|
|
|
"engines": {
|
2018-04-19 23:42:13 +03:00
|
|
|
|
"Palemoon": "*",
|
2018-01-19 03:59:58 +08:00
|
|
|
|
"Firefox": "*",
|
|
|
|
|
|
"SeaMonkey": "*"
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const { Ci, Cc } = require("chrome"),
|
|
|
|
|
|
{ setTimeout } = require("./timers"),
|
|
|
|
|
|
{ emit, off } = require("./event/core"),
|
|
|
|
|
|
{ Class, obscure } = require("./core/heritage"),
|
|
|
|
|
|
{ EventTarget } = require("./event/target"),
|
|
|
|
|
|
{ ns } = require("./core/namespace"),
|
|
|
|
|
|
{ when: unload } = require("./system/unload"),
|
|
|
|
|
|
{ ignoreWindow } = require('./private-browsing/utils'),
|
|
|
|
|
|
{ getTabs, getTabForContentWindow,
|
|
|
|
|
|
getAllTabContentWindows } = require('./tabs/utils'),
|
|
|
|
|
|
winUtils = require("./window/utils"),
|
|
|
|
|
|
events = require("./system/events");
|
|
|
|
|
|
|
|
|
|
|
|
// The selection types
|
|
|
|
|
|
const HTML = 0x01,
|
|
|
|
|
|
TEXT = 0x02,
|
|
|
|
|
|
DOM = 0x03; // internal use only
|
|
|
|
|
|
|
|
|
|
|
|
// A more developer-friendly message than the caught exception when is not
|
|
|
|
|
|
// possible change a selection.
|
|
|
|
|
|
const ERR_CANNOT_CHANGE_SELECTION =
|
|
|
|
|
|
"It isn't possible to change the selection, as there isn't currently a selection";
|
|
|
|
|
|
|
|
|
|
|
|
const selections = ns();
|
|
|
|
|
|
|
|
|
|
|
|
const Selection = Class({
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Creates an object from which a selection can be set, get, etc. Each
|
|
|
|
|
|
* object has an associated with a range number. Range numbers are the
|
|
|
|
|
|
* 0-indexed counter of selection ranges as explained at
|
|
|
|
|
|
* https://developer.mozilla.org/en/DOM/Selection.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param rangeNumber
|
|
|
|
|
|
* The zero-based range index into the selection
|
|
|
|
|
|
*/
|
|
|
|
|
|
initialize: function initialize(rangeNumber) {
|
|
|
|
|
|
// In order to hide the private `rangeNumber` argument from API consumers
|
|
|
|
|
|
// while still enabling Selection getters/setters to access it, we define
|
|
|
|
|
|
// it as non enumerable, non configurable property. While consumers still
|
|
|
|
|
|
// may discover it they won't be able to do any harm which is good enough
|
|
|
|
|
|
// in this case.
|
|
|
|
|
|
Object.defineProperties(this, {
|
|
|
|
|
|
rangeNumber: {
|
|
|
|
|
|
enumerable: false,
|
|
|
|
|
|
configurable: false,
|
|
|
|
|
|
value: rangeNumber
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
get text() { return getSelection(TEXT, this.rangeNumber); },
|
|
|
|
|
|
set text(value) { setSelection(TEXT, value, this.rangeNumber); },
|
|
|
|
|
|
get html() { return getSelection(HTML, this.rangeNumber); },
|
|
|
|
|
|
set html(value) { setSelection(HTML, value, this.rangeNumber); },
|
|
|
|
|
|
get isContiguous() {
|
|
|
|
|
|
|
|
|
|
|
|
// If there are multiple non empty ranges, the selection is definitely
|
|
|
|
|
|
// discontiguous. It returns `false` also if there are no valid selection.
|
|
|
|
|
|
let count = 0;
|
|
|
|
|
|
for (let sel in selectionIterator)
|
|
|
|
|
|
if (++count > 1)
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
return count === 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const selectionListener = {
|
|
|
|
|
|
notifySelectionChanged: function (document, selection, reason) {
|
|
|
|
|
|
if (!["SELECTALL", "KEYPRESS", "MOUSEUP"].some(type => reason &
|
|
|
|
|
|
Ci.nsISelectionListener[type + "_REASON"]) || selection.toString() == "")
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
this.onSelect();
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
onSelect: function() {
|
|
|
|
|
|
emit(module.exports, "select");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Defines iterators so that discontiguous selections can be iterated.
|
|
|
|
|
|
* Empty selections are skipped - see `safeGetRange` for further details.
|
|
|
|
|
|
*
|
|
|
|
|
|
* If discontiguous selections are in a text field, only the first one
|
|
|
|
|
|
* is returned because the text field selection APIs doesn't support
|
|
|
|
|
|
* multiple selections.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function* forOfIterator() {
|
|
|
|
|
|
let selection = getSelection(DOM);
|
|
|
|
|
|
let count = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (selection)
|
|
|
|
|
|
count = selection.rangeCount || (getElementWithSelection() ? 1 : 0);
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
|
let sel = Selection(i);
|
|
|
|
|
|
|
|
|
|
|
|
if (sel.text)
|
|
|
|
|
|
yield Selection(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selectionIteratorOptions = {
|
|
|
|
|
|
__iterator__: function() {
|
|
|
|
|
|
for (let item of this)
|
|
|
|
|
|
yield item;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
selectionIteratorOptions[Symbol.iterator] = forOfIterator;
|
|
|
|
|
|
const selectionIterator = obscure(selectionIteratorOptions);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns the most recent focused window.
|
|
|
|
|
|
* if private browsing window is most recent and not supported,
|
|
|
|
|
|
* then ignore it and return `null`, because the focused window
|
|
|
|
|
|
* can't be targeted.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getFocusedWindow() {
|
|
|
|
|
|
let window = winUtils.getFocusedWindow();
|
|
|
|
|
|
|
|
|
|
|
|
return ignoreWindow(window) ? null : window;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns the focused element in the most recent focused window
|
|
|
|
|
|
* if private browsing window is most recent and not supported,
|
|
|
|
|
|
* then ignore it and return `null`, because the focused element
|
|
|
|
|
|
* can't be targeted.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getFocusedElement() {
|
|
|
|
|
|
let element = winUtils.getFocusedElement();
|
|
|
|
|
|
|
|
|
|
|
|
if (!element || ignoreWindow(element.ownerDocument.defaultView))
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
return element;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns the current selection from most recent content window. Depending on
|
|
|
|
|
|
* the specified |type|, the value returned can be a string of text, stringified
|
|
|
|
|
|
* HTML, or a DOM selection object as described at
|
|
|
|
|
|
* https://developer.mozilla.org/en/DOM/Selection.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param type
|
|
|
|
|
|
* Specifies the return type of the selection. Valid values are the one
|
|
|
|
|
|
* of the constants HTML, TEXT, or DOM.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param rangeNumber
|
|
|
|
|
|
* Specifies the zero-based range index of the returned selection.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getSelection(type, rangeNumber) {
|
|
|
|
|
|
let window, selection;
|
|
|
|
|
|
try {
|
|
|
|
|
|
window = getFocusedWindow();
|
|
|
|
|
|
selection = window.getSelection();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (e) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get the selected content as the specified type
|
|
|
|
|
|
if (type == DOM) {
|
|
|
|
|
|
return selection;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (type == TEXT) {
|
|
|
|
|
|
let range = safeGetRange(selection, rangeNumber);
|
|
|
|
|
|
|
|
|
|
|
|
if (range)
|
|
|
|
|
|
return range.toString();
|
|
|
|
|
|
|
|
|
|
|
|
let node = getElementWithSelection();
|
|
|
|
|
|
|
|
|
|
|
|
if (!node)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
return node.value.substring(node.selectionStart, node.selectionEnd);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (type == HTML) {
|
|
|
|
|
|
let range = safeGetRange(selection, rangeNumber);
|
|
|
|
|
|
// Another way, but this includes the xmlns attribute for all elements in
|
|
|
|
|
|
// Gecko 1.9.2+ :
|
|
|
|
|
|
// return Cc["@mozilla.org/xmlextras/xmlserializer;1"].
|
|
|
|
|
|
// createInstance(Ci.nsIDOMSerializer).serializeToSTring(range.
|
|
|
|
|
|
// cloneContents());
|
|
|
|
|
|
if (!range)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
let node = window.document.createElement("span");
|
|
|
|
|
|
node.appendChild(range.cloneContents());
|
|
|
|
|
|
return node.innerHTML;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error("Type " + type + " is unrecognized.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Sets the current selection of the most recent content document by changing
|
|
|
|
|
|
* the existing selected text/HTML range to the specified value.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param val
|
|
|
|
|
|
* The value for the new selection
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param rangeNumber
|
|
|
|
|
|
* The zero-based range index of the selection to be set
|
|
|
|
|
|
*
|
|
|
|
|
|
*/
|
|
|
|
|
|
function setSelection(type, val, rangeNumber) {
|
|
|
|
|
|
// Make sure we have a window context & that there is a current selection.
|
|
|
|
|
|
// Selection cannot be set unless there is an existing selection.
|
|
|
|
|
|
let window, selection;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
window = getFocusedWindow();
|
|
|
|
|
|
selection = window.getSelection();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (e) {
|
|
|
|
|
|
throw new Error(ERR_CANNOT_CHANGE_SELECTION);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let range = safeGetRange(selection, rangeNumber);
|
|
|
|
|
|
|
|
|
|
|
|
if (range) {
|
|
|
|
|
|
let fragment;
|
|
|
|
|
|
|
|
|
|
|
|
if (type === HTML)
|
|
|
|
|
|
fragment = range.createContextualFragment(val);
|
|
|
|
|
|
else {
|
|
|
|
|
|
fragment = range.createContextualFragment("");
|
|
|
|
|
|
fragment.textContent = val;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
range.deleteContents();
|
|
|
|
|
|
range.insertNode(fragment);
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
let node = getElementWithSelection();
|
|
|
|
|
|
|
|
|
|
|
|
if (!node)
|
|
|
|
|
|
throw new Error(ERR_CANNOT_CHANGE_SELECTION);
|
|
|
|
|
|
|
|
|
|
|
|
let { value, selectionStart, selectionEnd } = node;
|
|
|
|
|
|
|
|
|
|
|
|
let newSelectionEnd = selectionStart + val.length;
|
|
|
|
|
|
|
|
|
|
|
|
node.value = value.substring(0, selectionStart) +
|
|
|
|
|
|
val +
|
|
|
|
|
|
value.substring(selectionEnd, value.length);
|
|
|
|
|
|
|
|
|
|
|
|
node.setSelectionRange(selectionStart, newSelectionEnd);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns the specified range in a selection without throwing an exception.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param selection
|
|
|
|
|
|
* A selection object as described at
|
|
|
|
|
|
* https://developer.mozilla.org/en/DOM/Selection
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param [rangeNumber]
|
|
|
|
|
|
* Specifies the zero-based range index of the returned selection.
|
|
|
|
|
|
* If it's not provided the function will return the first non empty
|
|
|
|
|
|
* range, if any.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function safeGetRange(selection, rangeNumber) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let { rangeCount } = selection;
|
|
|
|
|
|
let range = null;
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof rangeNumber === "undefined")
|
|
|
|
|
|
rangeNumber = 0;
|
|
|
|
|
|
else
|
|
|
|
|
|
rangeCount = rangeNumber + 1;
|
|
|
|
|
|
|
|
|
|
|
|
for (; rangeNumber < rangeCount; rangeNumber++ ) {
|
|
|
|
|
|
range = selection.getRangeAt(rangeNumber);
|
|
|
|
|
|
|
|
|
|
|
|
if (range && range.toString())
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
range = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return range;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (e) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Returns a reference of the DOM's active element for the window given, if it
|
|
|
|
|
|
* supports the text field selection API and has a text selected.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Note:
|
|
|
|
|
|
* we need this method because window.getSelection doesn't return a selection
|
|
|
|
|
|
* for text selected in a form field (see bug 85686)
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getElementWithSelection() {
|
|
|
|
|
|
let element = getFocusedElement();
|
|
|
|
|
|
|
|
|
|
|
|
if (!element)
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Accessing selectionStart and selectionEnd on e.g. a button
|
|
|
|
|
|
// results in an exception thrown as per the HTML5 spec. See
|
|
|
|
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
|
|
|
|
|
|
|
|
|
|
|
|
let { value, selectionStart, selectionEnd } = element;
|
|
|
|
|
|
|
|
|
|
|
|
let hasSelection = typeof value === "string" &&
|
|
|
|
|
|
!isNaN(selectionStart) &&
|
|
|
|
|
|
!isNaN(selectionEnd) &&
|
|
|
|
|
|
selectionStart !== selectionEnd;
|
|
|
|
|
|
|
|
|
|
|
|
return hasSelection ? element : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (err) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Adds the Selection Listener to the content's window given
|
|
|
|
|
|
*/
|
|
|
|
|
|
function addSelectionListener(window) {
|
|
|
|
|
|
let selection = window.getSelection();
|
|
|
|
|
|
|
|
|
|
|
|
// Don't add the selection's listener more than once to the same window,
|
|
|
|
|
|
// if the selection object is the same
|
|
|
|
|
|
if ("selection" in selections(window) && selections(window).selection === selection)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// We ensure that the current selection is an instance of
|
|
|
|
|
|
// `nsISelectionPrivate` before working on it, in case is `null`.
|
|
|
|
|
|
//
|
|
|
|
|
|
// If it's `null` it's likely too early to add the listener, and we demand
|
|
|
|
|
|
// that operation to `document-shown` - it can easily happens for frames
|
|
|
|
|
|
if (selection instanceof Ci.nsISelectionPrivate)
|
|
|
|
|
|
selection.addSelectionListener(selectionListener);
|
|
|
|
|
|
|
|
|
|
|
|
// nsISelectionListener implementation seems not fire a notification if
|
|
|
|
|
|
// a selection is in a text field, therefore we need to add a listener to
|
|
|
|
|
|
// window.onselect, that is fired only for text fields.
|
|
|
|
|
|
// For consistency, we add it only when the nsISelectionListener is added.
|
|
|
|
|
|
//
|
|
|
|
|
|
// https://developer.mozilla.org/en/DOM/window.onselect
|
|
|
|
|
|
window.addEventListener("select", selectionListener.onSelect, true);
|
|
|
|
|
|
|
|
|
|
|
|
selections(window).selection = selection;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Removes the Selection Listener to the content's window given
|
|
|
|
|
|
*/
|
|
|
|
|
|
function removeSelectionListener(window) {
|
|
|
|
|
|
// Don't remove the selection's listener to a window that wasn't handled.
|
|
|
|
|
|
if (!("selection" in selections(window)))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
let selection = window.getSelection();
|
|
|
|
|
|
let isSameSelection = selection === selections(window).selection;
|
|
|
|
|
|
|
|
|
|
|
|
// Before remove the listener, we ensure that the current selection is an
|
|
|
|
|
|
// instance of `nsISelectionPrivate` (it could be `null`), and that is still
|
|
|
|
|
|
// the selection we managed for this window (it could be detached).
|
|
|
|
|
|
if (selection instanceof Ci.nsISelectionPrivate && isSameSelection)
|
|
|
|
|
|
selection.removeSelectionListener(selectionListener);
|
|
|
|
|
|
|
|
|
|
|
|
window.removeEventListener("select", selectionListener.onSelect, true);
|
|
|
|
|
|
|
|
|
|
|
|
delete selections(window).selection;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function onContent(event) {
|
|
|
|
|
|
let window = event.subject.defaultView;
|
|
|
|
|
|
|
|
|
|
|
|
// We are not interested in documents without valid defaultView (e.g. XML)
|
|
|
|
|
|
// that aren't in a tab (e.g. Panel); or in private windows
|
|
|
|
|
|
if (window && getTabForContentWindow(window) && !ignoreWindow(window)) {
|
|
|
|
|
|
addSelectionListener(window);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Adds Selection listener to new documents
|
|
|
|
|
|
// Note that strong reference is needed for documents that are loading slowly or
|
|
|
|
|
|
// where the server didn't close the connection (e.g. "comet").
|
|
|
|
|
|
events.on("document-element-inserted", onContent, true);
|
|
|
|
|
|
|
|
|
|
|
|
// Adds Selection listeners to existing documents
|
|
|
|
|
|
getAllTabContentWindows().forEach(addSelectionListener);
|
|
|
|
|
|
|
|
|
|
|
|
// When a document is not visible anymore the selection object is detached, and
|
|
|
|
|
|
// a new selection object is created when it becomes visible again.
|
|
|
|
|
|
// That makes the previous selection's listeners added previously totally
|
|
|
|
|
|
// useless – the listeners are not notified anymore.
|
|
|
|
|
|
// To fix that we're listening for `document-shown` event in order to add
|
|
|
|
|
|
// the listeners to the new selection object created.
|
|
|
|
|
|
//
|
|
|
|
|
|
// See bug 665386 for further details.
|
|
|
|
|
|
|
|
|
|
|
|
function onShown(event) {
|
|
|
|
|
|
let window = event.subject.defaultView;
|
|
|
|
|
|
|
|
|
|
|
|
// We are not interested in documents without valid defaultView.
|
|
|
|
|
|
// For example XML documents don't have windows and we don't yet support them.
|
|
|
|
|
|
if (!window)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// We want to handle only the windows where we added selection's listeners
|
|
|
|
|
|
if ("selection" in selections(window)) {
|
|
|
|
|
|
let currentSelection = window.getSelection();
|
|
|
|
|
|
let { selection } = selections(window);
|
|
|
|
|
|
|
|
|
|
|
|
// If the current selection for the window given is different from the one
|
|
|
|
|
|
// stored in the namespace, we need to add the listeners again, and replace
|
|
|
|
|
|
// the previous selection in our list with the new one.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Notice that we don't have to remove the listeners from the old selection,
|
|
|
|
|
|
// because is detached. An attempt to remove the listener, will raise an
|
|
|
|
|
|
// error (see http://mxr.mozilla.org/mozilla-central/source/layout/generic/nsSelection.cpp#5343 )
|
|
|
|
|
|
//
|
|
|
|
|
|
// We ensure that the current selection is an instance of
|
|
|
|
|
|
// `nsISelectionPrivate` before working on it, in case is `null`.
|
|
|
|
|
|
if (currentSelection instanceof Ci.nsISelectionPrivate &&
|
|
|
|
|
|
currentSelection !== selection) {
|
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener("select", selectionListener.onSelect, true);
|
|
|
|
|
|
currentSelection.addSelectionListener(selectionListener);
|
|
|
|
|
|
selections(window).selection = currentSelection;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
events.on("document-shown", onShown, true);
|
|
|
|
|
|
|
|
|
|
|
|
// Removes Selection listeners when the add-on is unloaded
|
|
|
|
|
|
unload(function(){
|
|
|
|
|
|
getAllTabContentWindows().forEach(removeSelectionListener);
|
|
|
|
|
|
|
|
|
|
|
|
events.off("document-element-inserted", onContent);
|
|
|
|
|
|
events.off("document-shown", onShown);
|
|
|
|
|
|
|
|
|
|
|
|
off(exports);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const selection = Class({
|
|
|
|
|
|
extends: EventTarget,
|
|
|
|
|
|
implements: [ Selection, selectionIterator ]
|
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = selection;
|