summaryrefslogtreecommitdiffstats
path: root/addon-sdk/source/lib/sdk/selection.js
diff options
context:
space:
mode:
Diffstat (limited to 'addon-sdk/source/lib/sdk/selection.js')
-rw-r--r--addon-sdk/source/lib/sdk/selection.js470
1 files changed, 0 insertions, 470 deletions
diff --git a/addon-sdk/source/lib/sdk/selection.js b/addon-sdk/source/lib/sdk/selection.js
deleted file mode 100644
index 8682e8c6d..000000000
--- a/addon-sdk/source/lib/sdk/selection.js
+++ /dev/null
@@ -1,470 +0,0 @@
-/* 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": {
- "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;