/* * CPCompatibility.j * AppKit * * Created by Francisco Tolmasky. * Copyright 2008, 280 North, Inc. * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @import "CPEvent_Constants.j" @import "CPPlatform.j" // Browser Engines CPUnknownBrowserEngine = 0; CPGeckoBrowserEngine = 1; CPInternetExplorerBrowserEngine = 2; CPKHTMLBrowserEngine = 3; CPOperaBrowserEngine = 4; CPWebKitBrowserEngine = 5; // Operating Systems CPMacOperatingSystem = 0; CPWindowsOperatingSystem = 1; CPOtherOperatingSystem = 2; // Features CPCSSRGBAFeature = 5; CPHTMLCanvasFeature = 6; CPHTMLContentEditableFeature = 7; CPHTMLDragAndDropFeature = 8; CPJavaScriptInnerTextFeature = 9; CPJavaScriptTextContentFeature = 10; // In onpaste, oncopy and oncut events, the event has an event.clipboardData from which the current pasteboard contents can be read with event.clipboardData.getData. CPJavaScriptClipboardEventsFeature = 11; // window.clipboardData exists and can be read and written to at any time using window.clipboardData.getData/setData. CPJavaScriptClipboardAccessFeature = 12; CPJavaScriptCanvasDrawFeature = 13; CPJavaScriptCanvasTransformFeature = 14; CPVMLFeature = 15; CPJavaScriptRemedialKeySupport = 16; CPJavaScriptShadowFeature = 20; CPJavaScriptNegativeMouseWheelValues = 22; CPJavaScriptMouseWheelValues_8_15 = 23; CPOpacityRequiresFilterFeature = 24; // Internet explorer does not allow dynamically changing the type of an input element CPInputTypeCanBeChangedFeature = 25; CPHTML5DragAndDropSourceYOffBy1 = 26; CPSOPDisabledFromFileURLs = 27; // element.style.font can be set for an element not in the DOM. CPInputSetFontOutsideOfDOM = 28; // Input elements have 1 px of extra padding on the left regardless of padding setting. CPInput1PxLeftPadding = 29; CPInputOnInputEventFeature = 30; CPFileAPIFeature = 31; /* When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen. This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203 */ CPCanvasParentDrawErrorsOnMovementBug = 1 << 0; // The paste event is only sent if an input or textarea has focus. CPJavaScriptPasteRequiresEditableTarget = 1 << 1; // Redirecting the focus of the browser on keydown to an input for Cmd-V or Ctrl-V makes the paste fail. CPJavaScriptPasteCantRefocus = 1 << 2; var USER_AGENT = "", PLATFORM_ENGINE = CPUnknownBrowserEngine, PLATFORM_FEATURES = [], PLATFORM_BUGS = 0, PLATFORM_STYLE_JS_PROPERTIES = {}; // default these features to true PLATFORM_FEATURES[CPInputTypeCanBeChangedFeature] = YES; PLATFORM_FEATURES[CPInputSetFontOutsideOfDOM] = YES; if (typeof window !== "undefined" && typeof window.navigator !== "undefined") USER_AGENT = window.navigator.userAgent; // Opera if (typeof window !== "undefined" && window.opera) { PLATFORM_ENGINE = CPOperaBrowserEngine; PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES; } // Internet Explorer else if (typeof window !== "undefined" && window.attachEvent) // Must follow Opera check. { PLATFORM_ENGINE = CPInternetExplorerBrowserEngine; // Features we can only be sure of with IE (no known independent tests) PLATFORM_FEATURES[CPVMLFeature] = YES; PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES; PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES; PLATFORM_FEATURES[CPOpacityRequiresFilterFeature] = YES; PLATFORM_FEATURES[CPInputTypeCanBeChangedFeature] = NO; // Tested in Internet Explore 8 and 9. PLATFORM_FEATURES[CPInputSetFontOutsideOfDOM] = NO; // IE allows free clipboard access. PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES; } // WebKit else if (USER_AGENT.indexOf("AppleWebKit/") != -1) { PLATFORM_ENGINE = CPWebKitBrowserEngine; // Features we can only be sure of with WebKit (no known independent tests) PLATFORM_FEATURES[CPCSSRGBAFeature] = YES; PLATFORM_FEATURES[CPHTMLContentEditableFeature] = YES; PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES; PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = NO; PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES; var versionStart = USER_AGENT.indexOf("AppleWebKit/") + "AppleWebKit/".length, versionEnd = USER_AGENT.indexOf(" ", versionStart), versionString = USER_AGENT.substring(versionStart, versionEnd), versionDivision = versionString.indexOf('.'), majorVersion = parseInt(versionString.substring(0, versionDivision)), minorVersion = parseInt(versionString.substr(versionDivision + 1)); if ((USER_AGENT.indexOf("Safari") !== CPNotFound && (majorVersion > 525 || (majorVersion === 525 && minorVersion > 14))) || USER_AGENT.indexOf("Chrome") !== CPNotFound) PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES; // FIXME this is a terrible hack to get around this bug: // https://bugs.webkit.org/show_bug.cgi?id=21548 if (![CPPlatform isBrowser]) PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES; if (majorVersion < 532 || (majorVersion === 532 && minorVersion < 6)) PLATFORM_FEATURES[CPHTML5DragAndDropSourceYOffBy1] = YES; // This is supposedly fixed in webkit r123603. Seems to work in Chrome 21 but not Safari 6.0. if (majorVersion < 537) PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES; if (USER_AGENT.indexOf("Chrome") === CPNotFound) { PLATFORM_FEATURES[CPSOPDisabledFromFileURLs] = YES; PLATFORM_FEATURES[CPHTMLDragAndDropFeature] = YES; // https://bugs.webkit.org/show_bug.cgi?id=75891 PLATFORM_BUGS |= CPJavaScriptPasteRequiresEditableTarget; // https://bugs.webkit.org/show_bug.cgi?id=39689 PLATFORM_BUGS |= CPJavaScriptPasteCantRefocus; } // Assume this bug was introduced around Safari 5.1/Chrome 16. This could probably be tighter. if (majorVersion > 533) PLATFORM_BUGS |= CPCanvasParentDrawErrorsOnMovementBug; } // KHTML else if (USER_AGENT.indexOf("KHTML") != -1) // Must follow WebKit check. { PLATFORM_ENGINE = CPKHTMLBrowserEngine; } // Gecko else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check. { PLATFORM_ENGINE = CPGeckoBrowserEngine; PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES; var index = USER_AGENT.indexOf("Firefox"), version = (index === -1) ? 2.0 : parseFloat(USER_AGENT.substring(index + "Firefox".length + 1)); if (version >= 3.0) PLATFORM_FEATURES[CPCSSRGBAFeature] = YES; if (version < 3.0) PLATFORM_FEATURES[CPJavaScriptMouseWheelValues_8_15] = YES; // Some day this might be fixed and should be version prefixed. No known fixed version yet. PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES; // This was supposed to be added in Firefox 22, but when testing with the latest beta as of 2013-06-14 // it does not seem to work. It seems to exhibit the CPJavaScriptPasteRequiresEditableTarget problem, // and in addition doesn't seem to work with our native copy code either. /*if (version >= 22.0) { PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES; // TODO File a bug at https://bugzilla.mozilla.org/. In other browsers, one can return "false" from the // beforepaste event to indicate a paste should be enabled even that the DOMEvent.target is not editable. PLATFORM_BUGS |= CPJavaScriptPasteRequiresEditableTarget; }*/ } // Feature-specific checks if (typeof document != "undefined") { var canvasElement = document.createElement("canvas"); // Detect canvas support if (canvasElement && canvasElement.getContext) { PLATFORM_FEATURES[CPHTMLCanvasFeature] = YES; // Any browser that supports canvas supports CSS opacity PLATFORM_FEATURES[CPOpacityRequiresFilterFeature] = NO; // Detect canvas setTransform/transform support var context = document.createElement("canvas").getContext("2d"); if (context && context.setTransform && context.transform) PLATFORM_FEATURES[CPJavaScriptCanvasTransformFeature] = YES; } var DOMElement = document.createElement("div"); // Detect whether we have innerText or textContent (or neither) if (DOMElement.innerText != undefined) PLATFORM_FEATURES[CPJavaScriptInnerTextFeature] = YES; else if (DOMElement.textContent != undefined) PLATFORM_FEATURES[CPJavaScriptTextContentFeature] = YES; var DOMInputElement = document.createElement("input"); if ("oninput" in DOMInputElement) PLATFORM_FEATURES[CPInputOnInputEventFeature] = YES; else if (typeof DOMInputElement.setAttribute === "function") { DOMInputElement.setAttribute("oninput", "return;"); if (typeof DOMInputElement.oninput === "function") PLATFORM_FEATURES[CPInputOnInputEventFeature] = YES; } // Detect FileAPI support if (typeof DOMInputElement.setAttribute === "function") { DOMInputElement.setAttribute("type", "file"); PLATFORM_FEATURES[CPFileAPIFeature] = !!DOMInputElement["files"]; } else PLATFORM_FEATURES[CPFileAPIFeature] = NO; } function CPFeatureIsCompatible(aFeature) { return !!PLATFORM_FEATURES[aFeature]; } function CPPlatformHasBug(aBug) { return PLATFORM_BUGS & aBug; } function CPBrowserIsEngine(anEngine) { return PLATFORM_ENGINE === anEngine; } function CPBrowserIsOperatingSystem(anOperatingSystem) { return OPERATING_SYSTEM === anOperatingSystem; } OPERATING_SYSTEM = CPOtherOperatingSystem; if (USER_AGENT.indexOf("Mac") !== -1) { OPERATING_SYSTEM = CPMacOperatingSystem; CPPlatformActionKeyMask = CPCommandKeyMask; CPUndoKeyEquivalent = @"z"; CPRedoKeyEquivalent = @"Z"; CPUndoKeyEquivalentModifierMask = CPCommandKeyMask; CPRedoKeyEquivalentModifierMask = CPCommandKeyMask; } else { if (USER_AGENT.indexOf("Windows") !== -1) OPERATING_SYSTEM = CPWindowsOperatingSystem; CPPlatformActionKeyMask = CPControlKeyMask; CPUndoKeyEquivalent = @"z"; CPRedoKeyEquivalent = @"y"; CPUndoKeyEquivalentModifierMask = CPControlKeyMask; CPRedoKeyEquivalentModifierMask = CPControlKeyMask; } /*! Return the properly prefixed JS property for the given name. E.g. in a webkit browser, CPBrowserStyleProperty('transition') -> WebkitTransition While technically not a style property, style related event handler names are also supported. CPBrowserStyleProperty('transitionend') -> 'webkitTransitionEnd' CSS is only available in platform(dom), so don't rely too heavily on it. */ function CPBrowserStyleProperty(aProperty) { var lowerProperty = aProperty.toLowerCase(); if (PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] === undefined) { var r = nil; #if PLATFORM(DOM) var testElement = document.createElement('div'); switch (lowerProperty) { case 'transitionend': var candidates = { 'WebkitTransition' : 'webkitTransitionEnd', 'MozTransition' : 'transitionend', 'OTransition' : 'oTransitionEnd', 'msTransition' : 'MSTransitionEnd', 'transition' : 'transitionend' }; r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transition']] || nil; break; case 'transformorigin': var candidates = { 'WebkitTransform' : 'WebkitTransformOrigin', 'MozTransform' : 'MozTransformOrigin', 'OTransform' : 'OTransformOrigin', 'msTransform' : 'MSTransformOrigin', 'transform' : 'transformOrigin' }; r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transform']] || nil; break; default: var prefixes = ["Webkit", "Moz", "O", "ms"], strippedProperty = aProperty.split('-').join(' '), capProperty = [strippedProperty capitalizedString].split(' ').join(''); for (var i = 0; i < prefixes.length; i++) { // First check if the property is already valid without being formatted, otherwise try the capitalized property if (prefixes[i] + aProperty in testElement.style) { r = prefixes[i] + aProperty; break; } else if (prefixes[i] + capProperty in testElement.style) { r = prefixes[i] + capProperty; break; } } if (!r && lowerProperty in testElement.style) r = lowerProperty; break; } #endif PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] = r; } return PLATFORM_STYLE_JS_PROPERTIES[lowerProperty]; } function CPBrowserCSSProperty(aProperty) { var browserProperty = CPBrowserStyleProperty(aProperty); if (!browserProperty) return nil; var prefixes = { 'Webkit': '-webkit-', 'Moz': '-moz-', 'O': '-o-', 'ms': '-ms-' }; for (var prefix in prefixes) { if (browserProperty.substring(0, prefix.length) == prefix) { var browserPropertyWithoutPrefix = browserProperty.substring(prefix.length), parts = browserPropertyWithoutPrefix.match(/[A-Z][a-z]+/g); // If there were any capitalized words in the browserProperty, insert a "-" between each one if (parts && parts.length > 0) browserPropertyWithoutPrefix = parts.join("-"); return prefixes[prefix] + browserPropertyWithoutPrefix.toLowerCase(); } } var parts = browserProperty.match(/[A-Z][a-z]+/g); if (parts && parts.length > 0) browserProperty = parts.join("-"); return browserProperty.toLowerCase(); }