/*! CSS-REGIONS-POLYFILL - v3.0.0 - 2015-12-13 - https://github.com/FremyCompany/css-regions-polyfill - Copyright (c) 2015 François REMY; MIT-Licensed !*/ !(function() { 'use strict'; var module = { exports:{} }; var require = (function() { var modules = {}; var require = function(m) { return modules[m]; }; require.define = function(m) { modules[m]=module.exports; module.exports={}; }; return require; })(); //////////////////////////////////////// !(function(window, document) { "use strict"; // // some code for console polyfilling // if(!window.console) { window.console = { backlog: '', log: function(x) { this.backlog+=x+'\n'; if(window.debug) alert(x); }, dir: function(x) { try { var elm = function(e) { if(e.innerHTML) { return { tagName: e.tagName, className: e.className, id: e.id, innerHTML: e.innerHTML.substr(0,100) } } else { return { nodeName: e.nodeName, nodeValue: e.nodeValue } } }; var jsonify = function(o) { var seen=[]; var jso=JSON.stringify(o, function(k,v){ if (typeof v =='object') { if ( !seen.indexOf(v) ) { return '__cycle__'; } if ( v instanceof window.Node) { return elm(v); } seen.push(v); } return v; }); return jso; }; this.log(jsonify(x)); } catch(ex) { this.log(x) } }, warn: function(x) { this.log(x) }, error: function(x) { this.log("ERROR:"); this.log(x); } }; if(!window.onerror) { window.onerror = function() { console.log([].slice.call(arguments,0).join("\n")) }; } } // // this special console is used as a proxy emulating the CSS console of browsers // window.cssConsole = { enabled: (!!window.debug), warnEnabled: (true), log: function(x) { if(this.enabled) console.log(x) }, dir: function(x) { if(this.enabled) console.dir(x) }, warn: function(x) { if(this.warnEnabled) console.warn(x) }, error: function(x) { console.error(x); } } })(window, document); require.define('src/core/polyfill-dom-console.js'); //////////////////////////////////////// module.exports = (function(window, document) { "use strict"; require('src/core/polyfill-dom-console.js'); // // some other basic om code // var domEvents = { // // the following functions are about event cloning // cloneMouseEvent: function cloneMouseEvent(e) { var evt = document.createEvent("MouseEvent"); evt.initMouseEvent( e.type, e.canBubble||e.bubbles, e.cancelable, e.view, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget ); return evt; }, cloneKeyboardEvent: function cloneKeyboardEvent(e) { // TODO: this doesn't work cross-browser... // see https://gist.github.com/termi/4654819/ for the huge code return domEvents.cloneCustomEvent(e); }, cloneCustomEvent: function cloneCustomEvent(e) { var ne = document.createEvent("CustomEvent"); ne.initCustomEvent(e.type, e.canBubble||e.bubbles, e.cancelable, "detail" in e ? e.detail : e); for(var prop in e) { try { if(e[prop] != ne[prop] && e[prop] != e.target) { try { ne[prop] = e[prop]; } catch (ex) { Object.defineProperty(ne,prop,{get:function() { return e[prop]} }) } } } catch(ex) {} } return ne; }, cloneEvent: function cloneEvent(e) { if(e instanceof MouseEvent) { return domEvents.cloneMouseEvent(e); } else if(e instanceof KeyboardEvent) { return domEvents.cloneKeyboardEvent(e); } else { return domEvents.cloneCustomEvent(e); } }, // // allows you to drop event support to any class easily // EventTarget: { implementsIn: function(eventClass, static_class) { if(!static_class && typeof(eventClass)=="function") eventClass=eventClass.prototype; eventClass.dispatchEvent = domEvents.EventTarget.prototype.dispatchEvent; eventClass.addEventListener = domEvents.EventTarget.prototype.addEventListener; eventClass.removeEventListener = domEvents.EventTarget.prototype.removeEventListener; }, prototype: {} } }; domEvents.EventTarget.prototype.addEventListener = function(eventType,f) { if(!this.eventListeners) this.eventListeners=[]; var ls = (this.eventListeners[eventType] || (this.eventListeners[eventType]=[])); if(ls.indexOf(f)==-1) { ls.push(f); } } domEvents.EventTarget.prototype.removeEventListener = function(eventType,f) { if(!this.eventListeners) this.eventListeners=[]; var ls = (this.eventListeners[eventType] || (this.eventListeners[eventType]=[])), i; if((i=ls.indexOf(f))!==-1) { ls.splice(i,1); } } domEvents.EventTarget.prototype.dispatchEvent = function(event_or_type) { if(!this.eventListeners) this.eventListeners=[]; // abort quickly when no listener has been set up if(typeof(event_or_type) == "string") { if(!this.eventListeners[event_or_type] || this.eventListeners[event_or_type].length==0) { return; } } else { if(!this.eventListeners[event_or_type.type] || this.eventListeners[event_or_type.type].length==0) { return; } } // convert the event var event = event_or_type; function setUpPropertyForwarding(e,ee,key) { Object.defineProperty(ee,key,{ get:function() { var v = e[key]; if(typeof(v)=="function") { return v.bind(e); } else { return v; } }, set:function(v) { e[key] = v; } }); } function setUpTarget(e,v) { try { Object.defineProperty(e,"target",{get:function() {return v}}); } catch(ex) {} finally { if(e.target !== v) { var ee = Object.create(Object.getPrototypeOf(e)); ee = setUpTarget(ee,v); for(key in e) { if(key != "target") setUpPropertyForwarding(e,ee,key); } return ee; } else { return e; } } } // try to set the target if(typeof(event)=="object") { try { event=setUpTarget(event,this); } catch(ex) {} } else if(typeof(event)=="string") { event = document.createEvent("CustomEvent"); event.initCustomEvent(event_or_type, /*canBubble:*/ true, /*cancelable:*/ false, /*detail:*/this); try { event=setUpTarget(event,this); } catch(ex) {} } else { throw new Error("dispatchEvent expect an Event object or a string containing the event type"); } // call all listeners var ls = (this.eventListeners[event.type] || (this.eventListeners[event.type]=[])); for(var i=ls.length; i--;) { try { ls[i](event); } catch(ex) { setImmediate(function() { throw ex; }); } } return event.isDefaultPrevented; } return domEvents; })(window, document); require.define('src/core/dom-events.js'); //////////////////////////////////////// // // note: this file is based on Tab Atkins's CSS Parser // please include him (@tabatkins) if you open any issue for this file // module.exports = (function(window, document) { "use strict"; // // exports // var cssSyntax = { tokenize: function(string) {/*filled later*/}, parse: function(tokens) {/*filled later*/} }; // // css tokenizer // // Add support for token lists (superclass of array) function TokenList() { var array = []; array.toCSSString=TokenListToCSSString; return array; } function TokenListToCSSString(sep) { if(sep) { return this.map(function(o) { return o.toCSSString(); }).join(sep); } else { return this.asCSSString || (this.asCSSString = ( this.map(function(o) { return o.toCSSString(); }).join("/**/") .replace(/( +\/\*\*\/ *| * | *\/\*\*\/ +)/g," ") .replace(/( +\/\*\*\/ *| * | *\/\*\*\/ +)/g," ") .replace(/(\!|\:|\;|\@|\.|\,|\*|\=|\&|\\|\/|\<|\>|\[|\{|\(|\]|\}|\)|\|)\/\*\*\//g,"$1") .replace(/\/\*\*\/(\!|\:|\;|\@|\.|\,|\*|\=|\&|\\|\/|\<|\>|\[|\{|\(|\]|\}|\)|\|)/g,"$1") )); } } cssSyntax.TokenList = TokenList; cssSyntax.TokenListToCSSString = TokenListToCSSString; function between(num, first, last) { return num >= first && num <= last; } function digit(code) { return between(code, 0x30,0x39); } function hexdigit(code) { return digit(code) || between(code, 0x41,0x46) || between(code, 0x61,0x66); } function uppercaseletter(code) { return between(code, 0x41,0x5a); } function lowercaseletter(code) { return between(code, 0x61,0x7a); } function letter(code) { return uppercaseletter(code) || lowercaseletter(code); } function nonascii(code) { return code >= 0x80; } function namestartchar(code) { return letter(code) || nonascii(code) || code == 0x5f; } function namechar(code) { return namestartchar(code) || digit(code) || code == 0x2d; } function nonprintable(code) { return between(code, 0,8) || code == 0xb || between(code, 0xe,0x1f) || code == 0x7f; } function newline(code) { return code == 0xa; } function whitespace(code) { return newline(code) || code == 9 || code == 0x20; } function badescape(code) { return newline(code) || isNaN(code); } var maximumallowedcodepoint = 0x10ffff; function InvalidCharacterError(message) { this.message = message; }; InvalidCharacterError.prototype = new Error; InvalidCharacterError.prototype.name = 'InvalidCharacterError'; function preprocess(str) { // Turn a string into an array of code points, // following the preprocessing cleanup rules. var codepoints = []; for(var i = 0; i < str.length; i++) { var code = str.charCodeAt(i); if(code == 0xd && str.charCodeAt(i+1) == 0xa) { code = 0xa; i++; } if(code == 0xd || code == 0xc) code = 0xa; if(code == 0x0) code = 0xfffd; if(between(code, 0xd800, 0xdbff) && between(str.charCodeAt(i+1), 0xdc00, 0xdfff)) { // Decode a surrogate pair into an astral codepoint. var lead = code - 0xd800; var trail = str.charCodeAt(i+1) - 0xdc00; code = Math.pow(2, 21) + lead * Math.pow(2, 10) + trail; } codepoints.push(code); } return codepoints; } function stringFromCode(code) { if(code <= 0xffff) return String.fromCharCode(code); // Otherwise, encode astral char as surrogate pair. code -= Math.pow(2, 21); var lead = Math.floor(code/Math.pow(2, 10)) + 0xd800; var trail = code % Math.pow(2, 10); + 0xdc00; return String.fromCharCode(lead) + String.fromCharCode(trail); } function tokenize(str) { str = preprocess(str); var i = -1; var tokens = new TokenList(); var code; // Line number information. var line = 0; var column = 0; // The only use of lastLineLength is in reconsume(). var lastLineLength = 0; var incrLineno = function() { line += 1; lastLineLength = column; column = 0; }; var locStart = {line:line, column:column}; var codepoint = function(i) { if(i >= str.length) { return -1; } return str[i]; } var next = function(num) { if(num === undefined) { num = 1; } if(num > 3) { throw "Spec Error: no more than three codepoints of lookahead."; } return codepoint(i+num); }; var consume = function(num) { if(num === undefined) num = 1; i += num; code = codepoint(i); if(newline(code)) incrLineno(); else column += num; //console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16)); return true; }; var reconsume = function() { i -= 1; if (newline(code)) { line -= 1; column = lastLineLength; } else { column -= 1; } locStart.line = line; locStart.column = column; return true; }; var eof = function(codepoint) { if(codepoint === undefined) codepoint = code; return codepoint == -1; }; var donothing = function() {}; var tokenizeerror = function() { console.log("Parse error at index " + i + ", processing codepoint 0x" + code.toString(16) + ".");return true; }; var consumeAToken = function() { consumeComments(); consume(); if(whitespace(code)) { while(whitespace(next())) consume(); return new WhitespaceToken; } else if(code == 0x22) return consumeAStringToken(); else if(code == 0x23) { if(namechar(next()) || areAValidEscape(next(1), next(2))) { var token = new HashToken(); if(wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = "id"; token.value = consumeAName(); return token; } else { return new DelimToken(code); } } else if(code == 0x24) { if(next() == 0x3d) { consume(); return new SuffixMatchToken(); } else { return new DelimToken(code); } } else if(code == 0x27) return consumeAStringToken(); else if(code == 0x28) return new OpenParenToken(); else if(code == 0x29) return new CloseParenToken(); else if(code == 0x2a) { if(next() == 0x3d) { consume(); return new SubstringMatchToken(); } else { return new DelimToken(code); } } else if(code == 0x2b) { if(startsWithANumber()) { reconsume(); return consumeANumericToken(); } else { return new DelimToken(code); } } else if(code == 0x2c) return new CommaToken(); else if(code == 0x2d) { if(startsWithANumber()) { reconsume(); return consumeANumericToken(); } else if(next(1) == 0x2d && next(2) == 0x3e) { consume(2); return new CDCToken(); } else if(startsWithAnIdentifier()) { reconsume(); return consumeAnIdentlikeToken(); } else { return new DelimToken(code); } } else if(code == 0x2e) { if(startsWithANumber()) { reconsume(); return consumeANumericToken(); } else { return new DelimToken(code); } } else if(code == 0x3a) return new ColonToken; else if(code == 0x3b) return new SemicolonToken; else if(code == 0x3c) { if(next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) { consume(3); return new CDOToken(); } else { return new DelimToken(code); } } else if(code == 0x40) { if(wouldStartAnIdentifier(next(1), next(2), next(3))) { return new AtKeywordToken(consumeAName()); } else { return new DelimToken(code); } } else if(code == 0x5b) return new OpenSquareToken(); else if(code == 0x5c) { if(startsWithAValidEscape()) { reconsume(); return consumeAnIdentlikeToken(); } else { tokenizeerror(); return new DelimToken(code); } } else if(code == 0x5d) return new CloseSquareToken(); else if(code == 0x5e) { if(next() == 0x3d) { consume(); return new PrefixMatchToken(); } else { return new DelimToken(code); } } else if(code == 0x7b) return new OpenCurlyToken(); else if(code == 0x7c) { if(next() == 0x3d) { consume(); return new DashMatchToken(); } else if(next() == 0x7c) { consume(); return new ColumnToken(); } else { return new DelimToken(code); } } else if(code == 0x7d) return new CloseCurlyToken(); else if(code == 0x7e) { if(next() == 0x3d) { consume(); return new IncludeMatchToken(); } else { return new DelimToken(code); } } else if(digit(code)) { reconsume(); return consumeANumericToken(); } else if(namestartchar(code)) { reconsume(); return consumeAnIdentlikeToken(); } else if(eof()) return new EOFToken(); else return new DelimToken(code); }; var consumeComments = function() { while(next(1) == 0x2f && next(2) == 0x2a) { consume(2); while(true) { consume(); if(code == 0x2a && next() == 0x2f) { consume(); break; } else if(eof()) { tokenizeerror(); return; } } } }; var consumeANumericToken = function() { var num = consumeANumber(); if(wouldStartAnIdentifier(next(1), next(2), next(3))) { var token = new DimensionToken(); token.value = num.value; token.repr = num.repr; token.type = num.type; token.unit = consumeAName(); return token; } else if(next() == 0x25) { consume(); var token = new PercentageToken(); token.value = num.value; token.repr = num.repr; return token; } else { var token = new NumberToken(); token.value = num.value; token.repr = num.repr; token.type = num.type; return token; } }; var consumeAnIdentlikeToken = function() { var str = consumeAName(); if(str.toLowerCase() == "url" && next() == 0x28) { consume(); while(whitespace(next(1)) && whitespace(next(2))) consume(); if(next() == 0x22 || next() == 0x27) { return new FunctionToken(str); } else if(whitespace(next()) && (next(2) == 0x22 || next(2) == 0x27)) { return new FunctionToken(str); } else { return consumeAURLToken(); } } else if(next() == 0x28) { consume(); return new FunctionToken(str); } else { return new IdentifierToken(str); } }; var consumeAStringToken = function(endingCodePoint) { if(endingCodePoint === undefined) endingCodePoint = code; var string = ""; while(consume()) { if(code == endingCodePoint || eof()) { return new StringToken(string); } else if(newline(code)) { tokenizeerror(); reconsume(); return new BadStringToken(); } else if(code == 0x5c) { if(eof(next())) { donothing(); } else if(newline(next())) { consume(); } else { string += stringFromCode(consumeEscape()) } } else { string += stringFromCode(code); } } }; var consumeAURLToken = function() { var token = new URLToken(""); while(whitespace(next())) consume(); if(eof(next())) return token; while(consume()) { if(code == 0x29 || eof()) { return token; } else if(whitespace(code)) { while(whitespace(next())) consume(); if(next() == 0x29 || eof(next())) { consume(); return token; } else { consumeTheRemnantsOfABadURL(); return new BadURLToken(); } } else if(code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) { tokenizeerror(); consumeTheRemnantsOfABadURL(); return new BadURLToken(); } else if(code == 0x5c) { if(startsWithAValidEscape()) { token.value += stringFromCode(consumeEscape()); } else { tokenizeerror(); consumeTheRemnantsOfABadURL(); return new BadURLToken(); } } else { token.value += stringFromCode(code); } } }; var consumeEscape = function() { // Assume the the current character is the \ // and the next code point is not a newline. consume(); if(hexdigit(code)) { // Consume 1-6 hex digits var digits = [code]; for(var total = 0; total < 5; total++) { if(hexdigit(next())) { consume(); digits.push(code); } else { break; } } if(whitespace(next())) consume(); var value = parseInt(digits.map(function(x){return String.fromCharCode(x);}).join(''), 16); if( value > maximumallowedcodepoint ) value = 0xfffd; return value; } else if(eof()) { return 0xfffd; } else { return code; } }; var areAValidEscape = function(c1, c2) { if(c1 != 0x5c) return false; if(newline(c2)) return false; return true; }; var startsWithAValidEscape = function() { return areAValidEscape(code, next()); }; var wouldStartAnIdentifier = function(c1, c2, c3) { if(c1 == 0x2d) { return namestartchar(c2) || c2 == 0x2d || areAValidEscape(c2, c3); } else if(namestartchar(c1)) { return true; } else if(c1 == 0x5c) { return areAValidEscape(c1, c2); } else { return false; } }; var startsWithAnIdentifier = function() { return wouldStartAnIdentifier(code, next(1), next(2)); }; var wouldStartANumber = function(c1, c2, c3) { if(c1 == 0x2b || c1 == 0x2d) { if(digit(c2)) return true; if(c2 == 0x2e && digit(c3)) return true; return false; } else if(c1 == 0x2e) { if(digit(c2)) return true; return false; } else if(digit(c1)) { return true; } else { return false; } }; var startsWithANumber = function() { return wouldStartANumber(code, next(1), next(2)); }; var consumeAName = function() { var result = ""; while(consume()) { if(namechar(code)) { result += stringFromCode(code); } else if(startsWithAValidEscape()) { result += stringFromCode(consumeEscape()); } else { reconsume(); return result; } } }; var consumeANumber = function() { var repr = ''; var type = "integer"; if(next() == 0x2b || next() == 0x2d) { consume(); repr += stringFromCode(code); } while(digit(next())) { consume(); repr += stringFromCode(code); } if(next(1) == 0x2e && digit(next(2))) { consume(); repr += stringFromCode(code); consume(); repr += stringFromCode(code); type = "number"; while(digit(next())) { consume(); repr += stringFromCode(code); } } var c1 = next(1), c2 = next(2), c3 = next(3); if((c1 == 0x45 || c1 == 0x65) && digit(c2)) { consume(); repr += stringFromCode(code); consume(); repr += stringFromCode(code); type = "number"; while(digit(next())) { consume(); repr += stringFromCode(code); } } else if((c1 == 0x45 || c1 == 0x65) && (c2 == 0x2b || c2 == 0x2d) && digit(c3)) { consume(); repr += stringFromCode(code); consume(); repr += stringFromCode(code); consume(); repr += stringFromCode(code); type = "number"; while(digit(next())) { consume(); repr += stringFromCode(code); } } var value = convertAStringToANumber(repr); return {type:type, value:value, repr:repr}; }; var convertAStringToANumber = function(string) { // CSS's number rules are identical to JS, afaik. return +string; }; var consumeTheRemnantsOfABadURL = function() { while(consume()) { if(code == 0x2d || eof()) { return; } else if(startsWithAValidEscape()) { consumeEscape(); donothing(); } else { donothing(); } } }; var iterationCount = 0; while(!eof(next())) { tokens.push(consumeAToken()); if(iterationCount++ > str.length*2) throw new Error("The CSS Tokenizer is infinite-looping"); } return tokens; } function CSSParserToken() { return this; } CSSParserToken.prototype.toJSON = function() { return {token: this.tokenType}; } CSSParserToken.prototype.toString = function() { return this.tokenType; } CSSParserToken.prototype.toCSSString = function() { return ''+this; } function BadStringToken() { return this; } BadStringToken.prototype = new CSSParserToken; BadStringToken.prototype.tokenType = "BADSTRING"; BadStringToken.prototype.toCSSString = function() { return "'"; } function BadURLToken() { return this; } BadURLToken.prototype = new CSSParserToken; BadURLToken.prototype.tokenType = "BADURL"; BadURLToken.prototype.toCSSString = function() { return "url("; } function WhitespaceToken() { return this; } WhitespaceToken.prototype = new CSSParserToken; WhitespaceToken.prototype.tokenType = "WHITESPACE"; WhitespaceToken.prototype.toString = function() { return "WS"; } WhitespaceToken.prototype.toCSSString = function() { return " "; } function CDOToken() { return this; } CDOToken.prototype = new CSSParserToken; CDOToken.prototype.tokenType = "CDO"; CDOToken.prototype.toCSSString = function() { return ""; } function ColonToken() { return this; } ColonToken.prototype = new CSSParserToken; ColonToken.prototype.tokenType = ":"; function SemicolonToken() { return this; } SemicolonToken.prototype = new CSSParserToken; SemicolonToken.prototype.tokenType = ";"; function CommaToken() { return this; } CommaToken.prototype = new CSSParserToken; CommaToken.prototype.tokenType = ","; CommaToken.prototype.value = ";"; // backwards-compat with DELIM token function GroupingToken() { return this; } GroupingToken.prototype = new CSSParserToken; function OpenCurlyToken() { this.value = "{"; this.mirror = "}"; return this; } OpenCurlyToken.prototype = new GroupingToken; OpenCurlyToken.prototype.tokenType = "{"; function CloseCurlyToken() { this.value = "}"; this.mirror = "{"; return this; } CloseCurlyToken.prototype = new GroupingToken; CloseCurlyToken.prototype.tokenType = "}"; function OpenSquareToken() { this.value = "["; this.mirror = "]"; return this; } OpenSquareToken.prototype = new GroupingToken; OpenSquareToken.prototype.tokenType = "["; function CloseSquareToken() { this.value = "]"; this.mirror = "["; return this; } CloseSquareToken.prototype = new GroupingToken; CloseSquareToken.prototype.tokenType = "]"; function OpenParenToken() { this.value = "("; this.mirror = ")"; return this; } OpenParenToken.prototype = new GroupingToken; OpenParenToken.prototype.tokenType = "("; function CloseParenToken() { this.value = ")"; this.mirror = "("; return this; } CloseParenToken.prototype = new GroupingToken; CloseParenToken.prototype.tokenType = ")"; function IncludeMatchToken() { return this; } IncludeMatchToken.prototype = new CSSParserToken; IncludeMatchToken.prototype.tokenType = "~="; function DashMatchToken() { return this; } DashMatchToken.prototype = new CSSParserToken; DashMatchToken.prototype.tokenType = "|="; function PrefixMatchToken() { return this; } PrefixMatchToken.prototype = new CSSParserToken; PrefixMatchToken.prototype.tokenType = "^="; function SuffixMatchToken() { return this; } SuffixMatchToken.prototype = new CSSParserToken; SuffixMatchToken.prototype.tokenType = "$="; function SubstringMatchToken() { return this; } SubstringMatchToken.prototype = new CSSParserToken; SubstringMatchToken.prototype.tokenType = "*="; function ColumnToken() { return this; } ColumnToken.prototype = new CSSParserToken; ColumnToken.prototype.tokenType = "||"; function EOFToken() { return this; } EOFToken.prototype = new CSSParserToken; EOFToken.prototype.tokenType = "EOF"; EOFToken.prototype.toCSSString = function() { return ""; } function DelimToken(code) { this.value = stringFromCode(code); return this; } DelimToken.prototype = new CSSParserToken; DelimToken.prototype.tokenType = "DELIM"; DelimToken.prototype.toString = function() { return "DELIM("+this.value+")"; } DelimToken.prototype.toCSSString = function() { return (this.value == "\\") ? "\\\n" : this.value; } function StringValuedToken() { return this; } StringValuedToken.prototype = new CSSParserToken; StringValuedToken.prototype.ASCIIMatch = function(str) { return this.value.toLowerCase() == str.toLowerCase(); } function IdentifierToken(val) { this.value = val; } IdentifierToken.prototype = new StringValuedToken; IdentifierToken.prototype.tokenType = "IDENT"; IdentifierToken.prototype.toString = function() { return "IDENT("+this.value+")"; } IdentifierToken.prototype.toCSSString = function() { return escapeIdent(this.value); } function FunctionToken(val) { this.value = val; this.mirror = ")"; } FunctionToken.prototype = new StringValuedToken; FunctionToken.prototype.tokenType = "FUNCTION"; FunctionToken.prototype.toString = function() { return "FUNCTION("+this.value+")"; } FunctionToken.prototype.toCSSString = function() { return escapeIdent(this.value) + "("; } function AtKeywordToken(val) { this.value = val; } AtKeywordToken.prototype = new StringValuedToken; AtKeywordToken.prototype.tokenType = "AT-KEYWORD"; AtKeywordToken.prototype.toString = function() { return "AT("+this.value+")"; } AtKeywordToken.prototype.toCSSString = function() { return "@" + escapeIdent(this.value); } function HashToken(val) { this.value = val; this.type = "unrestricted"; } HashToken.prototype = new StringValuedToken; HashToken.prototype.tokenType = "HASH"; HashToken.prototype.toString = function() { return "HASH("+this.value+")"; } HashToken.prototype.toCSSString = function() { var escapeValue = (this.type == "id") ? escapeIdent : escapeHash; return "#" + escapeValue(this.value); } function StringToken(val) { this.value = val; } StringToken.prototype = new StringValuedToken; StringToken.prototype.tokenType = "STRING"; StringToken.prototype.toString = function() { return '"' + escapeString(this.value) + '"'; } function URLToken(val) { this.value = val; } URLToken.prototype = new StringValuedToken; URLToken.prototype.tokenType = "URL"; URLToken.prototype.toString = function() { return "URL("+this.value+")"; } URLToken.prototype.toCSSString = function() { return 'url("' + escapeString(this.value) + '")'; } function NumberToken() { this.value = null; this.type = "integer"; this.repr = ""; } NumberToken.prototype = new CSSParserToken; NumberToken.prototype.tokenType = "NUMBER"; NumberToken.prototype.toString = function() { if(this.type == "integer") return "INT("+this.value+")"; return "NUMBER("+this.value+")"; } NumberToken.prototype.toJSON = function() { var json = this.constructor.prototype.constructor.prototype.toJSON.call(this); json.value = this.value; json.type = this.type; json.repr = this.repr; return json; } NumberToken.prototype.toCSSString = function() { return this.repr; }; function PercentageToken() { this.value = null; this.repr = ""; } PercentageToken.prototype = new CSSParserToken; PercentageToken.prototype.tokenType = "PERCENTAGE"; PercentageToken.prototype.toString = function() { return "PERCENTAGE("+this.value+")"; } PercentageToken.prototype.toCSSString = function() { return this.repr + "%"; } function DimensionToken() { this.value = null; this.type = "integer"; this.repr = ""; this.unit = ""; } DimensionToken.prototype = new CSSParserToken; DimensionToken.prototype.tokenType = "DIMENSION"; DimensionToken.prototype.toString = function() { return "DIM("+this.value+","+this.unit+")"; } DimensionToken.prototype.toCSSString = function() { var source = this.repr; var unit = escapeIdent(this.unit); if(unit[0].toLowerCase() == "e" && (unit[1] == "-" || between(unit.charCodeAt(1), 0x30, 0x39))) { // Unit is ambiguous with scinot // Remove the leading "e", replace with escape. unit = "\\65 " + unit.slice(1, unit.length); } return source+unit; } function escapeIdent(string) { string = ''+string; var result = ''; var firstcode = string.charCodeAt(0); for(var i = 0; i < string.length; i++) { var code = string.charCodeAt(i); if(code == 0x0) { throw new InvalidCharacterError('Invalid character: the input contains U+0000.'); } if( between(code, 0x1, 0x1f) || code == 0x7f || (i == 0 && between(code, 0x30, 0x39)) || (i == 1 && between(code, 0x30, 0x39) && firstcode == 0x2d) ) { result += '\\' + code.toString(16) + ' '; } else if( code >= 0x80 || code == 0x2d || code == 0x5f || between(code, 0x30, 0x39) || between(code, 0x41, 0x5a) || between(code, 0x61, 0x7a) ) { result += string[i]; } else { result += '\\' + string[i]; } } return result; } function escapeHash(string) { // Escapes the contents of "unrestricted"-type hash tokens. // Won't preserve the ID-ness of "id"-type hash tokens; // use escapeIdent() for that. string = ''+string; var result = ''; var firstcode = string.charCodeAt(0); for(var i = 0; i < string.length; i++) { var code = string.charCodeAt(i); if(code == 0x0) { throw new InvalidCharacterError('Invalid character: the input contains U+0000.'); } if( code >= 0x80 || code == 0x2d || code == 0x5f || between(code, 0x30, 0x39) || between(code, 0x41, 0x5a) || between(code, 0x61, 0x7a) ) { result += string[i]; } else { result += '\\' + code.toString(16) + ' '; } } return result; } function escapeString(string) { string = ''+string; var result = ''; for(var i = 0; i < string.length; i++) { var code = string.charCodeAt(i); if(code == 0x0) { throw new InvalidCharacterError('Invalid character: the input contains U+0000.'); } if(between(code, 0x1, 0x1f) || code == 0x7f) { result += '\\' + code.toString(16) + ' '; } else if(code == 0x22 || code == 0x5c) { result += '\\' + string[i]; } else { result += string[i]; } } return result; } // Exportation. cssSyntax.tokenize = tokenize; cssSyntax.IdentToken = IdentifierToken; cssSyntax.IdentifierToken = IdentifierToken; cssSyntax.FunctionToken = FunctionToken; cssSyntax.AtKeywordToken = AtKeywordToken; cssSyntax.HashToken = HashToken; cssSyntax.StringToken = StringToken; cssSyntax.BadStringToken = BadStringToken; cssSyntax.URLToken = URLToken; cssSyntax.BadURLToken = BadURLToken; cssSyntax.DelimToken = DelimToken; cssSyntax.NumberToken = NumberToken; cssSyntax.PercentageToken = PercentageToken; cssSyntax.DimensionToken = DimensionToken; cssSyntax.IncludeMatchToken = IncludeMatchToken; cssSyntax.DashMatchToken = DashMatchToken; cssSyntax.PrefixMatchToken = PrefixMatchToken; cssSyntax.SuffixMatchToken = SuffixMatchToken; cssSyntax.SubstringMatchToken = SubstringMatchToken; cssSyntax.ColumnToken = ColumnToken; cssSyntax.WhitespaceToken = WhitespaceToken; cssSyntax.CDOToken = CDOToken; cssSyntax.CDCToken = CDCToken; cssSyntax.ColonToken = ColonToken; cssSyntax.SemicolonToken = SemicolonToken; cssSyntax.CommaToken = CommaToken; cssSyntax.OpenParenToken = OpenParenToken; cssSyntax.CloseParenToken = CloseParenToken; cssSyntax.OpenSquareToken = OpenSquareToken; cssSyntax.CloseSquareToken = CloseSquareToken; cssSyntax.OpenCurlyToken = OpenCurlyToken; cssSyntax.CloseCurlyToken = CloseCurlyToken; cssSyntax.EOFToken = EOFToken; cssSyntax.CSSParserToken = CSSParserToken; cssSyntax.GroupingToken = GroupingToken; // // css parser // function TokenStream(tokens) { // Assume that tokens is an array. this.tokens = tokens; this.i = -1; } TokenStream.prototype.tokenAt = function(i) { if(i < this.tokens.length) return this.tokens[i]; return new EOFToken(); } TokenStream.prototype.consume = function(num) { if(num === undefined) num = 1; this.i += num; this.token = this.tokenAt(this.i); //console.log(this.i, this.token); return true; } TokenStream.prototype.next = function() { return this.tokenAt(this.i+1); } TokenStream.prototype.reconsume = function() { this.i--; } function parseerror(s, msg) { console.log("Parse error at token " + s.i + ": " + s.token + ".\n" + msg); return true; } function donothing(){ return true; }; function consumeAListOfRules(s, topLevel) { var rules = new TokenList(); var rule; while(s.consume()) { if(s.token instanceof WhitespaceToken) { continue; } else if(s.token instanceof EOFToken) { return rules; } else if(s.token instanceof CDOToken || s.token instanceof CDCToken) { if(topLevel == "top-level") continue; s.reconsume(); if(rule = consumeAStyleRule(s)) rules.push(rule); } else if(s.token instanceof AtKeywordToken) { s.reconsume(); if(rule = consumeAnAtRule(s)) rules.push(rule); } else { s.reconsume(); if(rule = consumeAStyleRule(s)) rules.push(rule); } } } function consumeAnAtRule(s) { s.consume(); var rule = new AtRule(s.token.value); while(s.consume()) { if(s.token instanceof SemicolonToken || s.token instanceof EOFToken) { return rule; } else if(s.token instanceof OpenCurlyToken) { rule.value = consumeASimpleBlock(s); return rule; } else if(s.token instanceof SimpleBlock && s.token.name == "{") { rule.value = s.token; return rule; } else { s.reconsume(); rule.prelude.push(consumeAComponentValue(s)); } } } function consumeAStyleRule(s) { var rule = new StyleRule(); while(s.consume()) { if(s.token instanceof EOFToken) { parseerror(s, "Hit EOF when trying to parse the prelude of a qualified rule."); return; } else if(s.token instanceof OpenCurlyToken) { rule.value = consumeASimpleBlock(s); return rule; } else if(s.token instanceof SimpleBlock && s.token.name == "{") { rule.value = s.token; return rule; } else { s.reconsume(); rule.prelude.push(consumeAComponentValue(s)); } } } function consumeAListOfDeclarations(s) { var decls = new TokenList(); while(s.consume()) { if(s.token instanceof WhitespaceToken || s.token instanceof SemicolonToken) { donothing(); } else if(s.token instanceof EOFToken) { return decls; } else if(s.token instanceof AtKeywordToken) { s.reconsume(); decls.push(consumeAnAtRule(s)); } else if(s.token instanceof IdentifierToken) { var temp = [s.token]; while(!(s.next() instanceof SemicolonToken || s.next() instanceof EOFToken)) temp.push(consumeAComponentValue(s)); var decl; if(decl = consumeADeclaration(new TokenStream(temp))) decls.push(decl); } else { parseerror(s); s.reconsume(); while(!(s.next() instanceof SemicolonToken || s.next() instanceof EOFToken)) consumeAComponentValue(s); } } } function consumeADeclaration(s) { // Assumes that the next input token will be an ident token. s.consume(); var decl = new Declaration(s.token.value); while(s.next() instanceof WhitespaceToken) s.consume(); if(!(s.next() instanceof ColonToken)) { parseerror(s); return; } else { s.consume(); } while(!(s.next() instanceof EOFToken)) { decl.value.push(consumeAComponentValue(s)); } var foundImportant = false; for(var i = decl.value.length - 1; i >= 0; i--) { if(decl.value[i] instanceof WhitespaceToken) { continue; } else if(decl.value[i] instanceof IdentifierToken && decl.value[i].ASCIIMatch("important")) { foundImportant = true; } else if(foundImportant && decl.value[i] instanceof DelimToken && decl.value[i].value == "!") { decl.value.splice(i, decl.value.length); decl.important = true; break; } else { break; } } return decl; } function consumeAComponentValue(s) { s.consume(); if(s.token instanceof OpenCurlyToken || s.token instanceof OpenSquareToken || s.token instanceof OpenParenToken) return consumeASimpleBlock(s); if(s.token instanceof FunctionToken) return consumeAFunction(s); return s.token; } function consumeASimpleBlock(s) { var mirror = s.token.mirror; var block = new SimpleBlock(s.token.value); while(s.consume()) { if(s.token instanceof EOFToken || (s.token instanceof GroupingToken && s.token.value == mirror)) return block; else { s.reconsume(); block.value.push(consumeAComponentValue(s)); } } } function consumeAFunction(s) { var func = new Func(s.token.value); while(s.consume()) { if(s.token instanceof EOFToken || s.token instanceof CloseParenToken) return func; else { s.reconsume(); func.value.push(consumeAComponentValue(s)); } } } function normalizeInput(input) { if(typeof input == "string") return new TokenStream(tokenize(input)); if(input instanceof TokenStream) return input; if(input.length !== undefined) return new TokenStream(input); else throw SyntaxError(input); } function parseAStylesheet(s) { s = normalizeInput(s); var sheet = new Stylesheet(); sheet.value = consumeAListOfRules(s, "top-level"); return sheet; } function parseAListOfRules(s) { s = normalizeInput(s); return consumeAListOfRules(s); } function parseARule(s) { s = normalizeInput(s); while(s.next() instanceof WhitespaceToken) s.consume(); if(s.next() instanceof EOFToken) throw SyntaxError(); if(s.next() instanceof AtKeywordToken) { var rule = consumeAnAtRule(s); } else { var rule = consumeAStyleRule(s); if(!rule) throw SyntaxError(); } while(s.next() instanceof WhitespaceToken) s.consume(); if(s.next() instanceof EOFToken) return rule; throw SyntaxError(); } function parseADeclaration(s) { s = normalizeInput(s); while(s.next() instanceof WhitespaceToken) s.consume(); if(!(s.next() instanceof IdentifierToken)) throw SyntaxError(); var decl = consumeADeclaration(s); if(!decl) { throw new SyntaxError() } return decl; } function parseAListOfDeclarations(s) { s = normalizeInput(s); return consumeAListOfDeclarations(s); } function parseAComponentValue(s) { s = normalizeInput(s); while(s.next() instanceof WhitespaceToken) s.consume(); if(s.next() instanceof EOFToken) throw SyntaxError(); var val = consumeAComponentValue(s); if(!val) throw SyntaxError(); while(s.next() instanceof WhitespaceToken) s.consume(); if(!(s.next() instanceof EOFToken)) throw new SyntaxError(); return val; } function parseAListOfComponentValues(s) { s = normalizeInput(s); var vals = new TokenList(); while(true) { var val = consumeAComponentValue(s); if(val instanceof EOFToken) return vals else vals.push(val); } } function parseACommaSeparatedListOfComponentValues(s) { s = normalizeInput(s); var listOfCVLs = new TokenList(); while(true) { var vals = new TokenList(); while(true) { var val = consumeAComponentValue(s); if(val instanceof EOFToken) { listOfCVLs.push(vals); return listOfCVLs; } else if(val instanceof CommaToken) { listOfCVLs.push(vals); break; } else { vals.push(val); } } } } function CSSParserRule() { return this; } CSSParserRule.prototype.toString = function(indent) { return JSON.stringify(this,null,indent); } function Stylesheet() { this.value = new TokenList(); return this; } Stylesheet.prototype = new CSSParserRule; Stylesheet.prototype.type = "STYLESHEET"; Stylesheet.prototype.toCSSString = function() { return this.value.toCSSString("\n"); } function AtRule(name) { this.name = name; this.prelude = new TokenList(); this.value = null; return this; } AtRule.prototype = new CSSParserRule; AtRule.prototype.toCSSString = function() { if(this.value) { return "@" + escapeIdent(this.name) + " " + this.prelude.toCSSString() + this.value.toCSSString(); } else { return "@" + escapeIdent(this.name) + " " + this.prelude.toCSSString() + '; '; } } AtRule.prototype.toStylesheet = function() { return this.asStylesheet || (this.asStylesheet = this.value ? parseAStylesheet(this.value.value) : new Stylesheet()); } function StyleRule() { this.prelude = new TokenList(); this.selector = this.prelude; this.value = null; return this; } StyleRule.prototype = new CSSParserRule; StyleRule.prototype.type = "STYLE-RULE"; StyleRule.prototype.toCSSString = function() { return this.prelude.toCSSString() + this.value.toCSSString(); } StyleRule.prototype.getSelector = function() { return this.prelude; } StyleRule.prototype.getDeclarations = function() { if(!(this.value instanceof SimpleBlock)) { return new TokenList(); } var value = this.value.value; return parseAListOfDeclarations(value); } function Declaration(name) { this.name = name; this.value = new TokenList(); this.important = false; return this; } Declaration.prototype = new CSSParserRule; Declaration.prototype.type = "DECLARATION"; Declaration.prototype.toCSSString = function() { return this.name + ':' + this.value.toCSSString() + '; '; } function SimpleBlock(type) { this.name = type; this.value = new TokenList(); return this; } SimpleBlock.prototype = new CSSParserRule; SimpleBlock.prototype.type = "BLOCK"; SimpleBlock.prototype.toCSSString = function() { switch(this.name) { case "(": return "(" + this.value.toCSSString() + ")"; case "[": return "[" + this.value.toCSSString() + "]"; case "{": return "{" + this.value.toCSSString() + "}"; default: //best guess return this.name + this.value.toCSSString() + this.name; } } function Func(name) { this.name = name; this.value = new TokenList(); return this; } Func.prototype = new CSSParserRule; Func.prototype.type = "FUNCTION"; Func.prototype.toCSSString = function() { return this.name+'('+this.value.toCSSString()+')'; } Func.prototype.getArguments = function() { var args = new TokenList(); var arg = new TokenList(); var value = this.value; for(var i = 0; i 0 || arg.length > 0) { args.push(arg); } return args; } function FuncArg() { this.value = new TokenList(); return this; } FuncArg.prototype = new CSSParserRule; FuncArg.prototype.type = "FUNCTION-ARG"; FuncArg.prototype.toCSSString = function() { return this.value.toCSSString()+', '; } // Exportation. cssSyntax.CSSParserRule = CSSParserRule; cssSyntax.Stylesheet = Stylesheet; cssSyntax.AtRule = AtRule; cssSyntax.StyleRule = StyleRule; cssSyntax.Declaration = Declaration; cssSyntax.SimpleBlock = SimpleBlock; cssSyntax.Func = Func; cssSyntax.parseAStylesheet = parseAStylesheet; cssSyntax.parseAListOfRules = parseAListOfRules; cssSyntax.parseARule = parseARule; cssSyntax.parseADeclaration = parseADeclaration; cssSyntax.parseAListOfDeclarations = parseAListOfDeclarations; cssSyntax.parseAComponentValue = parseAComponentValue; cssSyntax.parseAListOfComponentValues = parseAListOfComponentValues; cssSyntax.parseACommaSeparatedListOfComponentValues = parseACommaSeparatedListOfComponentValues; cssSyntax.parse = parseAStylesheet; cssSyntax.parseCSSValue = parseAListOfComponentValues; return cssSyntax; }()); require.define('src/core/css-syntax.js'); //////////////////////////////////////// void function() { // request animation frame var vendors = ['webkit', 'moz', 'ms', 'o']; for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { var vp = vendors[i]; window.requestAnimationFrame = window[vp+'RequestAnimationFrame']; window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame'] || window[vp+'CancelRequestAnimationFrame']); } if (!window.requestAnimationFrame || !window.cancelAnimationFrame) { // tick every 16ms var listener_index = 0; var listeners = []; var tmp = []; var tick = function() { var now = +(new Date()); var callbacks = listeners; listeners = tmp; for(var i = 0; i= 0 && index < listeners.length) { listeners[index] = function() {}; } }; } // setImmediate if(!window.setImmediate) { window.setImmediate = function(f) { return setTimeout(f, 0) }; window.cancelImmediate = clearTimeout; } }(); require.define('src/core/polyfill-dom-requestAnimationFrame.js'); //////////////////////////////////////// ///////////////////////////////////////////////////////////////// //// //// //// prerequirements of qSL //// //// //// ///////////////////////////////////////////////////////////////// //// //// //// Please note that I require querySelectorAll to work //// //// //// //// See http://github.com/termi/CSS_selector_engine/ //// //// for a polyfill for older browsers //// //// //// ///////////////////////////////////////////////////////////////// // TODO: improve event streams // - look for a few optimizations ideas in gecko/webkit // - use arrays in CompositeEventStream to avoid nested debouncings module.exports = (function(window, document) { "use strict"; /// /// event stream implementation /// please note this is required to 'live update' the qSA requests /// function EventStream(connect, disconnect, reconnect) { var self=this; // validate arguments if(!disconnect) disconnect=function(){}; if(!reconnect) reconnect=connect; // high-level states var isConnected=false; var isDisconnected=false; var shouldDisconnect=false; // global variables var callback=null; var yieldEvent = function() { // call the callback function, and pend disposal shouldDisconnect=true; try { callback && callback(self); } catch(ex) { setImmediate(function() { throw ex; }); } // if no action was taken, dispose if(shouldDisconnect) { dispose(); } } // export the interface var schedule = this.schedule = function(newCallback) { // do not allow to schedule on disconnected event streams if(isDisconnected) { throw new Error("Cannot schedule on a disconnected event stream"); } // do not allow to schedule on already scheduled event streams if(isConnected && !shouldDisconnect) { throw new Error("Cannot schedule on an already-scheduled event stream"); } // schedule the new callback callback=newCallback; shouldDisconnect=false; // reconnect to the stream if(isConnected) { reconnect(yieldEvent); } else { connect(yieldEvent); isConnected=true; } } var dispose = this.dispose = function() { // do not allow to dispose non-connected streams if(isConnected) { // disconnect & save resources disconnect(); self=null; yieldEvent=null; callback=null; isConnected=false; isDisconnected=true; shouldDisconnect=false; } } } /// /// call a function every frame /// function AnimationFrameEventStream(options) { // flag that says whether the observer is still needed or not var rid = 0; // start the event stream EventStream.call( this, function connect(yieldEvent) { rid = requestAnimationFrame(yieldEvent); }, function disconnect() { cancelAnimationFrame(rid); } ); } /// /// call a function every timeout /// function TimeoutEventStream(options) { // flag that says whether the observer is still needed or not var rid = 0; var timeout=(typeof(options)=="number") ? (+options) : ("timeout" in options ? +options.timeout : 333); // start the event stream EventStream.call( this, function connect(yieldEvent) { rid = setTimeout(yieldEvent, timeout); }, function disconnect() { clearTimeout(rid); } ); } /// /// call a function every time the mouse moves /// function MouseEventStream() { var self=this; var pointermove = (("PointerEvent" in window) ? "pointermove" : (("MSPointerEvent" in window) ? "MSPointerMove" : "mousemove")); // flag that says whether the event is still observed or not var scheduled = false; var interval=0; // handle the synchronous nature of mutation events var yieldEvent=null; var yieldEventDelayed = function() { if(scheduled) return; window.removeEventListener(pointermove, yieldEventDelayed, true); scheduled = requestAnimationFrame(yieldEvent); } // start the event stream EventStream.call( this, function connect(newYieldEvent) { yieldEvent=newYieldEvent; //window.addEventListener(pointermove, yieldEventDelayed, true); }, function disconnect() { //window.removeEventListener(pointermove, yieldEventDelayed, true); cancelAnimationFrame(scheduled); yieldEventDelayed=null; yieldEvent=null; scheduled=false; }, function reconnect(newYieldEvent) { yieldEvent=newYieldEvent; scheduled=false; //window.addEventListener(pointermove, yieldEventDelayed, true); } ); } /// /// call a function every time the mouse is clicked/unclicked /// function MouseButtonEventStream() { var self=this; var pointerup = (("PointerEvent" in window) ? "pointerup" : (("MSPointerEvent" in window) ? "MSPointerUp" : "mouseup")); var pointerdown = (("PointerEvent" in window) ? "pointerdown" : (("MSPointerEvent" in window) ? "MSPointerDown" : "mousedown")); // flag that says whether the event is still observed or not var scheduled = false; var interval=0; // handle the synchronous nature of mutation events var yieldEvent=null; var yieldEventDelayed = function() { if(scheduled) return; window.removeEventListener(pointerup, yieldEventDelayed, true); window.removeEventListener(pointerdown, yieldEventDelayed, true); scheduled = requestAnimationFrame(yieldEvent); } // start the event stream EventStream.call( this, function connect(newYieldEvent) { yieldEvent=newYieldEvent; //window.addEventListener(pointerup, yieldEventDelayed, true); //window.addEventListener(pointerdown, yieldEventDelayed, true); }, function disconnect() { //window.removeEventListener(pointerup, yieldEventDelayed, true); //window.removeEventListener(pointerdown, yieldEventDelayed, true); cancelAnimationFrame(scheduled); yieldEventDelayed=null; yieldEvent=null; scheduled=false; }, function reconnect(newYieldEvent) { yieldEvent=newYieldEvent; scheduled=false; //window.addEventListener(pointerup, yieldEventDelayed, true); //window.addEventListener(pointerdown, yieldEventDelayed, true); } ); } /// /// call a function whenever the DOM is modified /// var DOMUpdateEventStream; if("MutationObserver" in window) { DOMUpdateEventStream = function DOMUpdateEventStream(options) { // configuration of the observer if(options) { var target = "target" in options ? options.target : document.documentElement; var config = { subtree: "subtree" in options ? !!options.subtree : true, attributes: "attributes" in options ? !!options.attributes : true, childList: "childList" in options ? !!options.childList : true, characterData: "characterData" in options ? !!options.characterData : false }; } else { var target = document.documentElement; var config = { subtree: true, attributes: true, childList: true, characterData: false }; } // start the event stream var observer = null; EventStream.call( this, function connect(yieldEvent) { if(config) { observer=new MutationObserver(yieldEvent); observer.observe(target,config); target=null; config=null; } }, function disconnect() { observer && observer.disconnect(); observer=null; }, function reconnect() { observer.takeRecords(); } ); } } else if("MutationEvent" in window) { DOMUpdateEventStream = function DOMUpdateEventStream(options) { var self=this; // flag that says whether the event is still observed or not var scheduled = false; // configuration of the observer if(options) { var target = "target" in options ? options.target : document.documentElement; } else { var target = document.documentElement; } // handle the synchronous nature of mutation events var yieldEvent=null; var yieldEventDelayed = function() { if(scheduled || !yieldEventDelayed) return; document.removeEventListener("DOMContentLoaded", yieldEventDelayed, false); document.removeEventListener("DOMContentLoaded", yieldEventDelayed, false); target.removeEventListener("DOMSubtreeModified", yieldEventDelayed, false); scheduled = requestAnimationFrame(yieldEvent); } // start the event stream EventStream.call( this, function connect(newYieldEvent) { yieldEvent=newYieldEvent; document.addEventListener("DOMContentLoaded", yieldEventDelayed, false); //target.addEventListener("DOMSubtreeModified", yieldEventDelayed, false); }, function disconnect() { document.removeEventListener("DOMContentLoaded", yieldEventDelayed, false); //target.removeEventListener("DOMSubtreeModified", yieldEventDelayed, false); cancelAnimationFrame(scheduled); yieldEventDelayed=null; yieldEvent=null; scheduled=false; }, function reconnect(newYieldEvent) { yieldEvent=newYieldEvent; scheduled=false; //target.addEventListener("DOMSubtreeModified", yieldEventDelayed, false); } ); } } else { DOMUpdateEventStream = AnimationFrameEventStream; } /// /// call a function every time the focus shifts /// function FocusEventStream() { var self=this; // handle the filtering nature of focus events var yieldEvent=null; var previousActiveElement=null; var previousHasFocus=false; var rid=0; var yieldEventDelayed = function() { // if the focus didn't change if(previousActiveElement==document.activeElement && previousHasFocus==document.hasFocus()) { // then do not generate an event setTimeout(yieldEventDelayed, 333); // focus that didn't move is expected to stay } else { // else, generate one & save config previousActiveElement=document.activeElement; previousHasFocus=document.hasFocus(); yieldEvent(); } } // start the event stream EventStream.call( this, function connect(newYieldEvent) { yieldEvent=newYieldEvent; rid=setTimeout(yieldEventDelayed, 500); // let the document load }, function disconnect() { clearTimeout(rid); yieldEventDelayed=null; yieldEvent=null; rid=0; }, function reconnect(newYieldEvent) { yieldEvent=newYieldEvent; rid=setTimeout(yieldEventDelayed, 100); // focus by tab navigation moves fast } ); } /// /// composite event stream /// because sometimes you need more than one event source /// function CompositeEventStream(stream1, stream2) { var self=this; // fields var yieldEvent=null; var s1=false, s2=false; var yieldEventWrapper=function(s) { if(s==stream1) s1=true; if(s==stream2) s2=true; if(s1&&s2) return; yieldEvent(self); } // start the event stream EventStream.call( this, function connect(newYieldEvent) { yieldEvent=newYieldEvent; stream1.schedule(yieldEventWrapper); stream2.schedule(yieldEventWrapper); }, function disconnect() { stream1.dispose(); stream2.dispose(); }, function reconnect(newYieldEvent) { yieldEvent=newYieldEvent; s1 && stream1.schedule(yieldEventWrapper); s2 && stream2.schedule(yieldEventWrapper); s1 = s2 = false; } ); } return { EventStream: EventStream, AnimationFrameEventStream: AnimationFrameEventStream, TimeoutEventStream: TimeoutEventStream, MouseEventStream: MouseEventStream, MouseButtonEventStream: MouseButtonEventStream, DOMUpdateEventStream: DOMUpdateEventStream, FocusEventStream: FocusEventStream, CompositeEventStream: CompositeEventStream }; })(window, document); require.define('src/core/dom-experimental-event-streams.js'); //////////////////////////////////////// ///////////////////////////////////////////////////////////////// //// //// //// Implementation of qSL //// //// //// ///////////////////////////////////////////////////////////////// //// //// //// Please note that I require querySelectorAll to work //// //// //// //// See http://github.com/termi/CSS_selector_engine/ //// //// for a polyfill for older browsers //// //// //// ///////////////////////////////////////////////////////////////// module.exports = (function(window, document) { "use strict"; // import dependencies var eventStreams = require('src/core/dom-experimental-event-streams.js'), DOMUpdateEventStream = eventStreams.DOMUpdateEventStream, AnimationFrameEventStream = eventStreams.AnimationFrameEventStream, CompositeEventStream = eventStreams.CompositeEventStream, FocusEventStream = eventStreams.FocusEventStream, MouseButtonEventStream = eventStreams.MouseButtonEventStream, TimeoutEventStream = eventStreams.TimeoutEventStream, MouseEventStream = eventStreams.MouseEventStream; /// /// the live querySelectorAll implementation /// function querySelectorLive(selector, handler, root) { // restrict the selector coverage to some part of the DOM only var root = root || document; // TODO: make use of "mutatedAncestorElement" to update only elements inside the mutated zone var currentElms = []; var loop = function loop(eventStream) { // schedule next run eventStream.schedule(loop); // update elements matching the selector var newElms = []; var oldElms = currentElms.slice(0); var temps = root.querySelectorAll(selector); for(var i=newElms.length=temps.length; i;) { newElms.push(temps[--i]); } currentElms = newElms.slice(0); temps=null; // first let's clear all elements that have been removed from the document oldElms = oldElms.filter(function(e) { // check whether the current element is still there var isStillInDocument = ( e===document.documentElement || document.documentElement.contains(e) ); if(isStillInDocument) { // NEED_COMPARE: we will compare this element to the new list return true; } else { // DELETE: raise onremoved, pop old elements try { handler.onremoved && handler.onremoved(e); } catch(ex) { setImmediate(function() {throw ex})} return false; } }); // now pop and match until both lists are exhausted // (we use the fact the returned elements are in document order) var el1 = oldElms.pop(); var el2 = newElms.pop(); while(el1 || el2) { if(el1===el2) { // MATCH: pop both elements el1 = oldElms.pop(); el2 = newElms.pop(); } else if (el2 && /*el1 is after el2*/(!el1||(el2.compareDocumentPosition(el1) & (1|2|8|32))===0)) { // INSERT: raise onadded, pop new elements try { handler.onadded && handler.onadded(el2); } catch(ex) { setImmediate(function() {throw ex})} el2 = newElms.pop(); } else { // DELETE: raise onremoved, pop old elements try { handler.onremoved && handler.onremoved(el1); } catch(ex) { setImmediate(function() {throw ex})} el1 = oldElms.pop(); } } }; // use the event stream that best matches our needs var simpleSelector = selector.replace(/:(dir|lang|root|empty|blank|nth-child|nth-last-child|first-child|last-child|only-child|nth-of-type|nth-last-of-child|fist-of-type|last-of-type|only-of-type|not|matches|default)\b/gi,'') var eventStream; if(simpleSelector.indexOf(':') == -1) { // static stuff only eventStream = new DOMUpdateEventStream({target:root}); } else { // dynamic stuff too eventStream = new DOMUpdateEventStream({target:root}); if(DOMUpdateEventStream != AnimationFrameEventStream) { // detect the presence of focus-related pseudo-classes var reg = /:(focus|active)\b/gi; if(reg.test(simpleSelector)) { // mouse events should be listened eventStream = new CompositeEventStream( new FocusEventStream(), eventStream ); // simplify simpleSelector var reg = /:(focus)\b/gi; simpleSelector = simpleSelector.replace(reg, ''); // :active has other hooks } // detect the presence of mouse-button-related pseudo-classes var reg = /:(active)\b/gi; if(reg.test(simpleSelector)) { // mouse events should be listened eventStream = new CompositeEventStream( new MouseButtonEventStream(), eventStream ); // simplify simpleSelector simpleSelector = simpleSelector.replace(reg, ''); } // detect the presence of user input pseudo-classes var reg = /:(target|checked|indeterminate|valid|invalid|in-range|out-of-range|user-error)\b/gi; if(reg.test(simpleSelector)) { // slowly dynamic stuff do happen eventStream = new CompositeEventStream( new TimeoutEventStream(250), eventStream ); // simplify simpleSelector simpleSelector = simpleSelector.replace(reg, ''); var reg = /:(any-link|link|visited|local-link|enabled|disabled|read-only|read-write|required|optional)\b/gi; // simplify simpleSelector simpleSelector = simpleSelector.replace(reg, ''); } // detect the presence of nearly-static pseudo-classes var reg = /:(any-link|link|visited|local-link|enabled|disabled|read-only|read-write|required|optional)\b/gi; if(reg.test(simpleSelector)) { // nearly static stuff do happen eventStream = new CompositeEventStream( new TimeoutEventStream(333), eventStream ); // simplify simpleSelector simpleSelector = simpleSelector.replace(reg, ''); } // detect the presence of mouse-related pseudo-classes var reg = /:(hover)\b/gi; if(reg.test(simpleSelector)) { // mouse events should be listened eventStream = new CompositeEventStream( new MouseEventStream(), eventStream ); // simplify simpleSelector simpleSelector = simpleSelector.replace(reg, ''); } // detect the presence of unknown pseudo-classes if(simpleSelector.indexOf(':') !== -1) { // other stuff do happen, too (let's give up on events) eventStream = new AnimationFrameEventStream(); } } } // start handling changes loop(eventStream); } return querySelectorLive; })(window, document); require.define('src/core/dom-query-selector-live.js'); //////////////////////////////////////// // TODO: comment about the 'no_auto_stylesheet_detection' flag? module.exports = (function(window, document) { "use strict"; // import dependencies require('src/core/polyfill-dom-console.js'); require('src/core/polyfill-dom-requestAnimationFrame.js'); var cssSyntax = require('src/core/css-syntax.js'); var domEvents = require('src/core/dom-events.js'); var querySelectorLive = require('src/core/dom-query-selector-live.js'); // define the module var cssCascade = { // // returns the priority of a unique selector (NO COMMA!) // { the return value is an integer, with the same formula as webkit } // computeSelectorPriorityOf: function computeSelectorPriorityOf(selector) { if(typeof selector == "string") selector = cssSyntax.parse(selector.trim()+"{}").value[0].selector; var numberOfIDs = 0; var numberOfClasses = 0; var numberOfTags = 0; // TODO: improve this parser, or find one on the web for(var i = 0; i < selector.length; i++) { if(selector[i] instanceof cssSyntax.IdentifierToken) { numberOfTags++; } else if(selector[i] instanceof cssSyntax.DelimToken) { if(selector[i].value==".") { numberOfClasses++; i++; } } else if(selector[i] instanceof cssSyntax.ColonToken) { if(selector[++i] instanceof cssSyntax.ColonToken) { numberOfTags++; i++; } else if((selector[i] instanceof cssSyntax.Func) && (/^(not|matches)$/i).test(selector[i].name)) { var nestedPriority = this.computeSelectorPriorityOf(selector[i].value); numberOfTags += nestedPriority % 256; nestedPriority /= 256; numberOfClasses += nestedPriority % 256; nestedPriority /= 256; numberOfIDs += nestedPriority; } else { numberOfClasses++; } } else if(selector[i] instanceof cssSyntax.SimpleBlock) { if(selector[i].name=="[") { numberOfClasses++; } } else if(selector[i] instanceof cssSyntax.HashToken) { numberOfIDs++; } else { // TODO: stop ignoring unknown symbols? } } if(numberOfIDs>255) numberOfIds=255; if(numberOfClasses>255) numberOfClasses=255; if(numberOfTags>255) numberOfTags=255; return ((numberOfIDs*256)+numberOfClasses)*256+numberOfTags; }, // // returns an array of the css rules matching an element // findAllMatchingRules: function findAllMatchingRules(element) { return this.findAllMatchingRulesWithPseudo(element); }, // // returns an array of the css rules matching a pseudo-element // findAllMatchingRulesWithPseudo: function findAllMatchingRules(element,pseudo) { pseudo = pseudo ? (''+pseudo).toLowerCase() : pseudo; // let's look for new results if needed... var results = []; // walk the whole stylesheet... var visit = function(rules) { try { for(var r = rules.length; r--; ) { var rule = rules[r]; // media queries hook if(rule.disabled) continue; if(rule instanceof cssSyntax.StyleRule) { // consider each selector independently var subrules = rule.subRules || cssCascade.splitRule(rule); for(var sr = subrules.length; sr--; ) { var selector = subrules[sr].selector.toCSSString().replace(/ *(\/\*\*\/| ) */g,' ').trim(); if(pseudo) { // WE ONLY ACCEPT SELECTORS ENDING WITH THE PSEUDO var selectorLow = selector.toLowerCase(); var newLength = selector.length-pseudo.length-1; if(newLength<=0) continue; if(selectorLow.lastIndexOf('::'+pseudo)==newLength-1) { selector = selector.substr(0,newLength-1); } else if(selectorLow.lastIndexOf(':'+pseudo)==newLength) { selector = selector.substr(0,newLength); } else { continue; } // fix selectors like "#element > :first-child ~ ::before" if(selector.trim().length == 0) { selector = '*' } else if(selector[selector.length-1] == ' ') { selector += '*' } else if(selector[selector.length-1] == '+') { selector += '*' } else if(selector[selector.length-1] == '>') { selector += '*' } else if(selector[selector.length-1] == '~') { selector += '*' } } // look if the selector matches var isMatching = false; try { if(element.matches) isMatching=element.matches(selector) else if(element.matchesSelector) isMatching=element.matchesSelector(selector) else if(element.oMatchesSelector) isMatching=element.oMatchesSelector(selector) else if(element.msMatchesSelector) isMatching=element.msMatchesSelector(selector) else if(element.mozMatchesSelector) isMatching=element.mozMatchesSelector(selector) else if(element.webkitMatchesSelector) isMatching=element.webkitMatchesSelector(selector) else { throw new Error("no element.matches?") } } catch(ex) { /*debugger; setImmediate(function() { throw ex; })*/ } // if yes, add it to the list of matched selectors if(isMatching) { results.push(subrules[sr]); } } } else if(rule instanceof cssSyntax.AtRule && rule.name=="media") { // visit them visit(rule.toStylesheet().value); } } } catch (ex) { setImmediate(function() { throw ex; }); } } for(var s=cssCascade.stylesheets.length; s--; ) { var rules = cssCascade.stylesheets[s]; visit(rules); } return results; }, // // a list of all properties supported by the current browser // allCSSProperties: null, getAllCSSProperties: function getAllCSSProperties() { if(this.allCSSProperties) return this.allCSSProperties; // get all claimed properties var s = getComputedStyle(document.documentElement); var ps = new Array(s.length); for(var i=s.length; i--; ) { ps[i] = s[i]; } // FIX A BUG WHERE WEBKIT DOESN'T REPORT ALL PROPERTIES if(ps.indexOf('content')==-1) {ps.push('content');} if(ps.indexOf('counter-reset')==-1) { ps.push('counter-reset'); ps.push('counter-increment'); // FIX A BUG WHERE WEBKIT RETURNS SHIT FOR THE COMPUTED VALUE OF COUNTER-RESET cssCascade.computationUnsafeProperties['counter-reset']=true; } // save in a cache for faster access the next times return this.allCSSProperties = ps; }, // // those properties are not safe for computation->specified round-tripping // computationUnsafeProperties: { "bottom" : true, "direction" : true, "display" : true, "font-size" : true, "height" : true, "left" : true, "line-height" : true, "margin-left" : true, "margin-right" : true, "margin-bottom" : true, "margin-top" : true, "max-height" : true, "max-width" : true, "min-height" : true, "min-width" : true, "padding-left" : true, "padding-right" : true, "padding-bottom" : true, "padding-top" : true, "right" : true, "text-align" : true, "text-align-last" : true, "top" : true, "width" : true, __proto__ : null, }, // // a list of property we should inherit... // inheritingProperties: { "border-collapse" : true, "border-spacing" : true, "caption-side" : true, "color" : true, "cursor" : true, "direction" : true, "empty-cells" : true, "font-family" : true, "font-size" : true, "font-style" : true, "font-variant" : true, "font-weight" : true, "font" : true, "letter-spacing" : true, "line-height" : true, "list-style-image" : true, "list-style-position" : true, "list-style-type" : true, "list-style" : true, "orphans" : true, "quotes" : true, "text-align" : true, "text-indent" : true, "text-transform" : true, "visibility" : true, "white-space" : true, "widows" : true, "word-break" : true, "word-spacing" : true, "word-wrap" : true, __proto__ : null, }, // // returns the default style for a tag // defaultStylesForTag: Object.create ? Object.create(null) : {}, getDefaultStyleForTag: function getDefaultStyleForTag(tagName) { // get result from cache var result = this.defaultStylesForTag[tagName]; if(result) return result; // create dummy virtual element var element = document.createElement(tagName); var style = this.defaultStylesForTag[tagName] = getComputedStyle(element); if(style.display) return style; // webkit fix: insert the dummy element anywhere (head -> display:none) document.head.insertBefore(element, document.head.firstChild); return style; }, // // returns the specified style of an element. // REMARK: may or may not unwrap "inherit" and "initial" depending on implementation // REMARK: giving "matchedRules" as a parameter allow you to mutualize the "findAllMatching" rules calls // getSpecifiedStyle: function getSpecifiedStyle(element, cssPropertyName, matchedRules) { // hook for css regions var fragmentSource; if(fragmentSource=element.getAttribute('data-css-regions-fragment-of')) { fragmentSource = document.querySelector('[data-css-regions-fragment-source="'+fragmentSource+'"]'); if(fragmentSource) return cssCascade.getSpecifiedStyle(fragmentSource, cssPropertyName); } // give IE a thumbs up for this! if(element.currentStyle && !window.opera) { // ask IE to manage the style himself... var bestValue = element.myStyle[cssPropertyName] || element.currentStyle[cssPropertyName] || ''; // return a parsed representation of the value return cssSyntax.parseAListOfComponentValues(bestValue); } else { // TODO: support the "initial" and "inherit" things? // first, let's try inline style as it's fast and generally accurate // TODO: what if important rules override that? try { if(bestValue = element.style.getPropertyValue(cssPropertyName) || element.myStyle[cssPropertyName]) { return cssSyntax.parseAListOfComponentValues(bestValue); } } catch(ex) {} // find all relevant style rules var isBestImportant=false; var bestPriority = 0; var bestValue = new cssSyntax.TokenList(); var rules = matchedRules || ( cssPropertyName in cssCascade.monitoredProperties ? element.myMatchedRules || [] : cssCascade.findAllMatchingRules(element) ); var visit = function(rules) { for(var i=rules.length; i--; ) { // media queries hook if(rules[i].disabled) continue; // find a relevant declaration if(rules[i] instanceof cssSyntax.StyleRule) { var decls = rules[i].getDeclarations(); for(var j=decls.length-1; j>=0; j--) { if(decls[j].type=="DECLARATION") { if(decls[j].name==cssPropertyName) { // only works if selectors containing a "," are deduplicated var currentPriority = cssCascade.computeSelectorPriorityOf(rules[i].selector); if(isBestImportant) { // only an important declaration can beat another important declaration if(decls[j].important) { if(currentPriority >= bestPriority) { bestPriority = currentPriority; bestValue = decls[j].value; } } } else { // an important declaration beats any non-important declaration if(decls[j].important) { isBestImportant = true; bestPriority = currentPriority; bestValue = decls[j].value; } else { // the selector priority has to be higher otherwise if(currentPriority >= bestPriority) { bestPriority = currentPriority; bestValue = decls[j].value; } } } } } } } else if((rules[i] instanceof cssSyntax.AtRule) && (rules[i].name=="media")) { // visit them visit(rules[i].toStylesheet()) } } } visit(rules); // return our best guess... return bestValue||null; } }, // // start monitoring a new stylesheet // (should usually not be used because stylesheets load automatically) // stylesheets: [], loadStyleSheet: function loadStyleSheet(cssText,i) { // load in order // parse the stylesheet content var rules = cssSyntax.parse(cssText).value; // add the stylesheet into the object model if(typeof(i)!=="undefined") { cssCascade.stylesheets[i]=rules; } else { i=cssCascade.stylesheets.push(rules);} // make sure to monitor the required rules cssCascade.startMonitoringStylesheet(rules) }, // // start monitoring a new stylesheet // (should usually not be used because stylesheets load automatically) // loadStyleSheetTag: function loadStyleSheetTag(stylesheet,i) { if(stylesheet.hasAttribute('data-css-polyfilled')) { return; } if(stylesheet.tagName=='LINK') { // oh, no, we have to download it... try { // dummy value in-between cssCascade.stylesheets[i] = new cssSyntax.TokenList(); // var xhr = new XMLHttpRequest(); xhr.href = stylesheet.href; xhr.open('GET',stylesheet.href,true); xhr.ruleIndex = i; xhr.onreadystatechange = function() { if(this.readyState==4) { // status 0 is a webkit bug for local files if(this.status==200||this.status==0) { cssCascade.loadStyleSheet(this.responseText,this.ruleIndex) } else { cssConsole.log("css-cascade polyfill failled to load: " + this.href); } } }; xhr.send(); } catch(ex) { cssConsole.log("css-cascade polyfill failled to load: " + stylesheet.href); } } else { // oh, cool, we just have to parse the content! cssCascade.loadStyleSheet(stylesheet.textContent,i); } // mark the stylesheet as ok stylesheet.setAttribute('data-css-polyfilled',true); }, // // calling this function will load all currently existing stylesheets in the document // (should usually not be used because stylesheets load automatically) // selectorForStylesheets: "style:not([data-no-css-polyfill]):not([data-css-polyfilled]), link[rel=stylesheet]:not([data-no-css-polyfill]):not([data-css-polyfilled])", loadAllStyleSheets: function loadAllStyleSheets() { // for all stylesheets in the tag... var head = document.head || document.documentElement; var stylesheets = head.querySelectorAll(cssCascade.selectorForStylesheets); var intialLength = this.stylesheets.length; this.stylesheets.length += stylesheets.length // for all of them... for(var i = stylesheets.length; i--;) { // // load the stylesheet // var stylesheet = stylesheets[i]; cssCascade.loadStyleSheetTag(stylesheet,intialLength+i) } }, // // this is where we store event handlers for monitored properties // monitoredProperties: Object.create ? Object.create(null) : {}, monitoredPropertiesHandler: { onupdate: function(element, rule) { // we need to find all regexps that matches var mps = cssCascade.monitoredProperties; var decls = rule.getDeclarations(); for(var j=decls.length-1; j>=0; j--) { if(decls[j].type=="DECLARATION") { if(decls[j].name in mps) { // call all handlers waiting for this var hs = mps[decls[j].name]; for(var hi=hs.length; hi--;) { hs[hi].onupdate(element,rule); }; // don't call twice break; } } } } }, // // add an handler to some properties (aka fire when their value *MAY* be affected) // REMARK: because this event does not promise the value changed, you may want to figure it out before relayouting // startMonitoringProperties: function startMonitoringProperties(properties, handler) { for(var i=properties.length; i--; ) { var property = properties[i]; var handlers = ( cssCascade.monitoredProperties[property] || (cssCascade.monitoredProperties[property] = []) ); handlers.push(handler) } for(var s=0; s=0; j--) { if(decls[j].type=="DECLARATION") { if(decls[j].name in cssCascade.monitoredProperties) { // if we found some, start monitoring cssCascade.startMonitoringRule(rules[i]); break; } } } } else if(rules[i] instanceof cssSyntax.AtRule) { // handle @media if(rules[i].name == "media" && window.matchMedia) { cssCascade.startMonitoringMedia(rules[i]); } } } }, // // calling this function will detect media query updates and fire events accordingly // (should usually not be used because stylesheets load automatically) // startMonitoringMedia: function startMonitoringMedia(atrule) { try { var media = window.matchMedia(atrule.prelude.toCSSString()); // update all the rules when needed var rules = atrule.toStylesheet().value; cssCascade.updateMedia(rules, !media.matches, false); media.addListener( function(newMedia) { cssCascade.updateMedia(rules, !newMedia.matches, true); } ); // it seems I like taking risks... cssCascade.startMonitoringStylesheet(rules); } catch(ex) { setImmediate(function() { throw ex; }) } }, // // define what happens when a media query status changes // updateMedia: function(rules,disabled,update) { for(var i=rules.length; i--; ) { rules[i].disabled = disabled; // TODO: should probably get handled by a setter on the rule... var sr = rules[i].subRules; if(sr) { for(var j=sr.length; j--; ) { sr[j].disabled = disabled; } } } // in case of update, all elements matching the selector went potentially updated... if(update) { for(var i=rules.length; i--; ) { var els = document.querySelectorAll(rules[i].selector.toCSSString()); for(var j=els.length; j--; ) { cssCascade.monitoredPropertiesHandler.onupdate(els[j],rules[i]); } } } }, // // splits a rule if it has multiple selectors // splitRule: function splitRule(rule) { // create an array for all the subrules var rules = []; // fill the array var currentRule = new cssSyntax.StyleRule(); currentRule.disabled=rule.disabled; for(var i=0; i0 ? value.toCSSString() : ''; } }, set: function(v) { // check that the style is writable this.clip = (this.clip===undefined?'':this.clip); // check we know which element we work on try { if(!this.parentElement) throw new Error("Please use the anHTMLElement.myStyle property to set polyfilled properties") } catch(ex) { setImmediate(function() { throw ex; }); return; } // modify the local style of the element if(this.parentElement.getAttribute('data-style-'+cssPropertyName) != v) { this.parentElement.setAttribute('data-style-'+cssPropertyName,v); } } }; var styleProtos = []; try { styleProtos.push(Object.getPrototypeOf(document.documentElement.style) || CSSStyleDeclaration); } catch (ex) {} //try { styleProtos.push(Object.getPrototypeOf(getComputedStyle(document.documentElement))); } catch (ex) {} //try { styleProtos.push(Object.getPrototypeOf(document.documentElement.currentStyle)); } catch (ex) {} //try { styleProtos.push(Object.getPrototypeOf(document.documentElement.runtimeStyle)); } catch (ex) {} //try { styleProtos.push(Object.getPrototypeOf(document.documentElement.specifiedStyle)); } catch (ex) {} //try { styleProtos.push(Object.getPrototypeOf(document.documentElement.cascadedStyle)); } catch (ex) {} //try { styleProtos.push(Object.getPrototypeOf(document.documentElement.usedStyle)); } catch (ex) {} for(var i = styleProtos.length; i--;) { var styleProto = styleProtos[i]; Object.defineProperty(styleProto,cssPropertyName,prop); Object.defineProperty(styleProto,cssCascade.toCamelCase(cssPropertyName),prop); } cssCascade.startMonitoringRule(cssSyntax.parse('[style*="'+cssPropertyName+'"]{'+cssPropertyName+':attr(style)}').value[0]); cssCascade.startMonitoringRule(cssSyntax.parse('[data-style-'+cssPropertyName+']{'+cssPropertyName+':attr(style)}').value[0]); // add to the list of polyfilled properties... cssCascade.getAllCSSProperties().push(cssPropertyName); cssCascade.computationUnsafeProperties[cssPropertyName] = true; } }; // // polyfill for browsers not support CSSStyleDeclaration.parentElement (all of them right now) // domEvents.EventTarget.implementsIn(cssCascade); Object.defineProperty(Element.prototype,'myStyle',{ get: function() { var style = this.style; if(!style.parentElement) style.parentElement = this; return style; } }); // // load all stylesheets at the time the script is loaded // then do it again when all stylesheets are downloaded // and again if some style tag is added to the DOM // if(!("no_auto_stylesheet_detection" in window)) { cssCascade.loadAllStyleSheets(); document.addEventListener("DOMContentLoaded", function() { cssCascade.loadAllStyleSheets(); querySelectorLive( cssCascade.selectorForStylesheets, { onadded: function(e) { // TODO: respect DOM order? cssCascade.loadStyleSheetTag(e); cssCascade.dispatchEvent('stylesheetadded'); } } ) }) } return cssCascade; })(window, document); require.define('src/core/css-cascade.js'); //////////////////////////////////////// module.exports = (function(window, document) { "use strict"; var cssSyntax = require('src/core/css-syntax.js'); var cssCascade = require('src/core/css-cascade.js'); var cssBreak = { // // returns true if an element is replaced // (can't be broken because considered as an image in css layout) // isReplacedElement: function isReplacedElement(element) { if(!(element instanceof Element)) return false; var replacedElementTags = /^(SVG|MATH|IMG|VIDEO|PICTURE|OBJECT|EMBED|IFRAME|TEXTAREA|BUTTON|INPUT)$/; // TODO: more return replacedElementTags.test(element.tagName); }, // // returns true if an element has a scrollbar or act on overflowing content // isScrollable: function isScrollable(element, elementOverflow) { if(!(element instanceof Element)) return false; if(typeof(elementOverflow)=="undefined") elementOverflow = getComputedStyle(element).overflow; return ( elementOverflow !== "visible" && elementOverflow !== "hidden" ); }, // // returns true if the element is part of an inline flow // TextNodes definitely qualify, but also inline-block elements // isSingleLineOfTextComponent: function(element, elementStyle, elementDisplay, elementPosition, isReplaced) { if(!(element instanceof Element)) return true; if(typeof(elementStyle)=="undefined") elementStyle = getComputedStyle(element); if(typeof(elementDisplay)=="undefined") elementDisplay = elementStyle.display; if(typeof(elementPosition)=="undefined") elementPosition = elementStyle.position; if(typeof(isReplaced)=="undefined") isReplaced = this.isReplacedElement(element); return ( elementDisplay === "inline-block" || elementDisplay === "inline-table" || elementDisplay === "inline-flex" || elementDisplay === "inline-grid" // TODO: more ) && ( elementPosition === "static" || elementPosition === "relative" ); }, // // returns true if the element is part of an inline flow // TextNodes definitely qualify, but also inline-block elements // hasAnyInlineFlow: function(element) { function countAsInline(element) { if(!(element instanceof Element)) return !(/^\s*$/.test(element.nodeValue)); return !cssBreak.isOutOfFlowElement(element) && cssBreak.isSingleLineOfTextComponent(element); } // try to find any inline element var current = element.firstChild; while(current) { if(countAsInline(current)) return true; current = current.nextSibling; } // no inline element return false; }, // // returns true if the element breaks the inline flow // (the case of block elements, mostly) // isLineBreakingElement: function(element, elementStyle, elementDisplay, elementPosition) { if(!(element instanceof Element)) return false; if(typeof(elementStyle)=="undefined") elementStyle = getComputedStyle(element); if(typeof(elementDisplay)=="undefined") elementDisplay = elementStyle.display; if(typeof(elementPosition)=="undefined") elementPosition = elementStyle.position; return ( ( // in-flow bock elements (elementDisplay === "block") && !this.isOutOfFlowElement(element, elementStyle, elementDisplay, elementPosition) ) || ( // displayed
elements element.tagName==="BR" && elementDisplay!=="none" ) ); }, // // returns true if the element breaks the inline flow before him // (the case of block elements, mostly) // isLinePreBreakingElement: function(element, elementStyle, elementDisplay, elementPosition) { if(!(element instanceof Element)) return false; var breakBefore = cssCascade.getSpecifiedStyle(element,'break-before').toCSSString(); return ( (breakBefore=="region"||breakBefore=="all") || cssBreak.isLineBreakingElement(element, elementStyle, elementDisplay, elementPosition) ); }, // // returns true if the element breaks the inline flow after him // (the case of block elements, mostly) // isLinePostBreakingElement: function(element, elementStyle, elementDisplay, elementPosition) { if(!(element instanceof Element)) return false; var breakAfter = cssCascade.getSpecifiedStyle(element,'break-after').toCSSString(); return ( (breakAfter=="region"||breakAfter=="all") || cssBreak.isLineBreakingElement(element, elementStyle, elementDisplay, elementPosition) ); }, // // returns true if the element is outside any block/inline flow // (this the case of absolutely positioned elements, and floats) // isOutOfFlowElement: function(element, elementStyle, elementDisplay, elementPosition, elementFloat) { if(!(element instanceof Element)) return false; if(typeof(elementStyle)=="undefined") elementStyle = getComputedStyle(element); if(typeof(elementDisplay)=="undefined") elementDisplay = elementStyle.display; if(typeof(elementPosition)=="undefined") elementPosition = elementStyle.position; if(typeof(elementFloat)=="undefined") elementFloat = elementStyle.float || elementStyle.styleFloat || elementStyle.cssFloat; return ( // positioned elements are out of the flow (elementPosition==="absolute"||elementPosition==="fixed") // floated elements as well || (elementFloat!=="none") // not sure but let's say hidden elements as well || (elementDisplay==="none") ); }, // // returns true if two sibling elements are in the same text line // (this function is not perfect, work with it with care) // areInSameSingleLine: function areInSameSingleLine(element1, element2) { // // look for obvious reasons why it wouldn't be the case // // if the element are not direct sibling, we must use their inner siblings as well if(element1.nextSibling != element2) { if(element2.nextSibling != element1) throw "I gave up!"; var t = element1; element1=element2; element2=t; } // a block element is never on the same line as another element if(this.isLinePostBreakingElement(element1)) return false; if(this.isLinePreBreakingElement(element2)) return false; // if the previous element is out of flow, we may consider it as being part of the current line if(this.isOutOfFlowElement(element1)) return true; // if the current object is not a single line component, return false if(!this.isSingleLineOfTextComponent(element1)) return false; // // compute the in-flow bounding rect of the two elements // var element1box = Node.getBoundingClientRect(element1); var element2box = Node.getBoundingClientRect(element2); function shift(box,shiftX,shiftY) { return { top: box.top+shiftY, bottom: box.bottom+shiftY, left: box.left+shiftX, right: box.right+shiftX } } // we only need to shift elements if(element1 instanceof Element) { var element1Style = getComputedStyle(element1); element1box = shift(element1box, parseFloat(element1Style.marginLeft), parseFloat(element1Style.marginTop)) if(element1Style.position=="relative") { element1box = shift(element1box, parseFloat(element1Style.left), parseFloat(element1Style.top)) } } // we only need to shift elements if(element2 instanceof Element) { var element2Style = getComputedStyle(element2); element2box = shift(element2box, parseFloat(element2Style.marginLeft), parseFloat(element2Style.marginTop)) if(element2Style.position=="relative") { element2box = shift(element2box, parseFloat(element2Style.left), parseFloat(element2Style.top)) } } // order the nodes so that they are in left-to-right order // (this means invert their order in the case of right-to-left flow) var firstElement = getComputedStyle(element1.parentNode).direction=="rtl" ? element2box : element1box; var secondElement = getComputedStyle(element1.parentNode).direction=="rtl" ? element1box : element2box; // return true if both elements are have non-overlapping // margin- and position-corrected in-flow bounding rect // and if their relative position is the one of the current // flow (either rtl or ltr) return firstElement.right <= secondElement.left; // TODO: what about left-to-right + right-aligned text? // I should probably takes care of vertical position in this case to solve ambiguities }, // // returns true if the element has "overflow: hidden" set on it, and actually overflows // isHiddenOverflowing: function isHiddenOverflowing(element, elementOverflow) { if(!(element instanceof Element)) return false; if(typeof(elementOverflow)=="undefined") elementOverflow = getComputedStyle(element).display; return ( elementOverflow == "hidden" && element.offsetHeight != element.scrollHeight // trust me that works ); }, // // returns true if the element has a border-radius that impacts his layout // hasBigRadius: function(element, elementStyle) { if(!(element instanceof Element)) return false; if(typeof(elementStyle)=="undefined") elementStyle = getComputedStyle(element); // if the browser supports radiuses {f### prefixes} if("borderTopLeftRadius" in elementStyle) { var tlRadius = parseFloat(elementStyle.borderTopLeftRadius); var trRadius = parseFloat(elementStyle.borderTopRightRadius); var blRadius = parseFloat(elementStyle.borderBottomLeftRadius); var brRadius = parseFloat(elementStyle.borderBottomRightRadius); // tiny radiuses (<15px) are tolerated anyway if(tlRadius < 15 && trRadius < 15 && blRadius < 15 && brRadius < 15) { return false; } var tWidth = parseFloat(elementStyle.borderTopWidth); var bWidth = parseFloat(elementStyle.borderBottomWidth); var lWidth = parseFloat(elementStyle.borderLeftWidth); var rWidth = parseFloat(elementStyle.borderRightWidth); // make sure the radius itself is contained into the border if(tlRadius > tWidth) return true; if(tlRadius > lWidth) return true; if(trRadius > tWidth) return true; if(trRadius > rWidth) return true; if(blRadius > bWidth) return true; if(blRadius > lWidth) return true; if(brRadius > bWidth) return true; if(brRadius > rWidth) return true; } // all conditions were met return false; }, // // return trus if the break-inside property is 'avoid' or 'avoid-region' // isBreakInsideAvoid: function isBreakInsideAvoid(element, elementStyle) { var breakInside = cssCascade.getSpecifiedStyle(element, 'break-inside', undefined, true).toCSSString().trim().toLowerCase(); return (breakInside == "avoid" || breakInside == "avoid-region"); }, // // returns true if the element is unbreakable according to the spec // (and some of the expected limitations of HTML/CSS) // isMonolithic: function isMonolithic(element) { if(!(element instanceof Element)) return false; var elementStyle = getComputedStyle(element); var elementOverflow = elementStyle.overflow; var elementDisplay = elementStyle.display; // Some content is not fragmentable, for example: // - many types of replaced elements (such as images or video) var isReplaced = this.isReplacedElement(element); // - scrollable elements var isScrollable = this.isScrollable(element, elementOverflow); // - a single line of text content. var isSingleLineOfText = this.isSingleLineOfTextComponent(element, elementStyle, elementDisplay, undefined, isReplaced); // Such content is considered monolithic: it contains no // possible break points. // In addition to any content which is not fragmentable, // UAs may consider as monolithic: // - any elements with ‘overflow’ set to ‘auto’ or ‘scroll’ // - any elements with ‘overflow: hidden’ and a non-‘auto’ logical height (and no specified maximum logical height). var isHiddenOverflowing = this.isHiddenOverflowing(element, elementOverflow); // ADDITION TO THE SPEC: // I don't want to handle the case where // an element has a border-radius that is bigger // than the border-width to which it belongs var hasBigRadius = this.hasBigRadius(element, elementStyle); // ADDITION TO THE SPEC: // Someone proposed to support "break-inside: avoid" here var isBreakInsideAvoid = this.isBreakInsideAvoid(element, elementStyle); // all of them are monolithic return isReplaced || isScrollable || isSingleLineOfText || isHiddenOverflowing || hasBigRadius || isBreakInsideAvoid; }, // // returns true if "r" is a collapsed range located at a possible break point for "region" // (this function does all the magic for you, but you may want to avoid using it too much) // isPossibleBreakPoint: function isPossibleBreakPoint(r, region) { // r has to be a range, and be collapsed if(!(r instanceof Range)) return false; if(!(r.collapsed)) return false; // no ancestor up to the region has to be monolithic var ancestor = r.startContainer; while(ancestor && ancestor !== region) { if(cssBreak.isMonolithic(ancestor)) { return false; } ancestor = ancestor.parentNode; } // we also have to check that we're not between two single-line-of-text elements // that are actually on the same line (in which case you can't break) var ancestor = r.startContainer; var lastAncestor = r.startContainer.childNodes[r.startOffset]; while(ancestor && lastAncestor !== region) { if(lastAncestor && lastAncestor.previousSibling) { if(this.areInSameSingleLine(lastAncestor, lastAncestor.previousSibling)) { return false; } } lastAncestor = ancestor; ancestor = ancestor.parentNode; } // there are some very specific conditions for breaking // at the edge of an element: if(r.startOffset==0) { // Class 3 breaking point: // ======================== // Between the content edge of a block container box // and the outer edges of its child content (margin // edges of block-level children or line box edges // for inline-level children) if there is a (non-zero) // gap between them. var firstChild = r.startContainer.childNodes[0]; if(firstChild) { var firstChildBox = ( Node.getBoundingClientRect(firstChild) ); var parentBox = ( r.startContainer.getBoundingClientRect() ); if(firstChildBox.top == parentBox.top) { return false; } } else { return false; } } // all conditions are met! return true; } }; return cssBreak; })(window, document); require.define('src/core/css-break.js'); //////////////////////////////////////// "use strict"; // // start by polyfilling caretRangeFromPoint // if(!document.caretRangeFromPoint) { if (document.caretPositionFromPoint) { document.caretRangeFromPoint = function caretRangeFromPoint(x,y) { var r = document.createRange(); var p = document.caretPositionFromPoint(x,y); if(p.offsetNode) { r.setStart(p.offsetNode, p.offset); r.setEnd(p.offsetNode, p.offset); } return r; } } else if((document.body||document.createElement('body')).createTextRange) { // // we may want to convert TextRange to Range // var TextRangeUtils = { convertToDOMRange: function (textRange, document) { var adoptBoundary = function(domRange, textRangeInner, bStart) { // iterate backwards through parent element to find anchor location var cursorNode = document.createElement('a'), cursor = textRangeInner.duplicate(); cursor.collapse(bStart); var parent = cursor.parentElement(); do { parent.insertBefore(cursorNode, cursorNode.previousSibling); cursor.moveToElementText(cursorNode); } while (cursor.compareEndPoints(bStart ? 'StartToStart' : 'StartToEnd', textRangeInner) > 0 && cursorNode.previousSibling); // when we exceed or meet the cursor, we've found the node if (cursor.compareEndPoints(bStart ? 'StartToStart' : 'StartToEnd', textRangeInner) == -1 && cursorNode.nextSibling) { // data node cursor.setEndPoint(bStart ? 'EndToStart' : 'EndToEnd', textRangeInner); domRange[bStart ? 'setStart' : 'setEnd'](cursorNode.nextSibling, cursor.text.length); } else { // element domRange[bStart ? 'setStartBefore' : 'setEndBefore'](cursorNode); } cursorNode.parentNode.removeChild(cursorNode); } // validate arguments if(!document) { document=window.document; } // return a DOM range var domRange = document.createRange(); adoptBoundary(domRange, textRange, true); adoptBoundary(domRange, textRange, false); return domRange; }, convertFromDOMRange: function (domRange) { var adoptEndPoint = function(textRange, domRangeInner, bStart) { // find anchor node and offset var container = domRangeInner[bStart ? 'startContainer' : 'endContainer']; var offset = domRangeInner[bStart ? 'startOffset' : 'endOffset'], textOffset = 0; var anchorNode = DOMUtils.isDataNode(container) ? container : container.childNodes[offset]; var anchorParent = DOMUtils.isDataNode(container) ? container.parentNode : container; // visible data nodes need a text offset if (container.nodeType == 3 || container.nodeType == 4) textOffset = offset; // create a cursor element node to position range (since we can't select text nodes) var cursorNode = domRangeInner._document.createElement('a'); anchorParent.insertBefore(cursorNode, anchorNode); var cursor = domRangeInner._document.body.createTextRange(); cursor.moveToElementText(cursorNode); cursorNode.parentNode.removeChild(cursorNode); // move range textRange.setEndPoint(bStart ? 'StartToStart' : 'EndToStart', cursor); textRange[bStart ? 'moveStart' : 'moveEnd']('character', textOffset); } // return an IE text range var textRange = domRange._document.body.createTextRange(); adoptEndPoint(textRange, domRange, true); adoptEndPoint(textRange, domRange, false); return textRange; } }; document.caretRangeFromPoint = function caretRangeFromPoint(x,y) { // the accepted number of vertical backtracking, in CSS pixels var IYDepth = 40; // try to create a text range at the specified location var r = document.body.createTextRange(); for(var iy=IYDepth; iy; iy=iy-4) { var ix = x; if(true) { try { r.moveToPoint(ix,iy+y-IYDepth); return TextRangeUtils.convertToDOMRange(r); } catch(ex) {} } } // if that fails, return the location just after the element located there try { var elem = document.elementFromPoint(x-1,y-1); var r = document.createRange(); r.setStartAfter(elem); return r; } catch(ex) { return null; } } } } /// /// helper function for moving ranges char by char /// Range.prototype.myMoveOneCharLeft = function() { var r = this; // move to the previous cursor location if(r.endOffset > 0) { // if we can enter into the previous sibling var previousSibling = r.endContainer.childNodes[r.endOffset-1]; if(previousSibling && previousSibling.lastChild) { // enter the previous sibling from its end r.setEndAfter(previousSibling.lastChild); } else if(previousSibling && previousSibling.nodeType==previousSibling.TEXT_NODE) { // todo: lookup value // enter the previous text node from its end r.setEnd(previousSibling, previousSibling.nodeValue.length); } else { // else move before that element r.setEnd(r.endContainer, r.endOffset-1); } } else { r.setEndBefore(r.endContainer); } } Range.prototype.myMoveOneCharRight = function() { var r = this; // move to the previous cursor location var max = (r.startContainer.nodeType==r.startContainer.TEXT_NODE ? r.startContainer.nodeValue.length : r.startContainer.childNodes.length) if(r.startOffset < max) { // if we can enter into the next sibling var nextSibling = r.endContainer.childNodes[r.endOffset]; if(nextSibling && nextSibling.firstChild) { // enter the next sibling from its start r.setStartBefore(nextSibling.firstChild); } else if(nextSibling && nextSibling.nodeType==nextSibling.TEXT_NODE && nextSibling.nodeValue!='') { // todo: lookup value // enter the next text node from its start r.setStart(nextSibling, 0); } else { // else move before that element r.setStart(r.startContainer, r.startOffset+1); } } else { r.setStartAfter(r.endContainer); } // shouldn't be needed but who knows... r.setEnd(r.startContainer, r.startOffset); } /// /// This functions is optimized to not yield inside a word in a text node /// Range.prototype.myMoveTowardRight = function() { var r = this; // move to the previous cursor location var isTextNode = r.startContainer.nodeType==r.startContainer.TEXT_NODE; var max = (isTextNode ? r.startContainer.nodeValue.length : r.startContainer.childNodes.length) if(r.startOffset < max) { // if we can enter into the next sibling var nextSibling = r.endContainer.childNodes[r.endOffset]; if(nextSibling && nextSibling.firstChild) { // enter the next sibling from its start r.setStartBefore(nextSibling.firstChild); } else if(nextSibling && nextSibling.nodeType==nextSibling.TEXT_NODE && nextSibling.nodeValue!='') { // todo: lookup value // enter the next text node from its start r.setStart(nextSibling, 0); } else if(isTextNode) { // move to the next non a-zA-Z symbol var currentText = r.startContainer.nodeValue; var currentOffset = r.startOffset; var currentLetter = currentText[currentOffset++]; while(currentOffset < max && /^\w$/.test(currentLetter)) { currentLetter = currentText[currentOffset++]; } r.setStart(r.startContainer, currentOffset); } else { // else move after that element r.setStart(r.startContainer, r.startOffset+1); } } else { r.setStartAfter(r.endContainer); } // shouldn't be needed but who knows... r.setEnd(r.startContainer, r.startOffset); } Range.prototype.myMoveEndOneCharLeft = function() { var r = this; // move to the previous cursor location if(r.endOffset > 0) { // if we can enter into the previous sibling var previousSibling = r.endContainer.childNodes[r.endOffset-1]; if(previousSibling && previousSibling.lastChild) { // enter the previous sibling from its end r.setEndAfter(previousSibling.lastChild); } else if(previousSibling && previousSibling.nodeType==previousSibling.TEXT_NODE) { // todo: lookup value // enter the previous text node from its end r.setEnd(previousSibling, previousSibling.nodeValue.length); } else { // else move before that element r.setEnd(r.endContainer, r.endOffset-1); } } else { r.setEndBefore(r.endContainer); } } Range.prototype.myMoveEndOneCharRight = function() { var r = this; // move to the previous cursor location var max = (r.endContainer.nodeType==r.endContainer.TEXT_NODE ? r.endContainer.nodeValue.length : r.endContainer.childNodes.length) if(r.endOffset < max) { // if we can enter into the next sibling var nextSibling = r.endContainer.childNodes[r.endOffset]; if(nextSibling && nextSibling.firstChild) { // enter the next sibling from its start r.setEndBefore(nextSibling.firstChild); } else if(nextSibling && nextSibling.nodeType==nextSibling.TEXT_NODE) { // todo: lookup value // enter the next text node from its start r.setEnd(nextSibling, 0); } else { // else move before that element r.setEnd(r.endContainer, r.endOffset+1); } } else { r.setEndAfter(r.endContainer); } } // // Get the *real* bounding client rect of the range // { therefore we need to fix some browser bugs... } // Range.prototype.myGetSelectionRect = function() { // get the browser's claimed rect var rect = this.getBoundingClientRect(); // HACK FOR ANDROID BROWSER AND OLD WEBKIT if(!rect) { rect={top:0,right:0,bottom:0,left:0,width:0,height:0}; } // if the value seems wrong... (some browsers don't like collapsed selections) if(this.collapsed && rect.top===0 && rect.bottom===0) { // select one char and infer location var clone = this.cloneRange(); var collapseToLeft=false; clone.collapse(false); // the case where no char before is tricky... if(clone.startOffset==0) { // let's move on char to the right clone.myMoveTowardRight(); collapseToLeft=true; // note: some browsers don't like selections // that spans multiple containers, so we will // iterate this process until we have one true // char selected clone.setStart(clone.endContainer, 0); } else { // else, just select the char before clone.setStart(this.startContainer, this.startOffset-1); collapseToLeft=false; } // get some real rect var rect = clone.myGetSelectionRect(); // compute final value if(collapseToLeft) { return { left: rect.left, right: rect.left, width: 0, top: rect.top, bottom: rect.bottom, height: rect.height } } else { return { left: rect.right, right: rect.right, width: 0, top: rect.top, bottom: rect.bottom, height: rect.height } } } else { return rect; } } // not sure it's needed but still if(!window.Element) window.Element=window.HTMLElement; if(!window.Node) window.Node = {}; // make getBCR working on text nodes & stuff Node.getBoundingClientRect = function getBoundingClientRect(element) { if (element.getBoundingClientRect) { var rect = element.getBoundingClientRect(); } else { var range = document.createRange(); range.selectNode(element); var rect = range.getBoundingClientRect(); } // HACK FOR ANDROID BROWSER AND OLD WEBKIT if(!rect) { rect={top:0,right:0,bottom:0,left:0,width:0,height:0}; } return rect; }; // FIXME: Bug with chromium recent version when we reload the page with scroll // make getCR working on text nodes & stuff Node.getClientRects = function getClientRects(firstChild) { if (firstChild.getBoundingClientRect) { return firstChild.getClientRects(); } else { var range = document.createRange(); range.selectNode(firstChild); return range.getClientRects(); } }; // fix for IE (contains fails for text nodes...) Node.contains = function contains(parentNode,node) { if(node.nodeType != 1) { if(!node.parentNode) return false; return node.parentNode==parentNode || parentNode.contains(node.parentNode); } else { return parentNode.contains(node); } } // // get the bounding rect of the selection, including the bottom padding/marging of the previous element if required // { this is a special version for breaking algorithms that do not want to miss the previous element real size } // Range.prototype.myGetExtensionRect = function() { // this function returns the selection rect // but does take care of taking in account // the bottom-{padding/border} of the previous // sibling element, to detect overflow points // more accurately var rect = this.myGetSelectionRect(); var previousSibling = this.endContainer.childNodes[this.endOffset-1]; if(previousSibling) { // correct with the new take var prevSibRect = Node.getBoundingClientRect(previousSibling); var adjustedBottom = Math.max(rect.bottom,prevSibRect.bottom); if(adjustedBottom == rect.bottom) return rect; return { left: rect.left, right: rect.right, width: rect.width, top: rect.top, bottom: adjustedBottom, height: adjustedBottom - rect.top }; } else if(rect.bottom==0 && this.endContainer.nodeType === 3) { // note that if we are in a text node, // we may want to cover all the previous // text in the node to avoid whitespace // related bugs var onlyWhiteSpaceBefore = /^(\s|\n)*$/.test(this.endContainer.nodeValue.substr(0,this.endOffset)); if(onlyWhiteSpaceBefore) { // if we are in the fucking whitespace land, return first line var prevSibRect = Node.getClientRects(this.endContainer)[0]; // FIXME: temporary fix the chromium bug //return prevSibRect; return rect; } else { // otherwhise, let's rely on previous chars var auxiliaryRange = this.cloneRange(); auxiliaryRange.setStart(this.endContainer,0); // correct with the new take var prevSibRect = auxiliaryRange.getBoundingClientRect(); var adjustedBottom = Math.max(rect.bottom,prevSibRect.bottom); return { left: rect.left, right: rect.right, width: rect.width, top: rect.top, bottom: adjustedBottom, height: adjustedBottom - rect.top }; } } else { return rect; } } require.define('src/css-regions/lib/range-extensions.js'); //////////////////////////////////////// // // this module holds the big-picture actions of the polyfill // module.exports = (function(window, document) { "use strict"; var domEvents = require('src/core/dom-events.js'); var cssSyntax = require('src/core/css-syntax.js'); var cssCascade = require('src/core/css-cascade.js'); var cssBreak = require('src/core/css-break.js'); var cssRegionsHelpers = window.cssRegionsHelpers = { // // returns the previous sibling of the element // or the previous sibling of its nearest ancestor that has one // getAllLevelPreviousSibling: function(e, region) { if(!e || e==region) return null; // find the nearest ancestor that has a previous sibling while(!e.previousSibling) { // but bubble to the next avail ancestor e = e.parentNode; // dont get over the bar if(!e || e==region) return null; } // return that sibling return e.previousSibling; }, // // prepares the element to become a css region // markNodesAsRegion: function(nodes,fast) { nodes.forEach(function(node) { node.regionOverset = 'empty'; node.setAttribute('data-css-region',node.cssRegionsLastFlowFromName); cssRegionsHelpers.hideTextNodesFromFragmentSource([node]); node.cssRegionsWrapper = node.cssRegionsWrapper || node.appendChild(document.createElement("cssregion")); }); }, // // prepares the element to return to its normal css life // unmarkNodesAsRegion: function(nodes,fast) { nodes.forEach(function(node) { // restore regionOverset to its natural value node.regionOverset = 'fit'; // remove the current tag try { node.cssRegionsWrapper && node.removeChild(node.cssRegionsWrapper); } catch(ex) { setImmediate(function() { throw ex })}; node.cssRegionsWrapper = undefined; delete node.cssRegionsWrapper; // restore top-level texts that may have been hidden cssRegionsHelpers.unhideTextNodesFromFragmentSource([node]); // unmark as a region node.removeAttribute('data-css-region'); }); }, // // prepares the element for cloning (mainly give them an ID) // fragmentSourceIndex: 0, markNodesAsFragmentSource: function(nodes,ignoreRoot) { function visit(node,k) { var child, next; switch (node.nodeType) { case 1: // Element node if(typeof(k)=="undefined" || !ignoreRoot) { // mark as fragment source var id = node.getAttributeNode('data-css-regions-fragment-source'); if(!id) { node.setAttribute('data-css-regions-fragment-source', cssRegionsHelpers.fragmentSourceIndex++); } } node.setAttribute('data-css-regions-cloning', true); // expand list values if(node.tagName=='OL') cssRegionsHelpers.expandListValues(node); if(typeof(k)!="undefined" && node.tagName=="LI") cssRegionsHelpers.expandListValues(node.parentNode); case 9: // Document node case 11: // Document fragment node child = node.firstChild; while (child) { next = child.nextSibling; visit(child); child = next; } break; } } nodes.forEach(visit); }, // // computes the "value" attribute of every LI element out there // expandListValues: function(OL) { if(OL.getAttribute("data-css-li-value-expanded")) return; OL.setAttribute('data-css-li-value-expanded', true); if(OL.hasAttribute("reversed")) { var currentValue = OL.getAttribute("start") ? parseInt(OL.getAttribute("start")) : OL.childElementCount; var increment = -1; } else { var currentValue = OL.getAttribute("start") ? parseInt(OL.getAttribute("start")) : 1; var increment = +1; } var LI = OL.firstElementChild; var LIV = null; while(LI) { if(LI.tagName==="LI") { if(LIV=LI.getAttributeNode("value")) { currentValue = parseInt(LIV.nodeValue); LI.setAttribute('data-css-old-value', currentValue) } else { LI.setAttribute("value", currentValue); } currentValue = currentValue + increment; } LI = LI.nextElementSibling; } }, // // reverts to automatic computation of the value of LI elements // unexpandListValues: function(OL) { if(!OL.hasAttribute('data-css-li-value-expanded')) return; OL.removeAttribute('data-css-li-value-expanded') var LI = OL.firstElementChild; var LIV = null; while(LI) { if(LI.tagName==="LI") { if(LIV=LI.getAttributeNode("data-css-old-value")) { LI.removeAttributeNode(LIV); } else { LI.removeAttribute('value'); } } LI = LI.nextElementSibling; } }, // // makes empty text nodes which cannot get "display: none" applied to them // listOfTextNodesForIE: [], hideTextNodesFromFragmentSource: function(nodes) { function visit(node,k) { var child, next; switch (node.nodeType) { case 3: // Text node if(!node.parentNode.getAttribute('data-css-regions-fragment-source')) { // we have to remove their content the hard way... node.cssRegionsSavedNodeValue = node.nodeValue; node.nodeValue = ""; // HACK: OTHERWISE IE WILL GC THE TEXTNODE AND RETURNS YOU // A FRESH TEXTNODE THE NEXT TIME WHERE YOUR EXPANDO // IS NOWHERE TO BE SEEN! if(navigator.userAgent.indexOf('MSIE')>0 || navigator.userAgent.indexOf("Trident")>0) { if(cssRegionsHelpers.listOfTextNodesForIE.indexOf(node)==-1) { cssRegionsHelpers.listOfTextNodesForIE.push(node); } } } break; case 1: // Element node if(node.hasAttribute('data-css-regions-cloning')) { node.removeAttribute('data-css-regions-cloning'); node.setAttribute('data-css-regions-cloned', true); if(node.currentStyle) node.currentStyle.display.toString(); // IEFIX FOR BAD STYLE RECALC } if(typeof(k)=="undefined") return; case 9: // Document node case 11: // Document fragment node child = node.firstChild; while (child) { next = child.nextSibling; visit(child); child = next; } break; } } nodes.forEach(visit); }, // // makes emptied text nodes visible again // unhideTextNodesFromFragmentSource: function(nodes) { function visit(node) { var child, next; switch (node.nodeType) { case 3: // Text node // we have to remove their content the hard way... if("cssRegionsSavedNodeValue" in node) { node.nodeValue = node.cssRegionsSavedNodeValue; delete node.cssRegionsSavedNodeValue; } break; case 1: // Element node if(typeof(k)=="undefined") return; case 9: // Document node case 11: // Document fragment node child = node.firstChild; while (child) { next = child.nextSibling; visit(child); child = next; } break; } } nodes.forEach(visit); }, // // prepares the content elements to return to ther normal css life // unmarkNodesAsFragmentSource: function(nodes) { function visit(node,k) { var child, next; switch (node.nodeType) { case 3: // Text node // we have to reinstall their content the hard way... if("cssRegionsSavedNodeValue" in node) { node.nodeValue = node.cssRegionsSavedNodeValue; delete node.cssRegionsSavedNodeValue; } break; case 1: // Element node node.removeAttribute('data-css-regions-cloned'); node.removeAttribute('data-css-regions-fragment-source'); if(node.currentStyle) node.currentStyle.display.toString(); // IEFIX FOR BAD STYLE RECALC if(node.tagName=="OL") cssRegionsHelpers.unexpandListValues(node); if(typeof(k)!="undefined" && node.tagName=="LI") cssRegionsHelpers.unexpandListValues(node.parentNode); case 9: // Document node case 11: // Document fragment node child = node.firstChild; while (child) { next = child.nextSibling; visit(child); child = next; } break; } } nodes.forEach(visit); }, // // marks cloned content as fragment instead of as fragment source (basically) // transformFragmentSourceToFragments: function(nodes) { function visit(node) { var child, next; switch (node.nodeType) { case 1: // Element node var id = node.getAttribute('data-css-regions-fragment-source'); node.removeAttribute('data-css-regions-fragment-source'); node.removeAttribute('data-css-regions-cloning'); node.removeAttribute('data-css-regions-cloned'); node.setAttribute('data-css-regions-fragment-of', id); if(node.id) node.id += "--fragment"; case 9: // Document node case 11: // Document fragment node child = node.firstChild; while (child) { next = child.nextSibling; visit(child); child = next; } break; } } nodes.forEach(visit); }, // // removes some invisible text nodes from the tree // (useful if you don't want to face browser bugs when dealing with them) // embedTrailingWhiteSpaceNodes: function(fragment) { var onlyWhiteSpace = /^\s*$/; function visit(node) { var child, next; switch (node.nodeType) { case 3: // Text node // we only remove nodes at the edges if (!node.previousSibling) { // we only remove nodes if their parent doesn't preserve whitespace if (getComputedStyle(node.parentNode).whiteSpace.substring(0,3)!=="pre") { // only remove pure whitespace nodes if (onlyWhiteSpace.test(node.nodeValue)) { node.parentNode.setAttribute('data-whitespace-before',node.nodeValue); node.parentNode.removeChild(node); } } break; } // we only remove nodes at the edges if (!node.nextSibling) { // we only remove nodes if their parent doesn't preserve whitespace if (getComputedStyle(node.parentNode).whiteSpace.substring(0,3)!=="pre") { // only remove pure whitespace nodes if (onlyWhiteSpace.test(node.nodeValue)) { node.parentNode.setAttribute('data-whitespace-after',node.nodeValue); node.parentNode.removeChild(node); } } break; } break; case 1: // Element node case 9: // Document node case 11: // Document fragment node child = node.firstChild; while (child) { next = child.nextSibling; visit(child); child = next; } break; } } visit(fragment); }, // // recover the previously removed invisible text nodes // unembedTrailingWhiteSpaceNodes: function(fragment) { var onlyWhiteSpace = /^\s*$/; function visit(node) { var child, next; switch (node.nodeType) { case 1: // Element node var txt = ""; if(txt = node.getAttribute('data-whitespace-before')) { if(node.getAttribute('data-starting-fragment')=='' && node.getAttribute('data-special-starting-fragment','')) { node.insertBefore(document.createTextNode(txt),node.firstChild); } } node.removeAttribute('data-whitespace-before') if(txt = node.getAttribute('data-whitespace-after')) { if(node.getAttribute('data-continued-fragment')=='' && node.getAttribute('data-special-continued-fragment','')) { node.insertAfter(document.createTextNode(txt),node.lastChild); } } node.removeAttribute('data-whitespace-after') case 9: // Document node case 11: // Document fragment node child = node.firstChild; while (child) { next = child.nextSibling; visit(child); child = next; } break; } } visit(fragment); }, /// /// walk the two trees the same way, and copy all the styles /// BEWARE: if the DOMs are different, funny things will happen /// NOTE: this function will also remove elements put in another flow /// copyStyle: function(root1, root2) { function visit(node1, node2, isRoot) { var child1, next1, child2, next2; switch (node1.nodeType) { case 1: // Element node // firstly, setup a cache of all css properties on the element var matchedRules = (node1.currentStyle && !window.opera) ? undefined : cssCascade.findAllMatchingRules(node1) // and compute the value of all css properties var properties = cssCascade.allCSSProperties || cssCascade.getAllCSSProperties(); for(var p=properties.length; p--; ) { // if the property is computation-safe, use the computed value if(!(properties[p] in cssCascade.computationUnsafeProperties) && properties[p][0]!='-') { var style = getComputedStyle(node1).getPropertyValue(properties[p]); var defaultStyle = cssCascade.getDefaultStyleForTag(node1.tagName).getPropertyValue(properties[p]); if(style != defaultStyle) node2.style.setProperty(properties[p], style) continue; } // otherwise, get the element's specified value var cssValue = cssCascade.getSpecifiedStyle(node1, properties[p], matchedRules); if(cssValue && cssValue.length) { // if we have a specified value, let's use it node2.style.setProperty(properties[p], cssValue.toCSSString()); } else if(isRoot && node1.parentNode && properties[p][0] != '-') { // NOTE: the root will be detached from its parent // Therefore, we have to inherit styles from it (oh no!) // TODO: create a list of inherited properties if(!(properties[p] in cssCascade.inheritingProperties)) continue; // if the property is computation-safe, use the computed value if((properties[p]=="font-size") || (!(properties[p] in cssCascade.computationUnsafeProperties) && properties[p][0]!='-')) { var style = getComputedStyle(node1).getPropertyValue(properties[p]); node2.style.setProperty(properties[p], style); //var parentStyle = style; try { parentStyle = getComputedStyle(node1.parentNode).getPropertyValue(properties[p]) } catch(ex){} //var defaultStyle = cssCascade.getDefaultStyleForTag(node1.tagName).getPropertyValue(properties[p]); //if(style === parentStyle) { // node2.style.setProperty(properties[p], style) //} continue; } // otherwise, get the parent's specified value var cssValue = cssCascade.getSpecifiedStyle(node1, properties[p], matchedRules); if(cssValue && cssValue.length) { // if we have a specified value, let's use it node2.style.setProperty(properties[p], cssValue.toCSSString()); } } } // now, let's work on ::after and ::before var importPseudo = function(node1,node2,pseudo) { // // we'll need to use getSpecifiedStyle here as the pseudo thing is slow // var mayExist = !!cssCascade.findAllMatchingRulesWithPseudo(node1,pseudo.substr(1)).length; if(!mayExist) return; var pseudoStyle = getComputedStyle(node1,pseudo); if(pseudoStyle.content!='none'){ // let's create a stylesheet for the element var stylesheet = document.createElement('style'); stylesheet.setAttribute('data-no-css-polyfill',true); // compute the value of all css properties var node2style = ""; var properties = cssCascade.allCSSProperties || cssCascade.getAllCSSProperties(); for(var p=properties.length; p--; ) { // we always use the computed value, because we don't have better var style = pseudoStyle.getPropertyValue(properties[p]); node2style += properties[p]+":"+style+";"; } stylesheet.textContent = ( '[data-css-regions-fragment-of="' + node1.getAttribute('data-css-regions-fragment-source') + '"]' +':not([data-css-regions-starting-fragment]):not([data-css-regions-special-starting-fragment])' +':'+pseudo+'{' +node2style +"}" ); node2.parentNode.insertBefore(stylesheet, node2); } } importPseudo(node1,node2,":before"); importPseudo(node1,node2,":after"); // retarget events cssRegionsHelpers.retargetEvents(node1,node2); case 9: // Document node case 11: // Document fragment node child1 = node1.firstChild; child2 = node2.firstChild; while (child1) { next1 = child1.nextSibling; next2 = child2.nextSibling; // decide between process style or hide if(child1.cssRegionsLastFlowIntoName && child1.cssRegionsLastFlowIntoType==="element") { node2.removeChild(child2); } else { visit(child1, child2); } child1 = next1; child2 = next2; } break; } } visit(root1, root2, true); }, // // make sure the most critical events still fire in the fragment source // even if the browser initially fire them on the fragments // retargetEvents: function retargetEvents(node1,node2) { var retargetEvent = "cssRegionsHelpers.retargetEvent(this,event)"; //node2.setAttribute("onclick", retargetEvent); //node2.setAttribute("ondblclick", retargetEvent); //node2.setAttribute("onmousedown", retargetEvent); //node2.setAttribute("onmouseup", retargetEvent); //node2.setAttribute("onmousein", retargetEvent); //node2.setAttribute("onmouseout", retargetEvent); //node2.setAttribute("onmouseenter", retargetEvent); //node2.setAttribute("onmouseleave", retargetEvent); }, // // single hub for event retargeting operations. // retargetEvent: function retargeEvent(node2,e) { // get the node we should fire the event on var node1 = ( (node2.cssRegionsFragmentSource) || (node2.cssRegionsFragmentSource=document.querySelector('[data-css-regions-fragment-source="' + node2.getAttribute('data-css-regions-fragment-of') + '"]')) ); if(node1) { // dispatch the event on the real node var ne = domEvents.cloneEvent(e); node1.dispatchEvent(ne); // prevent the event to fire on the region e.stopImmediatePropagation ? e.stopImmediatePropagation() : e.stopPropagation(); // make sure to cancel the event if required if(ne.isDefaultPrevented || ne.defaultPrevented) { e.preventDefault(); return false; } } } }; return cssRegionsHelpers; })(window, document); require.define('src/css-regions/lib/helpers.js'); //////////////////////////////////////// // // this module holds the front-facing features of the polyfill // module.exports = (function(window, document, cssRegions) { "use strict"; var domEvents = require('src/core/dom-events.js'); var cssSyntax = require('src/core/css-syntax.js'); var cssCascade = require('src/core/css-cascade.js'); var cssBreak = require('src/core/css-break.js'); var cssRegionsHelpers = require('src/css-regions/lib/helpers.js'); var ES = require('src/core/dom-experimental-event-streams.js'); // // this class contains flow-relative data field // cssRegions.Flow = function NamedFlow(name) { // TODO: force immediate relayout if someone ask the overset properties // and the layout has been deemed wrong (still isn't a proof of correctness but okay) // define the flow name this.name = name; Object.defineProperty(this, "name", {get: function() { return name; }}); // define the overset status this.overset = false; // define the first empty region this.firstEmptyRegionIndex = -1; // elements poured into the flow this.content = []; this.lastContent = []; // elements that consume this flow this.regions = []; this.lastRegions = []; // event handlers this.eventListeners = { "regionfragmentchange": [], "regionoversetchange": [], }; // this function is used to work with dom event streams var This=this; This.update = function(stream) { stream.schedule(This.update); This.relayout(); }; // register to style changes already This.lastStylesheetAdded = 0; cssCascade.addEventListener('stylesheetadded', function() { if(This.lastStylesheetAdded - Date() > 100) { This.lastStylesheetAdded = +Date(); This.relayout(); } else { cssConsole.warn("Please don't add stylesheets as a response to region events. Operation cancelled.") } }); // a small counter to avoid enter retry loops This.failedLayoutCount = 0; // some other fields This.lastEventRAF = 0; This.restartLayout = false; } cssRegions.Flow.prototype.removeFromContent = function(element) { // clean up stuff this.removeEventListenersOf(element); // remove reference var index = this.content.indexOf(element); if(index>=0) { this.content.splice(index,1); } }; cssRegions.Flow.prototype.removeFromRegions = function(element) { // clean up stuff this.removeEventListenersOf(element); // remove reference var index = this.regions.indexOf(element); if(index>=0) { this.regions.splice(index,1); } }; cssRegions.Flow.prototype.addToContent = function(element) { // walk the tree to find an element inside the content chain var content = this.content; var treeWalker = document.createTreeWalker( document.documentElement, NodeFilter.SHOW_ELEMENT, function(node) { return content.indexOf(node) >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; }, false ); // which by the way has to be after the considered element treeWalker.currentNode = element; // if we find such node if(treeWalker.nextNode()) { // insert the element at his current location content.splice(content.indexOf(treeWalker.currentNode),0,element); } else { // add the new element to the end of the array content.push(element); } }; cssRegions.Flow.prototype.addToRegions = function(element) { // walk the tree to find an element inside the region chain var regions = this.regions; var treeWalker = document.createTreeWalker( document.documentElement, NodeFilter.SHOW_ELEMENT, function(node) { return regions.indexOf(node) >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; }, false ); // which by the way has to be after the considered element treeWalker.currentNode = element; // if we find such node if(treeWalker.nextNode()) { // insert the element at his current location regions.splice(this.regions.indexOf(treeWalker.currentNode),0,element); } else { // add the new element to the end of the array regions.push(element); } }; cssRegions.Flow.prototype.generateContentFragment = function() { var fragment = document.createDocumentFragment(); var This=this; // add copies of all due content for(var i=0; i 0 && !This.restartLayout) { // before doing anything, let's check our stuff is consistent var isBuggy = false; isBuggy = isBuggy || This.regions.some(function(e) { return !document.documentElement.contains(e); }) isBuggy = isBuggy || This.content.some(function(e) { return !document.documentElement.contains(e); }) if(isBuggy) { // if we found any bug, we will need to restart a layout cssConsole.warn("Buggy css regions layout: the page changed; we need to restart."); This.restartLayout = true; } else { // if it was okay, let's fire some event This.lastEventRAF = requestAnimationFrame(function() { // TODO: only fire when necessary but... This.dispatchEvent('regionfragmentchange'); This.dispatchEvent('regionoversetchange'); }); } } // NOTE: we recover the scroll position in case the browser mess it up document.documentElement.scrollTop = docElmScrollTop; document.body.scrollTop = docBdyScrollTop; // mark layout has being done This.relayoutInProgress = false; This.failedLayoutCount = 0; // restart a layout if a request was queued during the current one if(This.restartLayout) { This.restartLayout = false; This.relayout(); } } }); } catch(ex) { // sometimes IE fails for no valid reason // (other than the page is still loading) setImmediate(function() { throw ex; }); // but we cannot accept to fail, so we need to try again // until we finish a complete layout pass... This.failedLayoutCount++; if(This.failedLayoutCount<7) {requestAnimationFrame(function() { This._relayout() });} else {This.failedLayoutCount=0; This.relayoutScheduled=false; This.relayoutInProgress=false; This.restartLayout=false; } } } cssRegions.Flow.prototype.relayoutIfSizeChanged = function() { // go through all regions // and see if any did change of size var rs = this.regions; for(var i=rs.length; i--; ) { if( rs[i].offsetHeight !== rs[i].cssRegionsLastOffsetHeight || rs[i].offsetWidth !== rs[i].cssRegionsLastOffsetWidth ) { this.relayout(); return; } } } cssRegions.Flow.prototype.addEventListenersTo = function(nodes) { var This=this; if(nodes instanceof Element) { nodes=[nodes] } nodes.forEach(function(element) { if(!element.cssRegionsEventStream) { element.cssRegionsEventStream = new ES.DOMUpdateEventStream({target: element}); element.cssRegionsEventStream.schedule(This.update); } }); } cssRegions.Flow.prototype.removeEventListenersOf = function(nodes) { var This=this; if(nodes instanceof Element) { nodes=[nodes] } nodes.forEach(function(element) { if(element.cssRegionsEventStream) { element.cssRegionsEventStream.dispose(); delete element.cssRegionsEventStream; } }); } // alias cssRegions.NamedFlow = cssRegions.Flow; // return a disconnected array of the content of a NamedFlow cssRegions.NamedFlow.prototype.getContent = function getContent() { return this.content.slice(0) } // return a disconnected array of the regions of a NamedFlow cssRegions.NamedFlow.prototype.getRegions = function getRegions() { return this.regions.slice(0) } cssRegions.NamedFlow.prototype.getRegionsByContent = function getRegionsByContent(node) { var regions = []; var fragments = document.querySelectorAll('[data-css-regions-fragment-of="'+node.getAttribute('data-css-regions-fragment-source')+'"]'); for (var i=0; i wrapper // we need to link this wrapper and the actual region if(region.cssRegionsWrapper) { region.cssRegionsWrapper.cssRegionHost = region; region = region.cssRegionsWrapper; } else { region.cssRegionHost = region; } // empty the region region.innerHTML = ''; // avoid doing the layout of empty regions if(!remainingContent.hasChildNodes()) { region.cssRegionHost.cssRegionsLastOffsetHeight = region.cssRegionHost.offsetHeight; region.cssRegionHost.cssRegionsLastOffsetWidth = region.cssRegionHost.offsetWidth; region.cssRegionHost.regionOverset = 'empty'; var dummyCallback = { ondone:function(){}, onprogress:function(f){f()} }; cssRegions.layoutContent(regions, remainingContent, dummyCallback, startTime); return callback.ondone(false); } // append the remaining content to the region region.appendChild(remainingContent); // check if we have more regions to process if(regions.length !== 0) { return this.layoutContentInNextRegionsWhenReady(region, regions, remainingContent, callback, startTime); } else { return this.layoutContentInLastRegionWhenReady(region, regions, remainingContent, callback, startTime); } }, layoutContentInNextRegionsWhenReady: function(region, regions, remainingContent, callback, startTime) { // delays until all images are loaded var imgs = region.getElementsByTagName('img'); for(var imgs_index=imgs.length; imgs_index--; ) { if(!imgs[imgs_index].complete && !imgs[imgs_index].hasAttribute('height')) { return setTimeout( function() { this.layoutContentInNextRegionsWhenReady(region, regions, remainingContent, callback, startTime+32); }.bind(this), 16 ); } } // check if there was an overflow or some break-before/after instruction var regionDidOverflow = region.cssRegionHost.scrollHeight != region.cssRegionHost.offsetHeight; var shouldSegmentContent = regionDidOverflow; if(!shouldSegmentContent) { var first = region.firstElementChild; var last = region.lastElementChild; var current = first; while(current) { if(current != first) { if(/(region|all|always)/i.test(cssCascade.getSpecifiedStyle(current,'break-before',undefined,true).toCSSString())) { shouldSegmentContent = true; break; } } if(current != last) { if(/(region|all|always)/i.test(cssCascade.getSpecifiedStyle(current,'break-after',undefined,true).toCSSString())) { current = current.nextElementSibling; shouldSegmentContent = true; break; } } current = current.nextElementSibling; } } if(shouldSegmentContent) { // the remaining content is what was overflowing remainingContent = this.extractOverflowingContent(region); } else { // there's nothing more to insert remainingContent = document.createDocumentFragment(); } // if any content didn't fit if(remainingContent.hasChildNodes()) { region.cssRegionHost.regionOverset = 'overset'; } else { region.cssRegionHost.regionOverset = 'fit'; } // update flags region.cssRegionHost.cssRegionsLastOffsetHeight = region.cssRegionHost.offsetHeight; region.cssRegionHost.cssRegionsLastOffsetWidth = region.cssRegionHost.offsetWidth; // layout the next regions // WE LET THE NEXT REGION DECIDE WHAT TO RETURN if(startTime+200 > Date.now()) { return cssRegions.layoutContent(regions, remainingContent, callback, startTime); } else { return callback.onprogress(function() { cssRegions.layoutContent(regions, remainingContent, callback); }); } }, layoutContentInLastRegionWhenReady: function(region, regions, remainingContent, callback, startTime) { // delays until all images are loaded var imgs = region.getElementsByTagName('img'); for(var imgs_index=imgs.length; imgs_index--; ) { if(!imgs[imgs_index].complete && !imgs[imgs_index].hasAttribute('height')) { return setTimeout( function() { this.layoutContentInLastRegionWhenReady(region, regions, remainingContent, callback, startTime+32); }.bind(this), 32 ); } } // support region-fragment: break if(cssCascade.getSpecifiedStyle(region.cssRegionHost,"region-fragment",undefined,true).toCSSString().trim().toLowerCase()=="break") { // WE RETURN TRUE IF WE DID OVERFLOW var didOverflow = (this.extractOverflowingContent(region).hasChildNodes()); // update flags region.cssRegionHost.cssRegionsLastOffsetHeight = region.cssRegionHost.offsetHeight; region.cssRegionHost.cssRegionsLastOffsetWidth = region.cssRegionHost.offsetWidth; return callback.ondone(didOverflow); } else { // update flags region.cssRegionHost.cssRegionsLastOffsetHeight = region.cssRegionHost.offsetHeight; region.cssRegionHost.cssRegionsLastOffsetWidth = region.cssRegionHost.offsetWidth; // WE RETURN FALSE IF WE DIDN'T OVERFLOW return callback.ondone(region.cssRegionHost.offsetHeight != region.cssRegionHost.scrollHeight); } }, // // this function returns a document fragment containing the content // that didn't fit in a particular element. // // in the simplest cases, we can just use hit-targeting to get very // close the the natural breaking point. for mostly textual flows, // this works perfectly, for the others, we may need some tweaks. // // there's a code detecting whether this hit-target optimization // did possibly fail, in which case we return to a setup where we // start from scratch. // extractOverflowingContent: function(region, dontOptimize) { // make sure empty nodes don't make our life more difficult cssRegionsHelpers.embedTrailingWhiteSpaceNodes(region); // get the region layout //var sizingH = region.cssRegionHost.offsetHeight; // avail size (max-height) // // FIXME: By Alex. added 1pt because it sometimes skips the last line var sizingH = region.cssRegionHost.offsetHeight + 1; // avail size (max-height) var sizingW = region.cssRegionHost.offsetWidth; // avail size (max-width) var pos = region.cssRegionHost.getBoundingClientRect(); // avail size? pos = {top: pos.top, bottom: pos.bottom, left: pos.left, right: pos.right}; // substract from the bottom any border/padding of the region var lostHeight = parseInt(getComputedStyle(region.cssRegionHost).paddingBottom); lostHeight += parseInt(getComputedStyle(region.cssRegionHost).borderBottomWidth); pos.bottom -= lostHeight; sizingH -= lostHeight; // // note: let's use hit targeting to find a dom range // which is close to the location where we will need to // break the content into fragments // // get the caret range for the bottom-right of that location try { var r = dontOptimize ? document.createRange() : document.caretRangeFromPoint( pos.left + sizingW - 1, pos.top + sizingH - 1 ); } catch (ex) { try { cssConsole.error(ex.message); cssConsole.dir(ex); } catch (ex) {} } // helper for logging info /*cssConsole.log("extracting overflow") cssConsole.log(pos.bottom)*/ var debug = function() { /*cssConsole.dir({ startContainer: r.startContainer, startOffset: r.startOffset, browserBCR: r.getBoundingClientRect(), computedBCR: rect });*/ } var fixNullRect = function() { if(rect.bottom==0 && rect.top==0 && rect.left==0 && rect.right==0) { var scrollTop = -(document.documentElement.scrollTop || document.body.scrollTop); var scrollLeft = -(document.documentElement.scrollLeft || document.body.scrollLeft); rect = { width: 0, heigth: 0, top: scrollTop, bottom: scrollTop, left: scrollLeft, right: scrollLeft } } } // if the caret is outside the region if(!r || (region !== r.endContainer && !Node.contains(region,r.endContainer))) { // if the caret is after the region wrapper but inside the host... if(r && r.endContainer === region.cssRegionHost && r.endOffset==r.endContainer.childNodes.length) { // move back at the end of the region, actually r.setStart(region, region.childNodes.length); r.setEnd(region, region.childNodes.length); } else { // move back into the region r = r || document.createRange(); r.setStart(region, 0); r.setEnd(region, 0); dontOptimize=true; } } // start finding the natural breaking point do { // store the current selection rect for fast access var rect = r.myGetExtensionRect(); fixNullRect(); debug(); // // note: maybe the text is right-to-left // in this case, we can go further than the caret // // move the end point char by char until it's completely in the region while(!(r.endContainer==region && r.endOffset==r.endContainer.childNodes.length) && rect.bottom<=pos.top+sizingH) { debug(); // look if we can optimize by moving fast forward var nextSibling = r.endContainer.childNodes[r.endOffset]; var nextSiblingRect = !nextSibling || Node.getBoundingClientRect(nextSibling); if(nextSibling && nextSiblingRect.bottom<=pos.top+sizingH) { // if yes, move element by element r.setStartAfter(nextSibling) r.setEndAfter(nextSibling) rect = nextSiblingRect fixNullRect() } else { // otherwise, go char-by-char r.myMoveTowardRight(); rect = r.myGetExtensionRect(); fixNullRect(); } } // // note: maybe the text is one line too big // in this case, we have to backtrack a little // // move the end point char by char until it's completely in the region while(!(r.endContainer==region && r.endOffset==0) && rect.bottom>pos.top+sizingH) { debug(); r.myMoveOneCharLeft(); rect = r.myGetExtensionRect(); fixNullRect(); } debug() // // note: if we optimized via hit-testing, this may be wrong // if next condition does not hold, we're fine. // otherwhise we must restart without optimization... // // if the selected content is possibly off-target var optimizationFailled = false; if(!dontOptimize) { var current = r.endContainer; while(current = cssRegionsHelpers.getAllLevelPreviousSibling(current, region)) { if(Node.getBoundingClientRect(current).bottom > pos.top + sizingH) { r.setStart(region,0); r.setEnd(region,0); optimizationFailled=true; dontOptimize=true; break; } } } } while(optimizationFailled) // // note: we should not break the content inside monolithic content // if we do, we need to change the selection to avoid that // // move the selection before the monolithic ancestors var current = r.endContainer; while(current !== region) { if(cssBreak.isMonolithic(current)) { r.setEndBefore(current); } current = current.parentNode; } // if the selection is not in the region anymore, add the whole region if(!r || (region !== r.endContainer && !Node.contains(region,r.endContainer))) { cssConsole.dir(r.cloneRange()); debugger; r.setStart(region,region.childNodes.length); r.setEnd(region,region.childNodes.length); } // // note: we don't want to break inside a line. // backtrack to end of previous line... // var first = r.startContainer.childNodes[r.startOffset], current = first; if(cssBreak.hasAnyInlineFlow(r.startContainer)) { while((current) && (current = current.previousSibling)) { if(cssBreak.areInSameSingleLine(current,first)) { // optimization: first and current are on the same line // so if next and current are not the same line, it will still be // the same line the "first" element is in first = current; if(current instanceof Element) { // we don't want to break inside text lines r.setEndBefore(current); } else { // get last line via client rects var lines = Node.getClientRects(current); // if the text node did wrap into multiple lines if(lines.length>1) { // move back from the end until we get into previous line var previousLineBottom = lines[lines.length-2].bottom; r.setEnd(current, current.nodeValue.length); while(rect.bottom>previousLineBottom) { r.myMoveOneCharLeft(); rect = r.myGetExtensionRect(); fixNullRect(); } // make sure we didn't exit the text node by mistake if(r.endContainer!==current) { // if we did, there's something wrong about the text node // but we can consider the text node as an element instead r.setEndBefore(current); // debugger; } } else { // we can consider the text node as an element r.setEndBefore(current); } } } else { // if the two elements are not on the same line, // then we just found a line break! break; } } } // if the selection is not in the region anymore, add the whole region if(!r || (region !== r.endContainer && !Node.contains(region,r.endContainer))) { cssConsole.dir(r.cloneRange()); debugger; r.setStart(region,region.childNodes.length); r.setEnd(region,region.childNodes.length); } // // note: the css-break spec says that a region should not be emtpy // // if we end up with nothing being selected, add the first block anyway if(r.endContainer===region && r.endOffset===0 && r.endOffset!==region.childNodes.length) { // find the first allowed break point do { //cssConsole.dir(r.cloneRange()); // move the position char-by-char r.myMoveTowardRight(); // but skip long islands of monolithic elements // since we know we cannot break inside them anyway var current = r.endContainer; while(current && current !== region) { if(cssBreak.isMonolithic(current)) { r.setStartAfter(current); r.setEndAfter(current); } current = current.parentNode; } } // do that until we reach a possible break point, or the end of the element while(!cssBreak.isPossibleBreakPoint(r,region) && !(r.endContainer===region && r.endOffset===region.childNodes.length)) } // if the selection is not in the region anymore, add the whole region if(!r || region !== r.endContainer && !Node.contains(region,r.endContainer)) { cssConsole.dir(r.cloneRange()); debugger; r.setStart(region,region.childNodes.length); r.setEnd(region,region.childNodes.length); } // now, let's try to find a break-before/break-after element before the splitting point var current = r.endContainer; if(current.hasChildNodes()) { if(r.endOffset>0) { current=current.childNodes[r.endOffset-1] } }; var first = r.endContainer.firstChild; do { if(current.style) { if(current != first) { if(/(region|all|always)/i.test(cssCascade.getSpecifiedStyle(current,'break-before',undefined,true).toCSSString())) { r.setStartBefore(current); r.setEndBefore(current); dontOptimize=true; // no algo involved in breaking, after all } } if(current !== region) { if(/(region|all|always)/i.test(cssCascade.getSpecifiedStyle(current,'break-after',undefined,true).toCSSString())) { r.setStartAfter(current); r.setEndAfter(current); dontOptimize=true; // no algo involved in breaking, after all } } } } while(current = cssRegionsHelpers.getAllLevelPreviousSibling(current, region)); // we're almost done! now, let's collect the ancestors to make some splitting postprocessing var current = r.endContainer; var allAncestors=[]; if(current.nodeType !== current.ELEMENT_NODE) current=current.parentNode; while(current !== region) { allAncestors.push(current); current = current.parentNode; } // // note: if we're about to split after the last child of // an element which has bottom-{padding/border/margin}, // we need to figure how how much of that p/b/m we can // actually keep in the first fragment // // TODO: avoid top & bottom p/b/m cuttings to use the // same variables names, it's ugly // // split bottom-{margin/border/padding} correctly if(r.endOffset == r.endContainer.childNodes.length && r.endContainer !== region) { // compute how much of the bottom border can actually fit var box = r.endContainer.getBoundingClientRect(); var excessHeight = box.bottom - (pos.top + sizingH); var endContainerStyle = getComputedStyle(r.endContainer); var availBorderHeight = parseFloat(endContainerStyle.borderBottomWidth); var availPaddingHeight = parseFloat(endContainerStyle.paddingBottom); // start by cutting into the border var borderCut = excessHeight; if(excessHeight > availBorderHeight) { borderCut = availBorderHeight; excessHeight -= borderCut; // continue by cutting into the padding var paddingCut = excessHeight; if(paddingCut > availPaddingHeight) { paddingCut = availPaddingHeight; excessHeight -= paddingCut; } else { excessHeight = 0; } } else { excessHeight = 0; } // we don't cut borders with radiuses // TODO: accept to cut the content not affected by the radius if(typeof(borderCut)==="number" && borderCut!==0) { // check the presence of a radius: var hasBottomRadius = ( parseInt(endContainerStyle.borderBottomLeftRadius)>0 || parseInt(endContainerStyle.borderBottomRightRadius)>0 ); if(hasBottomRadius) { // break before the whole border: borderCut = availBorderHeight; } } } // split top-{margin/border/padding} correctly if(r.endOffset == 0 && r.endContainer !== region) { // note: the only possibility here is that we // did split after a padding or a border. // // it can only happen if the border/padding is // too big to fit the region but is actually // the first break we could find! // compute how much of the top border can actually fit var box = r.endContainer.getBoundingClientRect(); var availHeight = (pos.top + sizingH) - pos.top; var endContainerStyle = getComputedStyle(r.endContainer); var availBorderHeight = parseFloat(endContainerStyle.borderTopWidth); var availPaddingHeight = parseFloat(endContainerStyle.paddingTop); var excessHeight = availBorderHeight + availPaddingHeight - availHeight; if(excessHeight > 0) { // start by cutting into the padding var topPaddingCut = excessHeight; if(excessHeight > availPaddingHeight) { topPaddingCut = availPaddingHeight; excessHeight -= topPaddingCut; // continue by cutting into the border var topBorderCut = excessHeight; if(topBorderCut > availBorderHeight) { topBorderCut = availBorderHeight; excessHeight -= topBorderCut; } else { excessHeight = 0; } } else { excessHeight = 0; } } } // remove bottom-{pbm} from all ancestors involved in the cut for(var i=allAncestors.length-1; i>=0; i--) { allAncestors[i].setAttribute('data-css-continued-fragment',true); if(getComputedStyle(allAncestors[i]).display.indexOf('block')>=0) { allAncestors[i].setAttribute('data-css-continued-block-fragment',true); } } if(typeof(borderCut)==="number") { allAncestors[0].removeAttribute('data-css-continued-fragment'); allAncestors[0].setAttribute('data-css-special-continued-fragment',true); allAncestors[0].style.borderBottomWidth = (availBorderHeight-borderCut)+'px'; } if(typeof(paddingCut)==="number") { allAncestors[0].removeAttribute('data-css-continued-fragment'); allAncestors[0].setAttribute('data-css-special-continued-fragment',true); allAncestors[0].style.paddingBottom = (availPaddingHeight-paddingCut)+'px'; } if(typeof(topBorderCut)==="number") { allAncestors[0].removeAttribute('data-css-continued-fragment'); allAncestors[0].setAttribute('data-css-continued-fragment',true); allAncestors[0].style.borderTopWidth = (availBorderHeight-topBorderCut)+'px'; } if(typeof(topPaddingCut)==="number") { allAncestors[0].removeAttribute('data-css-continued-fragment'); allAncestors[0].setAttribute('data-css-special-continued-fragment',true); allAncestors[0].style.paddingTop = (availPaddingHeight-topPaddingCut)+'px'; } // // note: at this point we have a collapsed range // located at the split point // // select the overflowing content r.setEnd(region, region.childNodes.length); // extract it from the current region var overflowingContent = r.extractContents(); // remove trailing whitespace from the cut element var tmp = allAncestors[0]; if(tmp && (tmp=tmp.lastChild) && !tmp.tagName && tmp.nodeValue) { var nodeValue = tmp.nodeValue.replace(/(\s|\r|\n)*$/,''); if(nodeValue) { // if the last cut was just after a ­ (soft hyphen), we need to append a dash if(/\u00AD$/.test(nodeValue)) { nodeValue = nodeValue.replace(/\u00AD$/, '-'); } tmp.nodeValue = nodeValue; } else { tmp.parentNode.removeChild(tmp); } } // // note: now we have to cancel out the artifacts of // the fragments cloning algorithm... // // do not forget to remove any top p/b/m on cut elements var newFragments = overflowingContent.querySelectorAll("[data-css-continued-fragment]"); for(var i=newFragments.length; i--;) { // TODO: optimize by using while loop and a simple matchesSelector. newFragments[i].removeAttribute('data-css-continued-fragment') newFragments[i].setAttribute('data-css-starting-fragment',true); } // deduct any already-used bottom p/b/m var specialNewFragment = overflowingContent.querySelector('[data-css-special-continued-fragment]'); if(specialNewFragment) { specialNewFragment.removeAttribute('data-css-special-continued-fragment') specialNewFragment.setAttribute('data-css-starting-fragment',true); if(typeof(borderCut)==="number") { specialNewFragment.style.borderBottomWidth = (borderCut)+'px'; } if(typeof(paddingCut)==="number") { specialNewFragment.style.paddingBottom = (paddingCut); } else { specialNewFragment.style.paddingBottom = '0px'; } if(typeof(topBorderCut)==="number") { specialNewFragment.removeAttribute('data-css-starting-fragment') specialNewFragment.setAttribute('data-css-special-starting-fragment',true); specialNewFragment.style.borderTopWidth = (topBorderCut)+'px'; } if(typeof(topPaddingCut)==="number") { specialNewFragment.removeAttribute('data-css-starting-fragment') specialNewFragment.setAttribute('data-css-special-starting-fragment',true); specialNewFragment.style.paddingTop = (topPaddingCut)+'px'; specialNewFragment.style.paddingBottom = '0px'; specialNewFragment.style.borderBottomWidth = '0px'; } } else if(typeof(borderCut)==="number") { // hum... there's an element missing here... {never happens anymore} try { throw new Error() } catch(ex) { setImmediate(function() { throw ex; }) } } else if(typeof(topPaddingCut)==="number") { // hum... there's an element missing here... {never happens anymore} try { throw new Error() } catch(ex) { setImmediate(function() { throw ex; }) } } // make sure empty nodes are reintroduced cssRegionsHelpers.unembedTrailingWhiteSpaceNodes(region); cssRegionsHelpers.unembedTrailingWhiteSpaceNodes(overflowingContent); // we're ready to return our result! return overflowingContent; }, enablePolyfill: function enablePolyfill() { // // [0] insert necessary css // var s = document.createElement('style'); s.setAttribute("data-css-no-polyfill", true); s.textContent = CSS_STYLE; var head = document.head || document.getElementsByTagName('head')[0]; head.appendChild(s); // // [1] when any update happens: // construct new content and region flow pairs // restart the region layout algorithm for the modified pairs // cssCascade.startMonitoringProperties( ["flow-into","flow-from","region-fragment"], { onupdate: function onupdate(element, rule) { // let's just ignore fragments if(element.getAttributeNode('data-css-regions-fragment-of')) return; // log some message in the console for debug cssConsole.dir({message:"onupdate",element:element,selector:rule.selector.toCSSString(),rule:rule}); var temp = null; // // compute the value of region properties // var flowInto = ( cssCascade.getSpecifiedStyle(element, "flow-into") .filter(function(t) { return t instanceof cssSyntax.IdentifierToken }) ); var flowIntoName = flowInto[0] ? flowInto[0].toCSSString().toLowerCase() : ""; if(flowIntoName=="none"||flowIntoName=="initial"||flowIntoName=="inherit"||flowIntoName=="default") {flowIntoName=""} var flowIntoType = flowInto[1] ? flowInto[1].toCSSString().toLowerCase() : ""; if(flowIntoType!="content") {flowIntoType="element"} var flowInto = flowIntoName ? flowIntoName + " " + flowIntoType : ""; var flowFrom = ( cssCascade.getSpecifiedStyle(element, "flow-from") .filter(function(t) { return t instanceof cssSyntax.IdentifierToken }) ); var flowFromName = flowFrom[0] ? flowFrom[0].toCSSString().toLowerCase() : ""; if(flowFromName=="none"||flowFromName=="initial"||flowFromName=="inherit"||flowFromName=="default") {flowFromName=""} var flowFrom = flowFromName; // // if the value of any property did change... // if(element.cssRegionsLastFlowInto != flowInto || element.cssRegionsLastFlowFrom != flowFrom) { // remove the element from previous regions var regionOverset = element.regionOverset; var lastFlowFrom = (cssRegions.flows[element.cssRegionsLastFlowFromName]); var lastFlowInto = (cssRegions.flows[element.cssRegionsLastFlowIntoName]); lastFlowFrom && lastFlowFrom.removeFromRegions(element); lastFlowInto && lastFlowInto.removeFromContent(element); // relayout those regions // (it's async so it will wait for us // to add the element back if needed) lastFlowFrom && regionOverset!='empty' && lastFlowFrom.relayout(); lastFlowInto && lastFlowInto.relayout(); // save some property values for later element.cssRegionsLastFlowInto = flowInto; element.cssRegionsLastFlowFrom = flowFrom; element.cssRegionsLastFlowIntoName = flowIntoName; element.cssRegionsLastFlowFromName = flowFromName; element.cssRegionsLastFlowIntoType = flowIntoType; // add the element to new regions // and relayout those regions, if deemed necessary if(flowFromName) { var lastFlowFrom = (cssRegions.flows[flowFromName] = cssRegions.flows[flowFromName] || new cssRegions.Flow(flowFromName)); lastFlowFrom && lastFlowFrom.addToRegions(element); lastFlowFrom && lastFlowFrom.relayout(); } if(flowIntoName) { var lastFlowInto = (cssRegions.flows[flowIntoName] = cssRegions.flows[flowIntoName] || new cssRegions.Flow(flowIntoName)); lastFlowInto && lastFlowInto.addToContent(element); lastFlowInto && lastFlowInto.relayout(); } } } } ); cssCascade.startMonitoringProperties( ["break-before","break-after"], {onupdate:function(element){ // avoid fragments triggering update loops if(element.getAttribute('data-css-regions-fragment-of')){return;} // update parent regions while(element) { if(element.cssRegionsLastFlowIntoName) { cssRegions.flows[element.cssRegionsLastFlowIntoName].relayout(); return; } element=element.parentNode; } }} ); // // [2] perform the OM exports // cssRegions.enablePolyfillObjectModel(); // // [3] make sure to update the region layout when all images loaded // window.addEventListener("load", function() { var flows = document.getNamedFlows(); for(var i=0; i lastWindowResize) continue; if(flows[i].relayoutInProgress) { flows[i].relayout(); } else { flows[i].relayoutIfSizeChanged(); } } } var hasOngoingLayouts = function() { var flows = document.getNamedFlows(); for(var i=0; i lastWindowResize) continue; if(flows[i].relayoutInProgress) { return true; } } return false; } var restartOngoingLayouts = function() { var flows = document.getNamedFlows(); for(var i=0; i lastWindowResize) continue; if(flows[i].relayoutInProgress) { flows[i].relayout(); } } } //window.addEventListener("resize", //function() { //// update the last layout flag //lastWindowResize = +new Date(); //// if we aren't planning a resfresh already //if(!relayoutModifiedFlows.timeout) { //// if we are already busy //if(hasOngoingLayouts()) { //// restart all layouts now //setTimeout(restartOngoingLayouts, 16); //// wait half a second before restarting them from now //relayoutModifiedFlows.timeout = setTimeout(relayoutModifiedFlows, 500); //} else { //// debounce by running the resize code every 200ms //relayoutModifiedFlows.timeout = setTimeout(relayoutModifiedFlows, 200); //} //} //} //); }, // this dictionary is supposed to contains all the currently existing flows flows: Object.create ? Object.create(null) : {} }; enableObjectModel(window, document, cssRegions); return cssRegions; })(window, document); require.define('src/css-regions/polyfill.js'); //////////////////////////////////////// //require('core:polyfill-dom-matchMedia'); //require('core:polyfill-dom-classList'); //require('css-grid:polyfill'); require('src/css-regions/polyfill.js'); require.define('src/requirements.js'); window.cssPolyfills = { require: require }; })(); //# sourceMappingURL=css-regions-polyfill.js.map