API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPCompatibility.j
Go to the documentation of this file.
1 /*
2  * CPCompatibility.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 // Browser Engines
32 
33 // Operating Systems
37 
38 // Features
40 
44 
47 // 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.
49 // window.clipboardData exists and can be read and written to at any time using window.clipboardData.getData/setData.
53 
55 
58 
61 
63 
64 // Internet explorer does not allow dynamically changing the type of an input element
67 
69 
70 // element.style.font can be set for an element not in the DOM.
72 
73 // Input elements have 1 px of extra padding on the left regardless of padding setting.
76 
78 
79 
80 
81 /*
82  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
83 */
85 
86 // The paste event is only sent if an input or textarea has focus.
88 // Redirecting the focus of the browser on keydown to an input for Cmd-V or Ctrl-V makes the paste fail.
90 
91 
92 var USER_AGENT = "",
97 
98 // default these features to true
101 
102 if (typeof window !== "undefined" && typeof window.navigator !== "undefined")
103  USER_AGENT = window.navigator.userAgent;
104 
105 // Opera
106 if (typeof window !== "undefined" && window.opera)
107 {
109 
111 }
112 
113 // Internet Explorer
114 else if (typeof window !== "undefined" && window.attachEvent) // Must follow Opera check.
115 {
117 
118  // Features we can only be sure of with IE (no known independent tests)
122 
124 
126 
127  // Tested in Internet Explore 8 and 9.
129 
130  // IE allows free clipboard access.
132 }
133 
134 // WebKit
135 else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
136 {
138 
139  // Features we can only be sure of with WebKit (no known independent tests)
142 
146 
147  var versionStart = USER_AGENT.indexOf("AppleWebKit/") + "AppleWebKit/".length,
148  versionEnd = USER_AGENT.indexOf(" ", versionStart),
149  versionString = USER_AGENT.substring(versionStart, versionEnd),
150  versionDivision = versionString.indexOf('.'),
151  majorVersion = parseInt(versionString.substring(0, versionDivision)),
152  minorVersion = parseInt(versionString.substr(versionDivision + 1));
153 
154  if ((USER_AGENT.indexOf("Safari") !== CPNotFound && (majorVersion > 525 || (majorVersion === 525 && minorVersion > 14))) || USER_AGENT.indexOf("Chrome") !== CPNotFound)
156 
157  // FIXME this is a terrible hack to get around this bug:
158  // https://bugs.webkit.org/show_bug.cgi?id=21548
159  if (![CPPlatform isBrowser])
161 
162  if (majorVersion < 532 || (majorVersion === 532 && minorVersion < 6))
164 
165  // This is supposedly fixed in webkit r123603. Seems to work in Chrome 21 but not Safari 6.0.
166  if (majorVersion < 537)
168 
169  if (USER_AGENT.indexOf("Chrome") === CPNotFound)
170  {
173  // https://bugs.webkit.org/show_bug.cgi?id=75891
175  // https://bugs.webkit.org/show_bug.cgi?id=39689
177  }
178 
179  // Assume this bug was introduced around Safari 5.1/Chrome 16. This could probably be tighter.
180  if (majorVersion > 533)
182 }
183 
184 // KHTML
185 else if (USER_AGENT.indexOf("KHTML") != -1) // Must follow WebKit check.
186 {
188 }
189 
190 // Gecko
191 else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
192 {
194 
196 
197  var index = USER_AGENT.indexOf("Firefox"),
198  version = (index === -1) ? 2.0 : parseFloat(USER_AGENT.substring(index + "Firefox".length + 1));
199 
200  if (version >= 3.0)
202 
203  if (version < 3.0)
205 
206  // Some day this might be fixed and should be version prefixed. No known fixed version yet.
208 
209  // This was supposed to be added in Firefox 22, but when testing with the latest beta as of 2013-06-14
210  // it does not seem to work. It seems to exhibit the CPJavaScriptPasteRequiresEditableTarget problem,
211  // and in addition doesn't seem to work with our native copy code either.
212  /*if (version >= 22.0)
213  {
214  PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES;
215  // TODO File a bug at https://bugzilla.mozilla.org/. In other browsers, one can return "false" from the
216  // beforepaste event to indicate a paste should be enabled even that the DOMEvent.target is not editable.
217  PLATFORM_BUGS |= CPJavaScriptPasteRequiresEditableTarget;
218  }*/
219 }
220 
221 // Feature-specific checks
222 if (typeof document != "undefined")
223 {
224  var canvasElement = document.createElement("canvas");
225 
226  // Detect canvas support
227  if (canvasElement && canvasElement.getContext)
228  {
230 
231  // Any browser that supports canvas supports CSS opacity
233 
234  // Detect canvas setTransform/transform support
235  var context = document.createElement("canvas").getContext("2d");
236 
237  if (context && context.setTransform && context.transform)
239  }
240 
241  var DOMElement = document.createElement("div");
242 
243  // Detect whether we have innerText or textContent (or neither)
244  if (DOMElement.innerText != undefined)
246  else if (DOMElement.textContent != undefined)
248 
249  var DOMInputElement = document.createElement("input");
250 
251  if ("oninput" in DOMInputElement)
253  else if (typeof DOMInputElement.setAttribute === "function")
254  {
255  DOMInputElement.setAttribute("oninput", "return;");
256 
257  if (typeof DOMInputElement.oninput === "function")
259  }
260 
261  // Detect FileAPI support
262  if (typeof DOMInputElement.setAttribute === "function")
263  {
264  DOMInputElement.setAttribute("type", "file");
265  PLATFORM_FEATURES[CPFileAPIFeature] = !!DOMInputElement["files"];
266  }
267  else
269 }
270 
271 function CPFeatureIsCompatible(aFeature)
272 {
273  return !!PLATFORM_FEATURES[aFeature];
274 }
275 
276 function CPPlatformHasBug(aBug)
277 {
278  return PLATFORM_BUGS & aBug;
279 }
280 
281 function CPBrowserIsEngine(anEngine)
282 {
283  return PLATFORM_ENGINE === anEngine;
284 }
285 
286 function CPBrowserIsOperatingSystem(anOperatingSystem)
287 {
288  return OPERATING_SYSTEM === anOperatingSystem;
289 }
290 
292 
293 if (USER_AGENT.indexOf("Mac") !== -1)
294 {
296 
298 
299  CPUndoKeyEquivalent = @"z";
300  CPRedoKeyEquivalent = @"Z";
301 
304 }
305 else
306 {
307  if (USER_AGENT.indexOf("Windows") !== -1)
309 
311 
314 
317 }
318 
328 function CPBrowserStyleProperty(aProperty)
329 {
330  var lowerProperty = aProperty.toLowerCase();
331 
332  if (PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] === undefined)
333  {
334  var r = nil;
335 
336 #if PLATFORM(DOM)
337  var testElement = document.createElement('div');
338 
339  switch (lowerProperty)
340  {
341  case 'transitionend':
342  var candidates = {
343  'WebkitTransition' : 'webkitTransitionEnd',
344  'MozTransition' : 'transitionend',
345  'OTransition' : 'oTransitionEnd',
346  'msTransition' : 'MSTransitionEnd',
347  'transition' : 'transitionend'
348  };
349 
350  r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transition']] || nil;
351  break;
352 
353  case 'transformorigin':
354 
355  var candidates = {
356  'WebkitTransform' : 'WebkitTransformOrigin',
357  'MozTransform' : 'MozTransformOrigin',
358  'OTransform' : 'OTransformOrigin',
359  'msTransform' : 'MSTransformOrigin',
360  'transform' : 'transformOrigin'
361  };
362 
363  r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transform']] || nil;
364  break;
365 
366  default:
367  var prefixes = ["Webkit", "Moz", "O", "ms"],
368  strippedProperty = aProperty.split('-').join(' '),
369  capProperty = [strippedProperty capitalizedString].split(' ').join('');
370 
371  for (var i = 0; i < prefixes.length; i++)
372  {
373  // First check if the property is already valid without being formatted, otherwise try the capitalized property
374  if (prefixes[i] + aProperty in testElement.style)
375  {
376  r = prefixes[i] + aProperty;
377  break;
378  }
379  else if (prefixes[i] + capProperty in testElement.style)
380  {
381  r = prefixes[i] + capProperty;
382  break;
383  }
384  }
385 
386  if (!r && lowerProperty in testElement.style)
387  r = lowerProperty;
388 
389  break;
390  }
391 #endif
392 
393  PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] = r;
394  }
395 
396  return PLATFORM_STYLE_JS_PROPERTIES[lowerProperty];
397 }
398 
399 function CPBrowserCSSProperty(aProperty)
400 {
401  var browserProperty = CPBrowserStyleProperty(aProperty);
402 
403  if (!browserProperty)
404  return nil;
405 
406  var prefixes = {
407  'Webkit': '-webkit-',
408  'Moz': '-moz-',
409  'O': '-o-',
410  'ms': '-ms-'
411  };
412 
413  for (var prefix in prefixes)
414  {
415  if (browserProperty.substring(0, prefix.length) == prefix)
416  {
417  var browserPropertyWithoutPrefix = browserProperty.substring(prefix.length),
418  parts = browserPropertyWithoutPrefix.match(/[A-Z][a-z]+/g);
419 
420  // If there were any capitalized words in the browserProperty, insert a "-" between each one
421  if (parts && parts.length > 0)
422  browserPropertyWithoutPrefix = parts.join("-");
423 
424  return prefixes[prefix] + browserPropertyWithoutPrefix.toLowerCase();
425  }
426  }
427 
428  var parts = browserProperty.match(/[A-Z][a-z]+/g);
429 
430  if (parts && parts.length > 0)
431  browserProperty = parts.join("-");
432 
433  return browserProperty.toLowerCase();
434 }