/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.client.common.util; import com.google.gwt.core.client.GWT; /** * Collection of constants defining various browser quirky behaviours. * * Each constant should be accompanied by a detailed comment, and a "Tested:" * section detailing which browsers and operating systems the quirk has been * tested on, so that it's easy to know what's untested as new browser versions * come out, etc. * * Sometimes an "Untested exceptions:" field is appropriate, to note exceptions * to the "Tested:" field, in particular if they are concerning and represent a * reasonable doubt as to the correctness of the field's value. * * It is preferable to use the constants in this class than to have logic that * switches on explicit browser checks. * * @author danilatos@google.com (Daniel Danilatos) */ public final class QuirksConstants { /** * Whether we get DOM Mutation events * * Tested: * Safari 3-4, Firefox 3-3.5, Chrome 1-2, IE7, IE8 * * Will IE9 give us mutation events? probably not. */ public static final boolean PROVIDES_MUTATION_EVENTS = UserAgent.isFirefox() || UserAgent.isWebkit(); /** * Whether the browser left normalises the caret in most cases (There are * exceptions, usually to do with links). * * Tested: * Safari 3*, Safari 4 beta, Firefox 3.0, IE7, IE8 */ public static final boolean USUALLY_LEFT_NORMALISES = UserAgent.isWebkit() || UserAgent.isWebkit(); /** * Certain versions of webkit have a specific hack implemented in them, where * they go against the regular left-normalised behaviour at the end of an * anchor boundary, if it has an href. They still report the cursor as being * left-normalised, but if the user types, text goes to the right, outside the * link. * * Tested: * All OS: Safari 3.2.1, Safari 4 beta, Chrome 1.0, Chrome 2.0 special sauce */ public static final boolean LIES_ABOUT_CARET_AT_LINK_END_BOUNDARY = UserAgent.isWebkit() && UserAgent.isAtLeastVersion(528, 0); /** * Similar to {@link #LIES_ABOUT_CARET_AT_LINK_END_BOUNDARY}, but does not * actually lie, just doesn't like reporting carets as being inside link * boundaries, and typing occurs outside as well. * * Tested: IE8 beta * * TODO(danilatos): check IE7 */ public static final boolean DOES_NOT_LEFT_NORMALISE_AT_LINK_END_BOUNDARY = UserAgent.isIE(); /** * If the user is typing, we always get a key event before the browser changes * the dom. * * Tested: * All OS: Safari 3.2.1, 4 beta, Chrome 1, 2, Firefox 3, IE7, IE8 * * Untested exceptions: Any IME on Linux! */ public static final boolean ALWAYS_GIVES_KEY_EVENT_BEFORE_CHANGING_DOM = UserAgent.isFirefox() || UserAgent.isIE7(); /** * Whether we get the magic 229 keycode for IME key events, at least for the * first one (sometimes we don't get key events for subsequent mutations). * * Tested: * All OS: Safari 3.2.1, 4 beta, Chrome 1, 2, Firefox 3.0 * * Untested exceptions: Any IME on Linux! */ public static final boolean CAN_TELL_WHEN_FIRST_KEY_EVENT_IS_IME = UserAgent.isIE() || UserAgent.isWebkit() || UserAgent.isWin(); /** * Whether the old school Ctrl+Insert, Shift+Delete, Shift+Insert shortcuts * for Copy, Cut, Paste work. * * Tested: All OS: Firefox 3, IE 7/8, Safari 3, Chrome 2 * * Untested exceptions: Safari on Windows */ public static final boolean HAS_OLD_SCHOOL_CLIPBOARD_SHORTCUTS = UserAgent.isWin() || UserAgent.isLinux(); // NOTE(danilatos): These selection constants, unless mentioned otherwise, // refer to selection boundaries (e.g. the start, or end, of a ranged // selection, or a collapsed selection). /** * Whether the selection is either cleared or correctly transformed by the * browser in at least the following scenarios: * - textNode.insertData adds to the cursor location, if it is after the * deletion point * - textNode.deleteData subtracts from the cursor location, if it is after * the deletion point * * Tested: Safari 3.2.1, FF 3.0, 3.5, Chrome 1, IE7, IE8 */ public static final boolean OK_SELECTION_ACROSS_TEXT_NODE_DATA_CHANGES = UserAgent.isFirefox() || UserAgent.isIE(); /** * Whether the selection is either cleared or correctly transformed by the * browser when text nodes are deleted. * * Gecko/IE preserve, Webkit clears selection. Both OK behaviours. * * Tested: Safari 3.2.1, FF 3.0, 3.5, Chrome 1, IE7, IE8 */ public static final boolean OK_SELECTION_ACROSS_NODE_REMOVALS = true; /** * Whether the browser moves the selection into the neighbouring text node * after a text node split before the selection point, or at least clears the * selection. * * Tested: Safari 3.2.1, FF 3.0, 3.5, Chrome 1, IE7, IE8 */ public static final boolean OK_SELECTION_ACROSS_TEXT_NODE_SPLITS = UserAgent.isIE(); /** * In this case, only clearing occurs by Webkit. Other two browsers move the * selection to the point where the moved node was. Which is BAD for wrapping! * * Tested: Safari 3.2.1, FF 3.0, 3.5, Chrome 1, IE7, IE8 * * @see #PRESERVES_SEMANTIC_SELECTION_ACROSS_MUTATIONS_OR_CLEARS_IT */ public static final boolean OK_SELECTION_ACROSS_MOVES = UserAgent.isWebkit(); /** * Preserves changes to text nodes made by calling methods on the text nodes * directly (i.e. not moving or deleting the text nodes). */ public static final boolean PRESERVES_SEMANTIC_SELECTION_ACROSS_INTRINSIC_TEXT_NODE_CHANGES = OK_SELECTION_ACROSS_TEXT_NODE_DATA_CHANGES && OK_SELECTION_ACROSS_TEXT_NODE_SPLITS; /** * Whether the selection preservation is completely reliable across mutations * in terms of correctness. It might get cleared in some circumstances, but * that's OK, we can just check if the selection is gone and restore it. We * don't need to transform it for correctness. * * The biggest problem here is wrap. Currently implemented with insertBefore, * it breaks selections in all browsers, even IE, AND IE doesn't clear the * selection, just moves it. Damn! Anyway, there might be a smarter way to * implement wrap - perhaps using an exec command to apply a styling to a * region and using the resulting wrapper nodes... but that's a long way off. * * Tested: Safari 3.2.1, FF 3.0, 3.5, Chrome 1, IE7, IE8 */ public static final boolean PRESERVES_SEMANTIC_SELECTION_ACROSS_MUTATIONS_OR_CLEARS_IT = OK_SELECTION_ACROSS_TEXT_NODE_DATA_CHANGES && OK_SELECTION_ACROSS_TEXT_NODE_SPLITS && OK_SELECTION_ACROSS_MOVES; /** * Whether changing stuff in the middle of a ranged selection (that doesn't * affect the selection end points in any way), such as splitting some * entirely contained text node, affects the ranged selection. With firefox, * it appears that the selection is still "correct", but visually new text * nodes inserted don't get highlighted as selected, which is bad. * * Tested: Safari 3.2.1, FF 3.0, IE8 */ public static final boolean RANGED_SELECTION_AFFECTED_BY_INTERNAL_CHANGED = UserAgent.isFirefox(); /** * True if IME input in not totally munged by adjacent text node mutations * * Tested: * Safari 3.2.1, FF 3.0, 3.5, Chrome 1, IE7, IE8 */ public static final boolean PRESERVES_IME_STATE_ACROSS_ADJACENT_CHANGES = UserAgent.isIE(); /** * True if we can do the __proto__ override trick to remove defaults method * in a JSO. * WARNING(dnailatos/reuben) Should be kept as static constant for * speed reasons in string map implementation. * * Tested: Safari 4, FF 3.0, 3.5, Chrome 3-4, IE8 */ public static final boolean DOES_NOT_SUPPORT_JSO_PROTO_FIELD = UserAgent.isIE(); /** * It appears that in some browsers, if you stopPropagation() an IME * composition or contextmenu event, the default action for the event is not * executed (as if you had cancelled it) * * TODO(danilatos): File a bug * * Tested: * FF 3.0 * * Untested: * Everything else (at time of writing, nothing else has composition * events...) */ public static final boolean CANCEL_BUBBLING_CANCELS_IME_COMPOSITION_AND_CONTEXTMENU = UserAgent.isFirefox(); /** * True if mouse events have rangeParent and rangeOffset fields. * * Tested: * FF 3.0, 3.6 * Safari 4 * Chrome 5 */ public static final boolean SUPPORTS_EVENT_GET_RANGE_METHODS = UserAgent.isFirefox(); /** * True if preventDefault stops a native context menu from being shown. In * firefox this is not the case when dom.event.contextmenu.enabled is set. * * Tested: * FF 3.0, 3.6 */ public static final boolean PREVENT_DEFAULT_STOPS_CONTEXTMENT = !UserAgent.isFirefox(); /** * True if displaying a context menu updates the current selection. Safari selects * the word clicked unless you click on the current selection, Firefox does not * change the selection and Chrome and IE clears the selection unless you click * on the current selection. * * The selection that is active on mousedown will, in all browsers, be the * original selection and the selection on the contextmenu event will be the new * one. * * Tested: * FF 3.0, 3.5 * Chrome 5 * Safari 4 * IE 8 */ public static final boolean CONTEXTMENU_SETS_SELECTION = !UserAgent.isFirefox(); /** * True if the browser has the setBaseAndExtent JS method to allow better setting of * the selection within the browser. * * So far, only webkit browsers have this in their API, and documentation is scarce. See: * http://developer.apple.com/DOCUMENTATION/AppleApplications/Reference/WebKitDOMRef * /DOMSelection_idl/Classes/DOMSelection/index.html#//apple_ref/idl/instm * /DOMSelection/setBaseAndExtent/void/(inNode,inlong,inNode,inlong) */ public static final boolean HAS_BASE_AND_EXTENT = UserAgent.isWebkit(); /** * Chrome on Mac generates doesn't keypress for command combos, only keydown. * * In general, it makes sense to only fire the keypress event if the combo * generates content. https://bugs.webkit.org/show_bug.cgi?id=30397 * * However since it is the odd one out here, it is listed as a * quirk. */ public static final boolean COMMAND_COMBO_DOESNT_GIVE_KEYPRESS = UserAgent.isMac() && UserAgent.isChrome(); /** * True if the browser has native support for getElementsByClassName. * * Tested: * Chrome, Safari 3.1, Firefox 3.0 */ public static final boolean SUPPORTS_GET_ELEMENTS_BY_CLASSNAME = GWT.isScript() && checkGetElementsByClassNameSupport(); /** * True if the browser supports composition events. * * (This does not differentiate between the per-browser composition event quirks, * such as whether they provide a text vs a compositionupdate event, or other * glitches). * * Tested: * Chrome 3.0, 4.0; Safari 4; FF 3.0, 3.5; IE 7,8 */ public static final boolean SUPPORTS_COMPOSITION_EVENTS = UserAgent.isFirefox() || (UserAgent.isWebkit() && UserAgent.isAtLeastVersion(532, 5)); /** * True if the browser does an extra modification of the DOM after the * compositionend event, and also fires a text input event if the composition * was not cancelled. * * Tested: Chrome 4.0; FF 3.0, 3.5; */ public static final boolean MODIFIES_DOM_AND_FIRES_TEXTINPUT_AFTER_COMPOSITION = UserAgent.isWebkit(); // Put an upper bound on the version here when it's fixed... /** * True if the browser keeps the selection in an empty span after the * app has programmatically set it there. * * Tested: * Chrome 3.0, 4.0; Safari 3, 4; FF 3.0, 3.5; IE 7,8 */ public static final boolean SUPPORTS_CARET_IN_EMPTY_SPAN = UserAgent.isFirefox(); /** * True if the browser automatically scrolls a contentEditable element * into view when we set focus on the element * * Tested: * Chrome 5.0.307.11 beta / linux, Safari 4.0.4 / mac, Firefox 3.0.7 + 3.6 / linux */ public static final boolean ADJUSTS_SCROLL_TOP_WHEN_FOCUSING = UserAgent.isWebkit(); /** * True if the browser does not emit a paste event for plaintext paste. * This was a bug on Webkit and has been fixed and pushed to Chrome 4+ * * Tested: * Chrome 4.0.302.3; Safari 4.05 Mac */ public static final boolean PLAINTEXT_PASTE_DOES_NOT_EMIT_PASTE_EVENT = UserAgent.isSafari(); /** * True if the browser supports input type 'search'. * * Tested: * Chrome 9.0, Chrome 4.0 */ public static final boolean SUPPORTS_SEARCH_INPUT = UserAgent.isWebkit(); /** * True if the browser sanitizes pasted content to contenteditable to * prevent script execution. * * Tested: * Chrome 9.0, Safari 5, FF 3.5, FF 4.0 */ public static final boolean SANITIZES_PASTED_CONTENT = (UserAgent.isWebkit() && UserAgent.isAtLeastVersion(533, 16)) || (UserAgent.isFirefox() && UserAgent.isAtLeastVersion(4, 0)); private static native boolean checkGetElementsByClassNameSupport() /*-{ return !!document.body.getElementsByClassName; }-*/; private QuirksConstants(){} }