Файл: library/wysihtml5/src/browser.js
Строк: 626
<?php
/**
* 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;
}
};
})();
?>