forked from tiff/wysihtml5
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbrowser.js
359 lines (317 loc) · 12.8 KB
/
browser.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
/**
* Detect browser support for specific features
*/
wysihtml5.browser = (function() {
var userAgent = navigator.userAgent,
testElement = document.createElement("div"),
// Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
isIE = userAgent.indexOf("MSIE") !== -1 && userAgent.indexOf("Opera") === -1,
isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1,
isWebKit = userAgent.indexOf("AppleWebKit/") !== -1,
isChrome = userAgent.indexOf("Chrome/") !== -1,
isOpera = userAgent.indexOf("Opera/") !== -1;
function iosVersion(userAgent) {
return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [, 0])[1];
}
function androidVersion(userAgent) {
return +(userAgent.match(/android (\d+)/) || [, 0])[1];
}
return {
// Static variable needed, publicly accessible, to be able override it in unit tests
USER_AGENT: userAgent,
/**
* Exclude browsers that are not capable of displaying and handling
* contentEditable as desired:
* - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
* - IE < 8 create invalid markup and crash randomly from time to time
*
* @return {Boolean}
*/
supported: function() {
var userAgent = this.USER_AGENT.toLowerCase(),
// Essential for making html elements editable
hasContentEditableSupport = "contentEditable" in testElement,
// Following methods are needed in order to interact with the contentEditable area
hasEditingApiSupport = document.execCommand && document.queryCommandSupported && document.queryCommandState,
// document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
hasQuerySelectorSupport = document.querySelector && document.querySelectorAll,
// contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
return hasContentEditableSupport
&& hasEditingApiSupport
&& hasQuerySelectorSupport
&& !isIncompatibleMobileBrowser;
},
isTouchDevice: function() {
return this.supportsEvent("touchmove");
},
isIos: function() {
return (/ipad|iphone|ipod/i).test(this.USER_AGENT);
},
isAndroid: function() {
return this.USER_AGENT.indexOf("Android") !== -1;
},
/**
* Whether the browser supports sandboxed iframes
* Currently only IE 6+ offers such feature <iframe security="restricted">
*
* http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx
* http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx
*
* HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage)
*/
supportsSandboxedIframes: function() {
return isIE;
},
/**
* IE6+7 throw a mixed content warning when the src of an iframe
* is empty/unset or about:blank
* window.querySelector is implemented as of IE8
*/
throwsMixedContentWarningWhenIframeSrcIsEmpty: function() {
return !("querySelector" in document);
},
/**
* Whether the caret is correctly displayed in contentEditable elements
* Firefox sometimes shows a huge caret in the beginning after focusing
*/
displaysCaretInEmptyContentEditableCorrectly: function() {
return isIE;
},
/**
* Opera and IE are the only browsers who offer the css value
* in the original unit, thx to the currentStyle object
* All other browsers provide the computed style in px via window.getComputedStyle
*/
hasCurrentStyleProperty: function() {
return "currentStyle" in testElement;
},
/**
* Firefox on OSX navigates through history when hitting CMD + Arrow right/left
*/
hasHistoryIssue: function() {
return isGecko && navigator.platform.substr(0, 3) === "Mac";
},
/**
* Whether the browser inserts a <br> when pressing enter in a contentEditable element
*/
insertsLineBreaksOnReturn: function() {
return isGecko;
},
supportsPlaceholderAttributeOn: function(element) {
return "placeholder" in element;
},
supportsEvent: function(eventName) {
return "on" + eventName in testElement || (function() {
testElement.setAttribute("on" + eventName, "return;");
return typeof(testElement["on" + eventName]) === "function";
})();
},
/**
* Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe
*/
supportsEventsInIframeCorrectly: function() {
return !isOpera;
},
/**
* Everything below IE9 doesn't know how to treat HTML5 tags
*
* @param {Object} context The document object on which to check HTML5 support
*
* @example
* wysihtml5.browser.supportsHTML5Tags(document);
*/
supportsHTML5Tags: function(context) {
var element = context.createElement("div"),
html5 = "<article>foo</article>";
element.innerHTML = html5;
return element.innerHTML.toLowerCase() === html5;
},
/**
* Checks whether a document supports a certain queryCommand
* In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree
* in oder to report correct results
*
* @param {Object} doc Document object on which to check for a query command
* @param {String} command The query command to check for
* @return {Boolean}
*
* @example
* wysihtml5.browser.supportsCommand(document, "bold");
*/
supportsCommand: (function() {
// Following commands are supported but contain bugs in some browsers
var buggyCommands = {
// formatBlock fails with some tags (eg. <blockquote>)
"formatBlock": isIE,
// When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
// converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
// IE and Opera act a bit different here as they convert the entire content of the current block element into a list
"insertUnorderedList": isIE || isWebKit,
"insertOrderedList": isIE || isWebKit
};
// Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
var supported = {
"insertHTML": isGecko
};
return function(doc, command) {
var isBuggy = buggyCommands[command];
if (!isBuggy) {
// Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
try {
return doc.queryCommandSupported(command);
} catch(e1) {}
try {
return doc.queryCommandEnabled(command);
} catch(e2) {
return !!supported[command];
}
}
return false;
};
})(),
/**
* IE: URLs starting with:
* www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://,
* nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url:
* will automatically be auto-linked when either the user inserts them via copy&paste or presses the
* space bar when the caret is directly after such an url.
* This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll
* (related blog post on msdn
* http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx).
*/
doesAutoLinkingInContentEditable: function() {
return isIE;
},
/**
* As stated above, IE auto links urls typed into contentEditable elements
* Since IE9 it's possible to prevent this behavior
*/
canDisableAutoLinking: function() {
return this.supportsCommand(document, "AutoUrlDetect");
},
/**
* IE leaves an empty paragraph in the contentEditable element after clearing it
* Chrome/Safari sometimes an empty <div>
*/
clearsContentEditableCorrectly: function() {
return isGecko || isOpera || isWebKit;
},
/**
* IE gives wrong results for getAttribute
*/
supportsGetAttributeCorrectly: function() {
var td = document.createElement("td");
return td.getAttribute("rowspan") != "1";
},
/**
* When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them.
* Chrome and Safari both don't support this
*/
canSelectImagesInContentEditable: function() {
return isGecko || isIE || isOpera;
},
/**
* All browsers except Safari and Chrome automatically scroll the range/caret position into view
*/
autoScrollsToCaret: function() {
return !isWebKit;
},
/**
* Check whether the browser automatically closes tags that don't need to be opened
*/
autoClosesUnclosedTags: function() {
var clonedTestElement = testElement.cloneNode(false),
returnValue,
innerHTML;
clonedTestElement.innerHTML = "<p><div></div>";
innerHTML = clonedTestElement.innerHTML.toLowerCase();
returnValue = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
// Cache result by overwriting current function
this.autoClosesUnclosedTags = function() { return returnValue; };
return returnValue;
},
/**
* Whether the browser supports the native document.getElementsByClassName which returns live NodeLists
*/
supportsNativeGetElementsByClassName: function() {
return String(document.getElementsByClassName).indexOf("[native code]") !== -1;
},
/**
* As of now (19.04.2011) only supported by Firefox 4 and Chrome
* See https://developer.mozilla.org/en/DOM/Selection/modify
*/
supportsSelectionModify: function() {
return "getSelection" in window && "modify" in window.getSelection();
},
/**
* Opera needs a white space after a <br> in order to position the caret correctly
*/
needsSpaceAfterLineBreak: function() {
return isOpera;
},
/**
* Whether the browser supports the speech api on the given element
* See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
*
* @example
* var input = document.createElement("input");
* if (wysihtml5.browser.supportsSpeechApiOn(input)) {
* // ...
* }
*/
supportsSpeechApiOn: function(input) {
var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [, 0];
return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input);
},
/**
* IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest
* See https://connect.microsoft.com/ie/feedback/details/650112
* or try the POC http://tifftiff.de/ie9_crash/
*/
crashesWhenDefineProperty: function(property) {
return isIE && (property === "XMLHttpRequest" || property === "XDomainRequest");
},
/**
* IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
*/
doesAsyncFocus: function() {
return isIE;
},
/**
* In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document
*/
hasProblemsSettingCaretAfterImg: function() {
return isIE;
},
hasUndoInContextMenu: function() {
return isGecko || isChrome || isOpera;
},
/**
* Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
* is used (regardless if rangy or native)
* This especially happens when the caret is positioned right after a <br> because then
* insertNode() will insert the node right before the <br>
*/
hasInsertNodeIssue: function() {
return isOpera;
},
/**
* IE 8+9 don't fire the focus event of the <body> when the iframe gets focused (even though the caret gets set into the <body>)
*/
hasIframeFocusIssue: function() {
return isIE;
},
/**
* Chrome + Safari create invalid nested markup after paste
*
* <p>
* foo
* <p>bar</p> <!-- BOO! -->
* </p>
*/
createsNestedInvalidMarkupAfterPaste: function() {
return isWebKit;
}
};
})();