您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
This makes the browser support emoji by using native fonts if possible and a fallback if not.
// ==UserScript== // @author James Edward Lewis II // @description This makes the browser support emoji by using native fonts if possible and a fallback if not. // @name Emoji Polyfill // @namespace greasyfork.org // @version 1.0.28 // @icon https://rawgit.com/lewisje/Chromoji/simple/icon16.png // @include * // @license MIT // @grant none // @run-at document-start // @copyright 2015 James Edward Lewis II // ==/UserScript== /*! * Shim for MutationObserver interface * Author: Graeme Yeates (github.com/megawac) * Repository: https://github.com/megawac/MutationObserver.js * License: WTFPL V2, 2004 (wtfpl.net). * Though credit and staring the repo will make me feel pretty, you can modify and redistribute as you please. * Attempts to follow spec (http:// www.w3.org/TR/dom/#mutation-observers) as closely as possible for native javascript * See https://github.com/WebKit/webkit/blob/master/Source/WebCore/dom/MutationObserver.cpp for current webkit source c++ implementation */ /** * prefix bugs: - https://bugs.webkit.org/show_bug.cgi?id=85161 - https://bugzilla.mozilla.org/show_bug.cgi?id=749920 * Don't use WebKitMutationObserver as Safari (6.0.5-6.1) use a buggy implementation */ window.MutationObserver = window.MutationObserver || window.MozMutationObserver || (function (window, undefined) { 'use strict'; var hasOwn = Object.hasOwnProperty; /** * @param {function(Array.<mutationRecord>, MutationObserver)} listener * @constructor */ function MutationObserver(listener) { /** * @type {Array.<Object>} * @private */ if (!(this instanceof MutationObserver)) return new MutationObserver(listener); this._watched = []; /** @private */ this._listener = listener; } /** * Start a recursive timeout function to check all items being observed for mutations * @type {MutationObserver} observer * @private */ function startMutationChecker(observer) { function check() { var mutations = observer.takeRecords(); if (mutations.length) // fire away // calling the listener with context is not spec but currently consistent with FF and WebKit observer._listener(mutations, observer); /** @private */ observer._timeout = setTimeout(check, MutationObserver._period); } check(); } /** * Period to check for mutations (~32 times/sec) * @type {number} * @expose */ MutationObserver._period = 30; /*ms+runtime*/ /** * Exposed API * @expose * @final */ MutationObserver.prototype = { /** * see http:// dom.spec.whatwg.org/#dom-mutationobserver-observe * not going to throw here but going to follow the current spec config sets * @param {Node|null} $target * @param {Object|null} config : MutationObserverInit configuration dictionary * @expose * @return undefined */ observe: function ($target, config) { /** * Using slightly different names so closure can go ham * @type {!Object} : A custom mutation config */ var settings = { attr: !!(config.attributes || config.attributeFilter || config.attributeOldValue), // some browsers are strict in their implementation that config.subtree and childList must be set together. We don't care - spec doesn't specify kids: !!config.childList, descendents: !!config.subtree, charData: !!(config.characterData || config.characterDataOldValue) }, watched = this._watched, i = 0; // remove already observed target element from pool for (; i < watched.length; i++) if (watched[i].tar === $target) watched.splice(i, 1); if (config.attributeFilter) /** * converts to a {key: true} dict for faster lookup * @type {Object.<String,Boolean>} */ settings.afilter = reduce(config.attributeFilter, function (a, b) { a[b] = true; return a; }, {}); watched.push({ tar: $target, fn: createMutationSearcher($target, settings) }); // reconnect if not connected if (!this._timeout) startMutationChecker(this); }, /** * Finds mutations since last check and empties the "record queue" i.e. mutations will only be found once * @expose * @return {Array.<mutationRecord>} */ takeRecords: function () { var mutations = [], watched = this._watched, wl = watched.length, i = 0; for (; i < wl; i++) watched[i].fn(mutations); return mutations; }, /** * @expose * @return undefined */ disconnect: function () { this._watched = []; // clear the stuff being observed clearTimeout(this._timeout); // ready for garbage collection /** @private */ this._timeout = null; } }; /** * @param {Object} obj * @param {(string|number)} prop * @return {boolean} */ function has(obj, prop) { return hasOwn.call(obj, prop) && typeof obj[prop] !== 'undefined'; // will be nicely inlined by gcc } /** * Simple mutationRecord pseudoclass. No longer exposing as its not fully compliant * @param {Object} data * @return {Object} a mutationRecord */ function mutationRecord(data) { var settings = { // technically these should be on proto so hasOwnProperty will return false for non explicitly set props type: null, target: null, addedNodes: [], removedNodes: [], previousSibling: null, nextSibling: null, attributeName: null, attributeNamespace: null, oldValue: null }, prop; for (prop in data) if (has(settings, prop) && has(data, prop)) settings[prop] = data[prop]; return settings; } /** * Creates a func to find all the mutations * * @param {Node} $target * @param {!Object} config : A custom mutation config */ function createMutationSearcher($target, config) { /** type {Elestuct} */ var $oldstate = clone($target, config); // create the cloned datastructure /** * consumes array of mutations we can push to * * @param {Array.<mutationRecord>} mutations */ return function (mutations) { var olen = mutations.length, dirty; // Alright we check base level changes in attributes... easy if (config.attr && $oldstate.attr) findAttributeMutations(mutations, $target, $oldstate.attr, config.afilter); // check childlist or subtree for mutations if (config.kids || config.descendents) dirty = searchSubtree(mutations, $target, $oldstate, config); // reclone data structure if theres changes if (dirty || mutations.length !== olen) /** type {Elestuct} */ $oldstate = clone($target, config); }; } /* attributes + attributeFilter helpers */ /** * fast helper to check to see if attributes object of an element has changed * doesnt handle the textnode case * * @param {Array.<mutationRecord>} mutations * @param {Node} $target * @param {Object.<string, string>} $oldstate : Custom attribute clone data structure from clone * @param {Object} filter */ function findAttributeMutations(mutations, $target, $oldstate, filter) { var checked = {}, attributes = $target.attributes, i = attributes.length, attr, name; while (i--) { attr = attributes[i]; name = attr.name; if (!filter || has(filter, name)) { if (attr.value !== $oldstate[name]) // The pushing is redundant but gzips very nicely mutations.push(mutationRecord({ type: 'attributes', target: $target, attributeName: name, oldValue: $oldstate[name], attributeNamespace: attr.namespaceURI // in ie<8 it incorrectly will return undefined })); checked[name] = true; } } for (name in $oldstate) if (!(checked[name]) && $oldstate.hasOwnProperty(name)) mutations.push(mutationRecord({target: $target, type: 'attributes', attributeName: name, oldValue: $oldstate[name]})); } /** * searchSubtree: array of mutations so far, element, element clone, bool * synchronous dfs comparision of two nodes * This function is applied to any observed element with childList or subtree specified * Sorry this is kind of confusing as shit, tried to comment it a bit... * codereview.stackexchange.com/questions/38351 discussion of an earlier version of this func * * @param {Array} mutations * @param {Node} $target * @param {!Object} $oldstate : A custom cloned node from clone() * @param {!Object} config : A custom mutation config */ function searchSubtree(mutations, $target, $oldstate, config) { // Track if the tree is dirty and has to be recomputed (#14). var dirty; /* * Helper to identify node rearrangment and stuff... * There is no gaurentee that the same node will be identified for both added and removed nodes * if the positions have been shuffled. * conflicts array will be emptied by end of operation */ function resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes) { // the distance between the first conflicting node and the last var distance = conflicts.length - 1, // prevents same conflict being resolved twice consider when two nodes switch places. // only one should be given a mutation event (note -~ is used as a math.ceil shorthand) counter = -~((distance - numAddedNodes) / 2), conflict = conflicts.pop(), $cur, oldstruct; while (conflict) { $cur = $kids[conflict.i]; oldstruct = $oldkids[conflict.j]; // attempt to determine if there was node rearrangement... won't gaurentee all matches // also handles case where added/removed nodes cause nodes to be identified as conflicts if (config.kids && counter && Math.abs(conflict.i - conflict.j) >= distance) { mutations.push(mutationRecord({ type: 'childList', target: node, addedNodes: [$cur], removedNodes: [$cur], // haha don't rely on this please nextSibling: $cur.nextSibling, previousSibling: $cur.previousSibling })); counter--; // found conflict } // Alright we found the resorted nodes now check for other types of mutations if (config.attr && oldstruct.attr) findAttributeMutations(mutations, $cur, oldstruct.attr, config.afilter); if (config.charData && $cur.nodeType === 3 && $cur.nodeValue !== oldstruct.charData) mutations.push(mutationRecord({type: 'characterData', target: $cur})); // now look @ subtree if (config.descendents) findMutations($cur, oldstruct); conflict = conflicts.pop(); } } /** * Main worker. Finds and adds mutations if there are any * @param {Node} node * @param {!Object} old : A cloned data structure using internal clone */ function findMutations(node, old) { var $kids = node.childNodes, $oldkids = old.kids, klen = $kids.length, olen = $oldkids ? $oldkids.length : 0; // $oldkids will be undefined for text and comment nodes // if (!olen && !klen) return; // both empty; clearly no changes // we delay the intialization of these for marginal performance in the expected case (actually quite signficant on large subtrees when these would be otherwise unused) // map of checked element of ids to prevent registering the same conflict twice var map, // array of potential conflicts (ie nodes that may have been re arranged) conflicts, id, // element id from getElementId helper idx, // index of a moved or inserted element oldstruct, $cur, $old, // current and old nodes numAddedNodes = 0, // track the number of added nodes so we can resolve conflicts more accurately i = 0, j = 0; // iterate over both old and current child nodes at the same time while (i < klen || j < olen) { // while there is still anything left in $kids or $oldkids (same as i < $kids.length || j < $oldkids.length;) $cur = $kids[i]; // current and old nodes at the indexes oldstruct = $oldkids[j]; $old = oldstruct && oldstruct.node; if ($cur === $old) { // expected case - optimized for this case // check attributes as specified by config if (config.attr && oldstruct.attr) /* oldstruct.attr instead of textnode check */ findAttributeMutations(mutations, $cur, oldstruct.attr, config.afilter); // check character data if node is a comment or textNode and it's being observed if (config.charData && oldstruct.charData !== undefined && $cur.nodeValue !== oldstruct.charData) mutations.push(mutationRecord({type: 'characterData', target: $cur})); // resolve conflicts; it will be undefined if there are no conflicts - otherwise an array if (conflicts) resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes); // recurse on next level of children. Avoids the recursive call when there are no children left to iterate if (config.descendents && ($cur.childNodes.length || oldstruct.kids && oldstruct.kids.length)) findMutations($cur, oldstruct); i++; j++; } else { // (uncommon case) lookahead until they are the same again or the end of children dirty = true; if (!map) { // delayed initalization (big perf benefit) map = {}; conflicts = []; } if ($cur) { // check id is in the location map otherwise do a indexOf search if (!(map[id = getElementId($cur)])) { // to prevent double checking // mark id as found map[id] = true; // custom indexOf using comparitor checking oldkids[i].node === $cur if ((idx = indexOfCustomNode($oldkids, $cur, j)) === -1) if (config.kids) { mutations.push(mutationRecord({ type: 'childList', target: node, addedNodes: [$cur], // $cur is a new node nextSibling: $cur.nextSibling, previousSibling: $cur.previousSibling })); numAddedNodes++; } else conflicts.push({i: i, j: idx}); // add conflict } i++; } if ($old && // special case: the changes may have been resolved: i and j appear congurent so we can continue using the expected case $old !== $kids[i] ) { if (!(map[id = getElementId($old)])) { map[id] = true; if ((idx = indexOf($kids, $old, i)) === -1) if (config.kids) { mutations.push(mutationRecord({ type: 'childList', target: old.node, removedNodes: [$old], nextSibling: $oldkids[j + 1], // praise no indexoutofbounds exception previousSibling: $oldkids[j - 1] })); numAddedNodes--; } else conflicts.push({i: idx, j: j}); } j++; } }// end uncommon case }// end loop // resolve any remaining conflicts if (conflicts) resolveConflicts(conflicts, node, $kids, $oldkids, numAddedNodes); } findMutations($target, $oldstate); return dirty; } /** * Utility * Clones a element into a custom data structure designed for comparision. https://gist.github.com/megawac/8201012 * * @param {Node} $target * @param {!Object} config : A custom mutation config * @return {!Object} : Cloned data structure */ function clone($target, config) { var recurse = true; // set true so childList we'll always check the first level function copy($target) { var elestruct = {/** @type {Node} */ node: $target}; // Store current character data of target text or comment node if the config requests // those properties to be observed. if (config.charData && ($target.nodeType === 3 || $target.nodeType === 8)) elestruct.charData = $target.nodeValue; // its either a element, comment, doc frag or document node else { // Add attr only if subtree is specified or top level and avoid if // attributes is a document object (#13). if (config.attr && recurse && $target.nodeType === 1) /** * clone live attribute list to an object structure {name: val} * @type {Object.<string, string>} */ elestruct.attr = reduce($target.attributes, function(memo, attr) { if (!config.afilter || config.afilter[attr.name]) memo[attr.name] = attr.value; return memo; }, {}); // whether we should iterate the children of $target node if (recurse && ((config.kids || config.charData) || (config.attr && config.descendents))) /** @type {Array.<!Object>} : Array of custom clone */ elestruct.kids = map($target.childNodes, copy); recurse = config.descendents; } return elestruct; } return copy($target); } // GCC hack see http://stackoverflow.com/a/23202438/1517919 function jsCompiler_renameProperty(a) { return a; } /** * **indexOf** find index of item in collection. * @param {Array|NodeList} set * @param {Object} item * @param {number} idx * @param {string} [prop] Property on set item to compare to item */ function indexOf(set, item, _idx, prop) { var sl = set.length, idx = ~~_idx; // start idx is always given as this is internal for (; idx < sl; idx++) if ((prop ? set[idx][prop] : set[idx]) === item) return idx; return -1; } /** * indexOf an element in a collection of custom nodes * * @param {NodeList} set * @param {!Object} $node : A custom cloned node * @param {number} idx : index to start the loop * @return {number} */ function indexOfCustomNode(set, $node, idx) { return indexOf(set, $node, idx, jsCompiler_renameProperty('node')); } // using a non id (eg outerHTML or nodeValue) is extremely naive and will run into issues with nodes that may appear the same like <li></li> var counter = 1, // don't use 0 as id (falsy) /** @const */ expando = 'mo_id'; /** * Attempt to uniquely id an element for hashing. We could optimize this for legacy browsers but it hopefully wont be called enough to be a concern * * @param {Node} $ele * @return {(string|number)} */ function getElementId($ele) { try {return $ele.id || ($ele[expando] = $ele[expando] || counter++);} catch (o_O) { // ie <8 will throw if you set an unknown property on a text node try {return $ele.nodeValue;} // naive catch (shitie) {return counter++;} // when text node is removed: https://gist.github.com/megawac/8355978 :( } } /** * **map** Apply a mapping function to each item of a set * @param {Array|NodeList} set * @param {Function} iterator */ function map(set, iterator) { var results = [], sl = set.length, index = 0; for (; index < sl; index++) results[index] = iterator(set[index], index, set); return results; } /** * **Reduce** builds up a single result from a list of values * @param {Array|NodeList|NamedNodeMap} set * @param {Function} iterator * @param {*} [memo] Initial value of the memo. */ function reduce(set, iterator, mem) { var sl = set.length, memo = mem, index = 0; for (; index < sl; index++) memo = iterator(memo, set[index], index, set); return memo; } return MutationObserver; })(window); // main code (function (Object, Function, Array, window, document, undefined) { 'use strict'; /* jshint elision: true */ var emo = { /*And: ['https://lewisje.github.io/fonts/emojiAnd.eot?#iefix', , 'https://lewisje.github.io/fonts/emojiAnd.woff', , 'https://lewisje.github.io/fonts/emojiAnd.ttf', 'https://lewisje.github.io/fonts/emojiAnd.svg#emojiAnd'], OSns: ['https://lewisje.github.io/fonts/emojiOSns.eot?#iefix', 'https://lewisje.github.io/fonts/emojiOSns.woff2', 'https://lewisje.github.io/fonts/emojiOSns.woff', 'https://lewisje.github.io/fonts/emojiOSns.otf', 'https://lewisje.github.io/fonts/emojiOSns.ttf', 'https://lewisje.github.io/fonts/emojiOSns.svg#emojiOSns'], Sym: ['https://lewisje.github.io/fonts/emojiSym.eot?#iefix', 'https://lewisje.github.io/fonts/emojiSym.woff2', 'https://lewisje.github.io/fonts/emojiSym.woff', , 'https://lewisje.github.io/fonts/emojiSym.ttf', 'https://lewisje.github.io/fonts/emojiSym.svg#emojiSym'],*/ Symb: ['https://lewisje.github.io/fonts/emojiSymb.eot?#iefix', 'https://lewisje.github.io/fonts/emojiSymb.woff2', 'https://lewisje.github.io/fonts/emojiSymb.woff', , 'https://lewisje.github.io/fonts/emojiSymb.ttf', 'https://lewisje.github.io/fonts/emojiSymb.svg#emojiSymb'] }, /* jshint elision: false */ fontEmoRegex = /\s*(?:(?:"|')?Segoe\sUI\s(?:Emoji|Symbol)(?:"|')?|Symbola|EmojiSymb),?/g, headingRegex = /^h[1-6]$/i, roughEmoRegex = /[^\s\w\x00-\x22\x24-\x29\x2B-\x2F\x3A-\u203B\u2050-\u2116\u3040-\u31FF\u3299-\uD83B\uD83F-\uDBFF\uE537-\uF8FE\uF900-\uFFFF]/, upperRegex = /[\uD840-\uD869]/g, lowerRegex = /[\uDC00-\uDFFF]/g, surrogate = false, constructorRegex = /\s*class /, textRegex = /^(?:i?frame|link|(?:no)?script|style|textarea|#text)$/i, typs = ['embedded-opentype', 'woff2', 'woff', 'opentype', 'truetype', 'svg'], css = ['/* Injected by Emoji Polyfill */'], style = document.createElement('style'), arrProto = Array.prototype, funcProto = Function.prototype, fnToString = funcProto.toString, _toString = Object.prototype.toString, hasSymbols = typeof Symbol === 'function' && typeof Symbol() === 'symbol', hasToStringTag = hasSymbols && typeof Symbol.toStringTag === 'symbol', head = document.head || document.getElementsByTagName('head')[0], MutationObserver = window.MutationObserver, emoProp = hasSymbols ? Symbol('$emoji$') : '$emoji$' + (10 * Math.random()) + '$', observer = {}, observerConfig, body, fontExtender, addHandler, removeHandler, setImmediate, bind, filter, getStyle, delStyle, fnt, emofnt, typ, r, NATIVE_MUTATION_EVENTS; for (fnt in emo) if (emo.hasOwnProperty(fnt)) { if (fnt === 'Sym') css.push('\n/* Emoji Symbols Font (C) Blockworks - Kenichi Kaneko http://emojisymbols.com/ */'); css.push('\n@font-face {\n font-family: "Emoji' + fnt + '";\n src: local("\u263A\uFE0E")'); emofnt = emo[fnt]; for (typ in emofnt) if (emofnt.hasOwnProperty(typ) && emofnt[typ]) css.push(',\n url("' + emofnt[typ] + '") format("' + typs[typ] + '")'); css.push(';\n}'); } css = css.join(''); style.type = 'text/css'; if (style.styleSheet) style.styleSheet.cssText = css; else style.appendChild(document.createTextNode(css)); head.appendChild(style); // part of a pair of functions intended to isolate code that kills the optimizing compiler // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers function functionize(func, arg) { /* jshint evil: true */ var typ = typeof func, thunk; if ('function' === typ || isCallable(func)) return func; if (func && 'string' === typ || ('object' === typ && '[object String]' === _toString.apply(func))) { trial(function () {thunk = arg ? new Function(String(arg), func) : new Function(func);}); // jshint evil: true } thunk = thunk || function thunk() {return func;}; return thunk; } // The first argument to the toCatch callback is the caught error; // if toCatch is passed as a string, this argument must be named "e" function trial(toTry, toCatch, toFinal) { var _try = functionize(toTry), _catch = functionize(toCatch, '_'), _final = functionize(toFinal); try {_try();} catch (_) {_catch(_);} finally {_final();} } function tryFunctionObject(value) { try {fnToString.apply(value); return true;} catch (_) {return false;} } // Reference: https://github.com/ljharb/is-callable/ function isCallable(fn) { var typ = typeof fn, callable = false, strClass; if (!fn) return false; if ('function' !== typ && 'object' !== typ && 'unknown' !== typ) return false; if (constructorRegex.test(fn)) return false; // 'unknown' means callable ActiveX in IE<9 if (hasToStringTag) return tryFunctionObject(fn); strClass = _toString.apply(fn); trial(function () {fn.apply(this, new Array(fn.length || 0)); callable = true;}); return '[object Function]' === strClass || '[object GeneratorFunction]' === strClass || callable; } function hasMethod(obj, key) { return key in obj && isCallable(obj[key]); } // Production steps of ECMA-262, Edition 5, 15.3.4.5 // Reference: https://es5.github.io/#x15.3.4.5 if (hasMethod(funcProto, 'bind')) bind = funcProto.call.bind(funcProto.bind); else bind = function bind(func, oThis) { var i = arguments.length - 2, aArgs = i < 1 ? [] : new Array(i), fToBind = functionize(func), FNOP, fBound; if (i >= 0) while (i--) aArgs[i] = arguments[i + 2]; FNOP = function FNOP() {}; if (fToBind.prototype) FNOP.prototype = fToBind.prototype; fBound = function fBound() { // jshint validthis: true var i = arguments.length, args = new Array(i); while (i--) args[i] = arguments[i]; return fToBind.apply(this instanceof FNOP && oThis ? this : oThis, aArgs.concat(args)); }; fBound.prototype = new FNOP(); return fBound; }; // Production steps of ECMA-262, Edition 5, 15.4.4.20 // Reference: https://es5.github.io/#x15.4.4.20 if (hasMethod(arrProto, 'filter')) filter = bind(funcProto.call, arrProto.filter); else filter = function filter(arr, callback, thisArg) { // jshint validthis: true if (!arr) return arr; if (!isCallable(callback)) throw new TypeError('callback must be a function'); var t = (typeof arr === 'string') ? arr.split('') : Object(arr), len = t.length >>> 0, res = [], i = 0, val; for (; i < len; i++) if (i in t) { val = t[i]; if (callback.call(thisArg, val, i, t)) res[res.length] = val; } return res; }; // via Douglas Crockford function walk(nod, fnc) { var func = functionize(fnc), node = nod; if (!func(node)) return; node = node.firstChild; while (node) { walk(node, func); node = node.nextSibling; } } /*! * contentloaded.js * * Author: Diego Perini (diego.perini at gmail.com) * Summary: cross-browser wrapper for DOMContentLoaded * Updated: 20101020 * License: MIT * Version: 1.2 * * URL: * http://javascript.nwbox.com/ContentLoaded/ * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE * */ // @win window reference // @fn function reference function contentLoaded(win, fn, cap) { var done = false, _top = true, doc = win.document, root = doc.documentElement, w3c = 'addEventListener' in doc, add = w3c ? 'addEventListener' : 'attachEvent', rem = w3c ? 'removeEventListener' : 'detachEvent', pre = w3c ? '' : 'on', capt = w3c && cap, startPoll; function init(e) { if (e.type === 'readystatechange' && doc.readyState !== 'complete') return; (e.type === 'load' ? win : doc)[rem](pre + e.type, init, capt); if (!done && (done = true)) fn.call(win, e.type || e); } function poll() { startPoll = true; trial(function () {root.doScroll('left');}, function () {setTimeout(poll, 50); startPoll = false;}); if (startPoll) init('poll'); } if (doc.readyState === 'complete') fn.call(win, 'lazy'); else { if (doc.createEventObject && root.doScroll) { trial(function () {_top = !win.frameElement;}); if (_top) poll(); } doc[add](pre + 'DOMContentLoaded', init, capt); doc[add](pre + 'readystatechange', init, capt); win[add](pre + 'load', init, capt); } return init; } // partly inspired by cb_addEventListener from Eduardo Cereto, partly by EventUtil from Nicolas C. Zakas // https://gist.github.com/eduardocereto/955642 // http://www.wrox.com/WileyCDA/WroxTitle/Professional-JavaScript-for-Web-Developers-3rd-Edition.productCd-1118026691,descCd-DOWNLOAD.html addHandler = function (obj, evt, fnc, cap) { if (hasMethod(window, 'addEventListener')) { addHandler = function addHandler(obj, evt, fnc, cap) { // W3C model var objt = Object(obj), evnt = String(evt), func = functionize(fnc), capt = !!cap; if (evnt === 'DOMContentLoaded') return contentLoaded(window, func, capt); objt.addEventListener(evnt, func, capt); return func; }; } else if (hasMethod(window, 'attachEvent')) { addHandler = function addHandler(obj, evt, fnc/*, cap*/) { // old Microsoft model var objt = Object(obj), evnt = String(evt), func = functionize(fnc), bound; if (evnt === 'DOMContentLoaded') return contentLoaded(window, func, false); bound = bind(func, objt, evnt); objt.attachEvent('on' + evnt, bound); return bound; }; } else { // Browser doesn't support W3C or MSFT model, go on with traditional addHandler = function addHandler(obj, evt, fnc/*, cap*/) { var objt = Object(obj), evnt = 'on' + String(evt), func = functionize(fnc); if (evnt === 'onDOMContentLoaded') { objt = window; evnt = 'onload'; } if (hasMethod(objt, evnt)) { // Object already has a function on traditional // Let's wrap it with our own function inside another function func = (function (f1, f2) { function wrapped() { // jshint validthis: true var args = [], len = arguments.length, i = 0; for (; i < len; i++) args.push(arguments[i]); f1.apply(this, args); f2.apply(this, args); } return wrapped; })(objt[evnt], func); } objt[evnt] = func; return func; }; } return addHandler(obj, evt, fnc, cap); }; removeHandler = function (obj, evt, fnc, cap) { if (hasMethod(window, 'removeEventListener')) { removeHandler = function removeHandler(obj, evt, fnc, cap) { // W3C model var objt = Object(obj), evnt = String(evt), func = functionize(fnc), capt = !!cap; if (evnt === 'DOMContentLoaded') { window.removeEventListener('load', func, capt); document.removeEventListener('readystatechange', func, capt); } objt.removeEventListener(evnt, func, capt); return func; }; } else if (hasMethod(window, 'detachEvent')) { removeHandler = function removeHandler(obj, evt, fnc/*, cap*/) { // old Microsoft model var objt = Object(obj), evnt = 'on' + String(evt), func = functionize(fnc); if (evnt === 'onDOMContentLoaded') { window.detachEvent('onload', func); document.detachEvent('onreadystatechange', func); } objt.detachEvent(evnt, func); return func; }; } else { removeHandler = function removeHandler(obj, evt/*, fnc, cap*/) { // traditional var objt = Object(obj), evnt = 'on' + String(evt), func; if (evnt === 'onDOMContentLoaded') { objt = window; evnt = 'onload'; } if (hasMethod(objt, evnt)) { func = objt[evnt]; objt[evnt] = null; } return func; }; } return removeHandler(obj, evt, fnc, cap); }; // https://github.com/lewisje/setImmediate-shim-demo/blob/gh-pages/setImmediate.js setImmediate = (function (Object, Array, global, undefined) { var noNative, doc, slice, toString, timer, polyfill; // See http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/ function notUseNative() { return global.navigator && /Trident/.test(global.navigator.userAgent); } noNative = notUseNative(); if (!noNative && (global.msSetImmediate || global.setImmediate)) { if (!global.setImmediate) { global.setImmediate = global.msSetImmediate; global.clearImmediate = global.msClearImmediate; } return global.setImmediate; } doc = global.document; slice = Array.prototype.slice; toString = Object.prototype.toString; timer = {polyfill: {}, nextId: 1, tasks: {}, lock: false}; timer.run = function (handleId) { var task; if (timer.lock) global.setTimeout(timer.wrap(timer.run, handleId), 1); else { task = timer.tasks[handleId]; if (task) { timer.lock = true; trial(task, null, function () { timer.clear(handleId); timer.lock = false; }); } } }; timer.wrap = function (handler) { var i = arguments.length - 1, args = i < 0 ? [] : new Array(i), func = typeof handler === 'function' ? handler : functionize(String(handler)); if (i >= 0) while (i--) args[i] = arguments[i + 1]; function wrapped() { return func.apply(null, args); } return wrapped; }; timer.create = function (args) { timer.tasks[timer.nextId] = timer.wrap.apply(null, args); return timer.nextId++; }; timer.clear = function (handleId) { delete timer.tasks[handleId]; }; timer.polyfill.messageChannel = function () { var channel = new global.MessageChannel(); channel.port1.onmessage = function (event) { timer.run(+event.data); }; function setImmediate() { var i = arguments.length, args = new Array(i), handleId; while (i--) args[i] = arguments[i]; handleId = timer.create(args); channel.port2.postMessage(handleId); return handleId; } return setImmediate; }; timer.polyfill.nextTick = function () { function setImmediate() { var i = arguments.length, args = new Array(i), handleId; while (i--) args[i] = arguments[i]; handleId = timer.create(args); global.process.nextTick(timer.wrap(timer.run, handleId)); return handleId; } return setImmediate; }; timer.polyfill.postMessage = function () { var messagePrefix = 'setImmediate$' + Math.random() + '$'; function onGlobalMessage(event) { if (event.source === global && typeof event.data === 'string' && event.data.indexOf(messagePrefix) === 0) timer.run(+event.data.slice(messagePrefix.length)); } function setImmediate() { var i = arguments.length, args = new Array(i), handleId; while (i--) args[i] = arguments[i]; handleId = timer.create(args); global.postMessage(messagePrefix + handleId, '*'); return handleId; } if (global.addEventListener) global.addEventListener('message', onGlobalMessage, false); else global.attachEvent('onmessage', onGlobalMessage); return setImmediate; }; timer.polyfill.readyStateChange = function readyStateChange() { var html = doc.documentElement; function setImmediate() { var i = arguments.length, args = new Array(i), handleId, script = doc.createElement('script'); while (i--) args[i] = arguments[i]; handleId = timer.create(args); script.onreadystatechange = function () { timer.run(handleId); script.onreadystatechange = null; html.removeChild(script); script = null; }; html.appendChild(script); return handleId; } return setImmediate; }; timer.polyfill.setTimeout = function () { function setImmediate() { var i = arguments.length, args = new Array(i), handleId; while (i--) args[i] = arguments[i]; handleId = timer.create(args); global.setTimeout(timer.wrap(timer.run, handleId), 1); return handleId; } return setImmediate; }; function canUsePostMessage() { if (global.postMessage && !global.importScripts) { var asynch = true, oldOnMessage = global.onmessage; global.onmessage = function () { asynch = false; }; global.postMessage('', '*'); global.onmessage = oldOnMessage; return asynch; } } // Don't get fooled by e.g. browserify environments. // For Node.js before 0.9 if (toString.call(global.process) === '[object process]') polyfill = 'nextTick'; // For non-IE10 modern browsers else if (canUsePostMessage()) polyfill = 'postMessage'; // For web workers, where supported else if (!noNative && global.MessageChannel) polyfill = 'messageChannel'; // For IE 6-8 else if (doc && ('onreadystatechange' in doc.createElement('script'))) polyfill = 'readyStateChange'; // For older browsers else polyfill = 'setTimeout'; // If supported, we should attach to the prototype of global, // since that is where setTimeout et al. live. var attachTo = Object.getPrototypeOf && Object.getPrototypeOf(global); attachTo = attachTo && hasMethod(attachTo, 'setTimeout') ? attachTo : global; attachTo.setImmediate = timer.polyfill[polyfill](); attachTo.setImmediate.usepolyfill = polyfill; attachTo.msSetImmediate = attachTo.setImmediate; attachTo.clearImmediate = attachTo.msClearImmediate = timer.clear; return attachTo.setImmediate; })(Object, Array, window); function isEdit(el) { var n = el.nodeName.toLowerCase(); return (n === 'input' && el.type === 'text') || (n === 'textarea') || el.isContentEditable; } function astralTest(str) { if (surrogate) { surrogate = upperRegex.test(str); return !surrogate && !lowerRegex.test(str); } surrogate = upperRegex.test(str); return !surrogate; } function hasText(el) { var nodes = el.childNodes, n = nodes.length, nam = el.nodeName.toLowerCase(), node, val; if (n && nam !== 'select' && nam !== 'noframes') while (n--) { node = nodes[n]; val = (node.nodeType === 3) ? node.nodeValue.match(roughEmoRegex) : null; if (val && val.length && filter(val, astralTest).join('')) return true; } return false; } getStyle = function (el, cssprop) { if (document.defaultView && hasMethod(document.defaultView, 'getComputedStyle')) getStyle = function getStyle(el, cssprop) {return document.defaultView.getComputedStyle(el, '')[cssprop];}; // W3C else if ('currentStyle' in el) getStyle = function getStyle(el, cssprop) {return el.currentStyle[cssprop];}; // IE8 and earlier else getStyle = function getStyle(el, cssprop) {return el.style[cssprop];}; // try to get inline style return getStyle(el, cssprop); }; delStyle = function (el, cssprop) { var es = el.style; if (hasMethod(es, 'removeProperty')) delStyle = function delStyle(el, cssprop) {return el.style.removeProperty(cssprop);}; else if (hasMethod(es, 'removeAttribute')) delStyle = function delStyle(el, cssprop) {return el.style.removeAttribute(cssprop);}; else delStyle = function delStyle(el, cssprop) {el.style[cssprop] = ''; return null;}; return delStyle(el, cssprop); }; function fontExtend(el) { var font = getStyle(el, 'fontFamily').replace(fontEmoRegex, '') || 'monospace', newfont = 'font-family: ' + font + ", 'Segoe UI Emoji', 'Segoe UI Symbol', Symbola, EmojiSymb !important;"; el[emoProp] = true; delStyle(el, 'fontFamily'); if (headingRegex.test(el.nodeName)) { el.innerHTML = ['<span style="', newfont, '">', el.innerHTML, '</span>'].join(''); el.firstChild[emoProp] = true; } else el.style.cssText += '; ' + newfont; } function fontExtendEdit(e) { var evt = e || window.event, el = evt.target || evt.srcElement; if (!el[emoProp] && isEdit(el)) fontExtend(el); } function fontExtendLoad(el) { if (!el) return false; if (!textRegex.test(el.nodeName) && !isEdit(el)) { if (!el[emoProp] && hasText(el)) setImmediate(bind(fontExtend, null, el)); return true; } return false; } function fontExtendNode(e) { var evt = e || window.event; walk(evt.target || evt.srcElement, fontExtendLoad); } function init(/*e*/) { removeHandler(document, 'readystatechange', init, false); removeHandler(document, 'DOMContentLoaded', init, false); removeHandler(window, 'load', init, false); body = document.body || document.getElementsByTagName('body')[0] || document.getElementsByTagName('frameset')[0]; fontExtender = bind(fontExtendNode, null, {target: body}); fontExtender(); observer.start(); } addHandler(document, 'focus', fontExtendEdit, true); NATIVE_MUTATION_EVENTS = (function () { var f = false, root = document.documentElement, l = root.id; function e() { removeHandler(root, 'DOMAttrModified', e, false); NATIVE_MUTATION_EVENTS = true; root.id = l; } addHandler(root, 'DOMAttrModified', e, false); root.id = 'nw'; // now modify a property f = (root.id !== 'nw'); root.id = l; return f; })(); var mutate = function (e) { if (typeof e.length === 'number') mutate = function mutate(mutations) { var i = mutations.length, nodes, j; while (i--) { nodes = mutations[i].addedNodes; j = nodes.length; while (j--) walk(nodes[j], fontExtendLoad); } }; else if (typeof e.target !== 'undefined') mutate = function mutate(e) {walk(e.target, fontExtendLoad);}; else if (typeof e.srcElement !== 'undefined') mutate = function mutate(e) {walk(e.srcElement, fontExtendLoad);}; else mutate = fontExtender; return mutate(e); }; function onMutation(mutations) { observer.stop(); mutate(mutations); observer.start(); } if (MutationObserver) { observer = new MutationObserver(onMutation); observerConfig = { attributes: false, characterData: false, childList: true, subtree: true }; observer.start = function () { observer.start = bind(observer.observe, observer, body, observerConfig); observer.start(); }; observer.stop = bind(observer.disconnect, observer); } else if (NATIVE_MUTATION_EVENTS) { observer.start = function () { //addHandler(body, 'DOMAttrModified', onMutation, false); //addHandler(body, 'DOMCharacterDataModified', onMutation, false); addHandler(body, 'DOMNodeInserted', onMutation, false); addHandler(body, 'DOMSubtreeModified', onMutation, false); }; observer.stop = function () { //removeHandler(body, 'DOMAttrModified', onMutation, false); //removeHandler(body, 'DOMCharacterDataModified', onMutation, false); removeHandler(body, 'DOMNodeInserted', onMutation, false); removeHandler(body, 'DOMSubtreeModified', onMutation, false); }; } else { observer.start = function () { observer.start = bind(addHandler, null, body, 'propertychange', onMutation, false); observer.start(); }; observer.stop = function () { observer.stop = bind(removeHandler, null, body, 'propertychange', onMutation, false); observer.stop(); }; } r = document.readyState; if (r === 'complete' || r === 'loaded' || r === 'interactive') init(); else addHandler(document, 'DOMContentLoaded', init, false); })(Object, Function, Array, window, document);