/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.gwt.dom.client.internal.ie;
import org.xwiki.gwt.dom.client.Document;
import com.google.gwt.core.client.JavaScriptObject;
/**
* Wraps the selection JavaScript object provided by Internet Explorer.
*
* @version $Id: bb02c211aff13a05f9472fdfd059fe96a5c4e6d7 $
*/
public final class NativeSelection extends JavaScriptObject
{
/**
* Default constructor. Needs to be protected because all instances are created from JavaScript.
*/
protected NativeSelection()
{
}
/**
* NOTE: If there's no selected range in the given document then the returned selection object corresponds to the
* parent document. As a consequence, ranges obtained through this selection object using the native API are from
* the parent document. {@link #ensureSelectionIsPreserved(Document)} tries to fix this, but we have to return a
* non-null selection object even if it's not the right one, as long as we set the {@code ownerDocument} property to
* the right value.
*
* @param document the DOM document for which to retrieve the selection object
* @return the selection object associated with the given in-line frame
*/
public static synchronized native NativeSelection getInstance(Document document)
/*-{
var selection = document.selection;
selection.ownerDocument = document;
return selection;
}-*/;
/**
* Ensures that the selection of the given document is preserved when the document looses focus. This method is
* required because IE has only one selection object per top level window. This means that when a child document
* looses the focus its selection object will return ranges from the parent document.
*
* @param document the document whose selection has to be preserved
*/
public static native void ensureSelectionIsPreserved(Document document)
/*-{
// If there is a previously stored selection then restore it. We have to do this before the edited document
// gets focused to allow users to have a different selection than the stored one (by clicking inside the edited
// document when it doesn't have the focus).
var restoreSelectionHandler = function(event) {
// In standards mode, the HTML element can gain focus separately from the BODY element without clearing its
// inner selection. For instance, by using the scroll bars we move the focus from the BODY element to the
// HTML element but the BODY element keeps its inner selection. In consequence, we don't have to restore
// the selection if the focus comes from the HTML element.
//
// Also, IE renders some elements as boxes (elements with fixed width or height, or with
// display:inline-block for instance) which can be edited by double clicking. Although this boxes are inside
// the edited document, the BODY element looses the focus when this elements are edited. There's no need to
// restore the selection when the focus comes from such an inner node.
if (!event.fromElement || event.fromElement.ownerDocument != document) {
var range = document.parentWindow.__xwe_savedRange;
// Clear the reference to the saved range.
document.parentWindow.__xwe_savedRange = undefined;
try {
// Try to restore the saved range as it is, preserving its type.
range.select();
} catch (e) {
try {
// Try to restore the saved range as a text range since it may be a control range with an
// invalid element. This can happen if an invalid control range is selected while the parent
// window is not focused. See NativeRange#select() and ControlRange#add(Element).
range.execCommand('SelectAll');
} catch(e) {
// ignore
}
}
}
};
document.body.attachEvent('onbeforeactivate', restoreSelectionHandler);
// 'onbeforeactivate' event is not fired when the parent window is focused from code (which happens for instance
// when we click on the HTML element).
document.parentWindow.attachEvent('onfocus', restoreSelectionHandler);
// Save the selection when the edited document is about to loose focus.
document.body.attachEvent('onbeforedeactivate', function(event) {
// In standards mode, the HTML element can gain focus separately from the BODY element without clearing its
// inner selection. For instance, by using the scroll bars we move the focus from the BODY element to the
// HTML element but the BODY element keeps its inner selection. In consequence, we don't have to save the
// selection if the focus goes to the HTML element.
//
// Also, IE renders some elements as boxes (elements with fixed width or height, or with
// display:inline-block for instance) which can be edited by double clicking. Although this boxes are inside
// the edited document, the BODY element looses the focus when this elements are edited. There's no need to
// save the selection when the focus goes to such an inner node.
if (!event.toElement || event.toElement.ownerDocument != document) {
document.parentWindow.__xwe_savedRange = document.selection.createRange();
document.parentWindow.__xwe_savedRange.ownerDocument = document;
}
});
}-*/;
/**
* Creates a TextRange object from the current text selection, or a ControlRange object from a control selection.
*
* @return the created range object
*/
public native NativeRange createRange()
/*-{
// Internet Explorer is limited to a single selection. Although each DOM document has its own selection object,
// they all return the same range which corresponds to the unique selection. When we have nested documents, the
// selection is in one of them, the focused one precisely. So it can happen that the range created from a
// document points to another document. Also, when a document looses focus its selection is lost; further calls
// to create range return the range from the focused document. In order to overcome this severe limitation of
// Internet Explorer we have to save the selection whenever a document looses focus and restore it when it gains
// it back. Additionally, we have to be able to work with a document's selection while it doesn't have the
// focus. To do so, we save a reference to the current range as a property of the window object. While the
// document doesn't have the focus we read and overwrite this range, instead of using the selection object.
var savedRange = this.ownerDocument.parentWindow.__xwe_savedRange;
if (typeof(savedRange) == 'undefined') {
// There's no saved range. Default on current range.
var currentRange = this.createRange();
currentRange.ownerDocument = this.ownerDocument;
// Ranges unsupported by the native selection are cached (ranges that start or end inside a hidden element).
var cachedRange = this.ownerDocument.parentWindow.__xwe_cachedRange;
var cachedRangeWitness = this.ownerDocument.parentWindow.__xwe_cachedRangeWitness;
// If there's a cached range and the witness equals the current range then return the cached range.
if (typeof(cachedRange) != 'undefined' && @org.xwiki.gwt.dom.client.internal.ie.NativeRange::areEqual(Lorg/xwiki/gwt/dom/client/internal/ie/NativeRange;Lorg/xwiki/gwt/dom/client/internal/ie/NativeRange;)(currentRange, cachedRangeWitness)) {
return @org.xwiki.gwt.dom.client.internal.ie.NativeRange::duplicate(Lorg/xwiki/gwt/dom/client/internal/ie/NativeRange;)(cachedRange);
} else {
// Reset the cache.
this.ownerDocument.parentWindow.__xwe_cachedRange = undefined;
this.ownerDocument.parentWindow.__xwe_cachedRangeWitness = undefined;
return currentRange;
}
} else {
// There's a saved range. Return a clone of the saved range.
return @org.xwiki.gwt.dom.client.internal.ie.NativeRange::duplicate(Lorg/xwiki/gwt/dom/client/internal/ie/NativeRange;)(savedRange);
}
}-*/;
/**
* Cancels the current selection and sets the selection type to none.
*/
public native void empty()
/*-{
if (typeof(this.ownerDocument.parentWindow.__xwe_savedRange) == 'undefined') {
try {
// Try canceling the current selection. This throws an exception if the parent window is hidden.
this.empty();
} catch(e) {
// Do nothing.
}
} else {
// Save an invalid range.
this.ownerDocument.parentWindow.__xwe_savedRange = {select : function(){}};
}
}-*/;
/**
* @return the document associated with this selection object
*/
public native Document getOwnerDocument()
/*-{
return this.ownerDocument;
}-*/;
}