diff --git a/browser/base/content/socialmarks.xml b/browser/base/content/socialmarks.xml index e98820b7bb68..06c3e824c661 100644 --- a/browser/base/content/socialmarks.xml +++ b/browser/base/content/socialmarks.xml @@ -145,11 +145,10 @@ -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); } - if (!found && options.lineNumbers) - options.gutters.push("CodeMirror-linenumbers"); } // SCROLLBARS @@ -333,13 +336,19 @@ window.CodeMirror = (function() { d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; d.scrollbarV.firstChild.style.height = (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px"; - } else d.scrollbarV.style.display = ""; + } else { + d.scrollbarV.style.display = ""; + d.scrollbarV.firstChild.style.height = "0"; + } if (needsH) { d.scrollbarH.style.display = "block"; d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; d.scrollbarH.firstChild.style.width = (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px"; - } else d.scrollbarH.style.display = ""; + } else { + d.scrollbarH.style.display = ""; + d.scrollbarH.firstChild.style.width = "0"; + } if (needsH && needsV) { d.scrollbarFiller.style.display = "block"; d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; @@ -350,8 +359,10 @@ window.CodeMirror = (function() { d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; } else d.gutterFiller.style.display = ""; - if (mac_geLion && scrollbarWidth(d.measure) === 0) + if (mac_geLion && scrollbarWidth(d.measure) === 0) { d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; + d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none"; + } } function visibleLines(display, doc, viewPort) { @@ -406,12 +417,18 @@ window.CodeMirror = (function() { function updateDisplay(cm, changes, viewPort, forced) { var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated; var visible = visibleLines(cm.display, cm.doc, viewPort); - for (;;) { + for (var first = true;; first = false) { + var oldWidth = cm.display.scroller.clientWidth; if (!updateDisplayInner(cm, changes, visible, forced)) break; - forced = false; updated = true; + changes = []; updateSelection(cm); updateScrollbars(cm); + if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) { + forced = true; + continue; + } + forced = false; // Clip forced viewport to actual scrollable area if (viewPort) @@ -420,7 +437,6 @@ window.CodeMirror = (function() { visible = visibleLines(cm.display, cm.doc, viewPort); if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo) break; - changes = []; } if (updated) { @@ -456,7 +472,7 @@ window.CodeMirror = (function() { var positionsChangedFrom = Infinity; if (cm.options.lineNumbers) for (var i = 0; i < changes.length; ++i) - if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; } + if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; } var end = doc.first + doc.size; var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); @@ -613,7 +629,7 @@ window.CodeMirror = (function() { if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift(); if (lineIsHidden(cm.doc, line)) { if (line.height != 0) updateLineHeight(line, 0); - if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) { + if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) { var w = line.widgets[i]; if (w.showIfHidden) { var prev = cur.previousSibling; @@ -658,10 +674,11 @@ window.CodeMirror = (function() { } function buildLineElement(cm, line, lineNo, dims, reuse) { - var lineElement = lineContent(cm, line); + var built = buildLineContent(cm, line), lineElement = built.pre; var markers = line.gutterMarkers, display = cm.display, wrap; - if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets) + var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass; + if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets) return lineElement; // Lines with gutter elements, widgets or a background class need @@ -699,8 +716,8 @@ window.CodeMirror = (function() { wrap.appendChild(lineElement); } // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.bgClass) - wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild); + if (bgClass) + wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild); if (cm.options.lineNumbers || markers) { var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), @@ -872,9 +889,10 @@ window.CodeMirror = (function() { clearInterval(display.blinker); var on = true; display.cursor.style.visibility = display.otherCursor.style.visibility = ""; - display.blinker = setInterval(function() { - display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(function() { + display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; + }, cm.options.cursorBlinkRate); } // HIGHLIGHT WORKER @@ -894,7 +912,7 @@ window.CodeMirror = (function() { doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) { if (doc.frontier >= cm.display.showingFrom) { // Visible var oldStyles = line.styles; - line.styles = highlightLine(cm, line, state); + line.styles = highlightLine(cm, line, state, true); var ischange = !oldStyles || oldStyles.length != line.styles.length; for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; if (ischange) { @@ -903,7 +921,7 @@ window.CodeMirror = (function() { } line.stateAfter = copyState(doc.mode, state); } else { - processLine(cm, line, state); + processLine(cm, line.text, state); line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; } ++doc.frontier; @@ -926,7 +944,8 @@ window.CodeMirror = (function() { // parse correctly. function findStartLine(cm, n, precise) { var minindent, minline, doc = cm.doc; - for (var search = n, lim = n - 100; search > lim; --search) { + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { if (search <= doc.first) return doc.first; var line = getLine(doc, search - 1); if (line.stateAfter && (!precise || search <= doc.frontier)) return search; @@ -941,16 +960,17 @@ window.CodeMirror = (function() { function getStateBefore(cm, n, precise) { var doc = cm.doc, display = cm.display; - if (!doc.mode.startState) return true; + if (!doc.mode.startState) return true; var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; if (!state) state = startState(doc.mode); else state = copyState(doc.mode, state); doc.iter(pos, n, function(line) { - processLine(cm, line, state); + processLine(cm, line.text, state); var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo; line.stateAfter = save ? copyState(doc.mode, state) : null; ++pos; }); + if (precise) doc.frontier = pos; return state; } @@ -966,6 +986,10 @@ window.CodeMirror = (function() { function measureChar(cm, line, ch, data, bias) { var dir = -1; data = data || measureLine(cm, line); + if (data.crude) { + var left = data.left + ch * data.width; + return {left: left, right: left + data.width, top: data.top, bottom: data.bottom}; + } for (var pos = ch;; pos += dir) { var r = data[pos]; @@ -987,7 +1011,7 @@ window.CodeMirror = (function() { var memo = cache[i]; if (memo.text == line.text && memo.markedSpans == line.markedSpans && cm.display.scroller.clientWidth == memo.width && - memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass) + memo.classes == line.textClass + "|" + line.wrapClass) return memo; } } @@ -1007,15 +1031,18 @@ window.CodeMirror = (function() { var cache = cm.display.measureLineCache; var memo = {text: line.text, width: cm.display.scroller.clientWidth, markedSpans: line.markedSpans, measure: measure, - classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass}; + classes: line.textClass + "|" + line.wrapClass}; if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; else cache.push(memo); return measure; } function measureLineInner(cm, line) { + if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom) + return crudelyMeasureLine(cm, line); + var display = cm.display, measure = emptyArray(line.text.length); - var pre = lineContent(cm, line, measure, true); + var pre = buildLineContent(cm, line, measure, true).pre; // IE does not cache element positions of inline elements between // calls to getBoundingClientRect. This makes the loop below, @@ -1091,6 +1118,7 @@ window.CodeMirror = (function() { if (cur.measureRight) rect.right = getRect(cur.measureRight).left; if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide)); } + removeChildren(cm.display.measure); for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { finishRect(cur); if (cur.leftSide) finishRect(cur.leftSide); @@ -1099,6 +1127,15 @@ window.CodeMirror = (function() { return data; } + function crudelyMeasureLine(cm, line) { + var copy = new Line(line.text.slice(0, 100), null); + if (line.textClass) copy.textClass = line.textClass; + var measure = measureLineInner(cm, copy); + var left = measureChar(cm, copy, 0, measure, "left"); + var right = measureChar(cm, copy, 99, measure, "right"); + return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100}; + } + function measureLineWidth(cm, line) { var hasBadSpan = false; if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) { @@ -1106,9 +1143,10 @@ window.CodeMirror = (function() { if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; } var cached = !hasBadSpan && findCachedMeasurement(cm, line); - if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right; + if (cached || line.text.length >= cm.options.crudeMeasuringFrom) + return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right; - var pre = lineContent(cm, line, null, true); + var pre = buildLineContent(cm, line, null, true).pre; var end = pre.appendChild(zeroWidthElement(cm.display.measure)); removeChildrenAndAdd(cm.display.measure, pre); return getRect(end).right - getRect(cm.display.lineDiv).left; @@ -1351,11 +1389,14 @@ window.CodeMirror = (function() { } if (!updated && op.selectionChanged) updateSelection(cm); if (op.updateScrollPos) { - display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop; - display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft; + var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, newScrollPos.scrollTop)); + var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, newScrollPos.scrollLeft)); + display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top; + display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left; alignHorizontally(cm); if (op.scrollToPos) - scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin); + scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from), + clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin); } else if (newScrollPos) { scrollCursorIntoView(cm); } @@ -1443,6 +1484,10 @@ window.CodeMirror = (function() { function readInput(cm) { var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } var text = input.value; if (text == prevInput && posEq(sel.from, sel.to)) return false; if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { @@ -1573,7 +1618,10 @@ window.CodeMirror = (function() { if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; if (e.keyCode == 16) cm.doc.sel.shift = false; })); - on(d.input, "input", bind(fastPoll, cm)); + on(d.input, "input", function() { + if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; + fastPoll(cm); + }); on(d.input, "keydown", operation(cm, onKeyDown)); on(d.input, "keypress", operation(cm, onKeyPress)); on(d.input, "focus", bind(onFocus, cm)); @@ -1589,12 +1637,22 @@ window.CodeMirror = (function() { on(d.scroller, "dragover", drag_); on(d.scroller, "drop", operation(cm, onDrop)); } - on(d.scroller, "paste", function(e){ + on(d.scroller, "paste", function(e) { if (eventInWidget(d, e)) return; focusInput(cm); fastPoll(cm); }); on(d.input, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = d.input.selectionStart, end = d.input.selectionEnd; + d.input.value += "$"; + d.input.selectionStart = start; + d.input.selectionEnd = end; + cm.state.fakedLastChar = true; + } cm.state.pasteIncoming = true; fastPoll(cm); }); @@ -1658,6 +1716,7 @@ window.CodeMirror = (function() { if (captureMiddleClick) onContextMenu.call(cm, cm, e); return; case 2: + if (webkit) cm.state.lastMiddleDown = +new Date; if (start) extendSelection(cm.doc, start); setTimeout(bind(focusInput, cm), 20); e_preventDefault(e); @@ -1778,17 +1837,16 @@ window.CodeMirror = (function() { on(document, "mouseup", up); } - function clickInGutter(cm, e) { - var display = cm.display; + function gutterEvent(cm, e, type, prevent, signalfn) { try { var mX = e.clientX, mY = e.clientY; } catch(e) { return false; } + if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false; + if (prevent) e_preventDefault(e); - if (mX >= Math.floor(getRect(display.gutters).right)) return false; - e_preventDefault(e); - if (!hasHandler(cm, "gutterClick")) return true; - + var display = cm.display; var lineBox = getRect(display.lineDiv); - if (mY > lineBox.bottom) return true; + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); mY -= lineBox.top - display.viewOffset; for (var i = 0; i < cm.options.gutters.length; ++i) { @@ -1796,11 +1854,19 @@ window.CodeMirror = (function() { if (g && getRect(g).right >= mX) { var line = lineAtHeight(cm.doc, mY); var gutter = cm.options.gutters[i]; - signalLater(cm, "gutterClick", cm, line, gutter, e); - break; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); } } - return true; + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); } // Kludge to work around strange IE behavior where it'll sometimes @@ -1845,7 +1911,6 @@ window.CodeMirror = (function() { if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste"); cm.replaceSelection(text, null, "paste"); focusInput(cm); - onFocus(cm); } } catch(e){} @@ -1863,6 +1928,7 @@ window.CodeMirror = (function() { // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. if (e.dataTransfer.setDragImage && !safari) { var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; if (opera) { img.width = img.height = 1; cm.display.wrapper.appendChild(img); @@ -2061,8 +2127,8 @@ window.CodeMirror = (function() { function onKeyDown(e) { var cm = this; if (!cm.state.focused) onFocus(cm); - if (ie && e.keyCode == 27) { e.returnValue = false; } if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; + if (ie && e.keyCode == 27) e.returnValue = false; var code = e.keyCode; // IE does strange things with escape. cm.doc.sel.shift = code == 16 || e.shiftKey; @@ -2099,7 +2165,10 @@ window.CodeMirror = (function() { cm.state.focused = true; if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1) cm.display.wrapper.className += " CodeMirror-focused"; - resetInput(cm, true); + if (!cm.curOp) { + resetInput(cm, true); + if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730 + } } slowPoll(cm); restartBlink(cm); @@ -2118,11 +2187,15 @@ window.CodeMirror = (function() { function onContextMenu(cm, e) { if (signalDOMEvent(cm, e, "contextmenu")) return; var display = cm.display, sel = cm.doc.sel; - if (eventInWidget(display, e)) return; + if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return; var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; if (!pos || opera) return; // Opera is difficult. - if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))) operation(cm, setSelection)(cm.doc, pos, pos); var oldCSS = display.input.style.cssText; @@ -2137,8 +2210,8 @@ window.CodeMirror = (function() { function prepareSelectAllHack() { if (display.input.selectionStart != null) { - var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value); - display.prevInput = " "; + var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value); + display.prevInput = "\u200b"; display.input.selectionStart = 1; display.input.selectionEnd = extval.length; } } @@ -2272,6 +2345,7 @@ window.CodeMirror = (function() { } function makeChangeNoReadonly(doc, change, selUpdate) { + if (change.text.length == 1 && change.text[0] == "" && posEq(change.from, change.to)) return; var selAfter = computeSelAfterChange(doc, change, selUpdate); addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); @@ -2494,6 +2568,7 @@ window.CodeMirror = (function() { var sel = doc.sel; sel.goalColumn = null; + if (bias == null) bias = posLess(head, sel.head) ? -1 : 1; // Skip over atomic spans. if (checkAtomic || !posEq(anchor, sel.anchor)) anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push"); @@ -2571,7 +2646,7 @@ window.CodeMirror = (function() { // SCROLLING function scrollCursorIntoView(cm) { - var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin); + var coords = scrollPosIntoView(cm, cm.doc.sel.head, null, cm.options.cursorScrollMargin); if (!cm.state.focused) return; var display = cm.display, box = getRect(display.sizer), doScroll = null; if (coords.top + box.top < 0) doScroll = true; @@ -2588,11 +2663,15 @@ window.CodeMirror = (function() { } } - function scrollPosIntoView(cm, pos, margin) { + function scrollPosIntoView(cm, pos, end, margin) { if (margin == null) margin = 0; for (;;) { var changed = false, coords = cursorCoords(cm, pos); - var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; if (scrollPos.scrollTop != null) { setScrollTop(cm, scrollPos.scrollTop); @@ -2689,6 +2768,8 @@ window.CodeMirror = (function() { if (indentString != curSpaceString) replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + else if (doc.sel.head.line == n && doc.sel.head.ch < curSpaceString.length) + setSelection(doc, Pos(n, curSpaceString.length), Pos(n, curSpaceString.length), 1); line.stateAfter = null; } @@ -2789,7 +2870,7 @@ window.CodeMirror = (function() { CodeMirror.prototype = { constructor: CodeMirror, - focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, + focus: function(){window.focus(); focusInput(this); fastPoll(this);}, setOption: function(option, value) { var options = this.options, old = options[option]; @@ -3110,17 +3191,23 @@ window.CodeMirror = (function() { clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; }, - scrollIntoView: operation(null, function(pos, margin) { - if (typeof pos == "number") pos = Pos(pos, 0); + scrollIntoView: operation(null, function(range, margin) { + if (range == null) range = {from: this.doc.sel.head, to: null}; + else if (typeof range == "number") range = {from: Pos(range, 0), to: null}; + else if (range.from == null) range = {from: range, to: null}; + if (!range.to) range.to = range.from; if (!margin) margin = 0; - var coords = pos; - if (!pos || pos.line != null) { - this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head; - this.curOp.scrollToPosMargin = margin; - coords = cursorCoords(this, this.curOp.scrollToPos); + var coords = range; + if (range.from.line != null) { + this.curOp.scrollToPos = {from: range.from, to: range.to, margin: margin}; + coords = {from: cursorCoords(this, range.from), + to: cursorCoords(this, range.to)}; } - var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin); + var sPos = calculateScrollPos(this, Math.min(coords.from.left, coords.to.left), + Math.min(coords.from.top, coords.to.top) - margin, + Math.max(coords.from.right, coords.to.right), + Math.max(coords.from.bottom, coords.to.bottom) + margin); updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); }), @@ -3138,9 +3225,11 @@ window.CodeMirror = (function() { operation: function(f){return runInOp(this, f);}, refresh: operation(null, function() { + var badHeight = this.display.cachedTextHeight == null; clearCaches(this); updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop); regChange(this); + if (badHeight) estimateLineHeights(this); }), swapDoc: operation(null, function(doc) { @@ -3150,6 +3239,7 @@ window.CodeMirror = (function() { clearCaches(this); resetInput(this, true); updateScrollPos(this, doc.scrollLeft, doc.scrollTop); + signalLater(this, "swapDoc", this, old); return old; }), @@ -3193,8 +3283,14 @@ window.CodeMirror = (function() { clearCaches(cm); regChange(cm); }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) { + cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + cm.refresh(); + }, true); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); option("electricChars", true); option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); option("theme", "default", function(cm) { themeChanged(cm); @@ -3224,9 +3320,17 @@ window.CodeMirror = (function() { option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); option("showCursorWhenSelecting", false, updateSelection, true); + option("resetSelectionOnContextMenu", true); + option("readOnly", false, function(cm, val) { - if (val == "nocursor") {onBlur(cm); cm.display.input.blur();} - else if (!val) resetInput(cm, true); + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + if (!val) resetInput(cm, true); + } }); option("dragDrop", true); @@ -3241,6 +3345,7 @@ window.CodeMirror = (function() { option("historyEventDelay", 500); option("viewportMargin", 10, function(cm){cm.refresh();}, true); option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); + option("crudeMeasuringFrom", 10000); option("moveInputWithCursor", true, function(cm, val) { if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; }); @@ -3459,7 +3564,8 @@ window.CodeMirror = (function() { keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" }; // Note that the save and find-related commands aren't defined by @@ -3730,9 +3836,10 @@ window.CodeMirror = (function() { TextMarker.prototype.changed = function() { var pos = this.find(), cm = this.doc.cm; if (!pos || !cm) return; - var line = getLine(this.doc, pos.from.line); + if (this.type != "bookmark") pos = pos.from; + var line = getLine(this.doc, pos.line); clearCachedMeasurement(cm, line); - if (pos.from.line >= cm.display.showingFrom && pos.from.line < cm.display.showingTo) { + if (pos.line >= cm.display.showingFrom && pos.line < cm.display.showingTo) { for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) { if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight); break; @@ -3764,7 +3871,9 @@ window.CodeMirror = (function() { if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); var marker = new TextMarker(doc, type); - if (type == "range" && !posLess(from, to)) return marker; + if (posLess(to, from) || posEq(from, to) && type == "range" && + !(options.inclusiveLeft && options.inclusiveRight)) + return marker; if (options) copyObj(options, marker); if (marker.replacedWith) { marker.collapsed = true; @@ -3880,7 +3989,9 @@ window.CodeMirror = (function() { if (old) for (var i = 0, nw; i < old.length; ++i) { var span = old[i], marker = span.marker; var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) { + if (startsBefore || + (marker.inclusiveLeft && marker.inclusiveRight || marker.type == "bookmark") && + span.from == startCh && (!isInsert || !span.marker.insertLeft)) { var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); (nw || (nw = [])).push({from: span.from, to: endsAfter ? null : span.to, @@ -4153,6 +4264,7 @@ window.CodeMirror = (function() { this.height = estimateHeight ? estimateHeight(this) : 1; }; eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; function updateLine(line, text, markedSpans, estimateHeight) { line.text = text; @@ -4173,7 +4285,7 @@ window.CodeMirror = (function() { // Run the given mode's parser over a line, update the styles // array, which contains alternating fragments of text and CSS // classes. - function runMode(cm, text, mode, state, f) { + function runMode(cm, text, mode, state, f, forceToEnd) { var flattenSpans = mode.flattenSpans; if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; var curStart = 0, curStyle = null; @@ -4182,8 +4294,8 @@ window.CodeMirror = (function() { while (!stream.eol()) { if (stream.pos > cm.options.maxHighlightLength) { flattenSpans = false; - // Webkit seems to refuse to render text nodes longer than 57444 characters - stream.pos = Math.min(text.length, stream.start + 50000); + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; style = null; } else { style = mode.token(stream, state); @@ -4194,15 +4306,22 @@ window.CodeMirror = (function() { } stream.start = stream.pos; } - if (curStart < stream.pos) f(stream.pos, curStyle); + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } } - function highlightLine(cm, line, state) { + function highlightLine(cm, line, state, forceToEnd) { // A styles array always starts with a number identifying the // mode/overlays that it is based on (for easy invalidation). var st = [cm.state.modeGen]; // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);}); + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, forceToEnd); // Run overlays, adjust style array. for (var o = 0; o < cm.state.overlays.length; ++o) { @@ -4241,10 +4360,11 @@ window.CodeMirror = (function() { // Lightweight form of highlight -- proceed over this line and // update state, but don't save a style array. - function processLine(cm, line, state) { + function processLine(cm, text, state, startAt) { var mode = cm.doc.mode; - var stream = new StringStream(line.text, cm.options.tabSize); - if (line.text == "" && mode.blankLine) mode.blankLine(state); + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { mode.token(stream, state); stream.start = stream.pos; @@ -4252,13 +4372,23 @@ window.CodeMirror = (function() { } var styleToClassCache = {}; - function styleToClass(style) { + function interpretTokenStyle(style, builder) { if (!style) return null; + for (;;) { + var lineClass = style.match(/(?:^|\s)line-(background-)?(\S+)/); + if (!lineClass) break; + style = style.slice(0, lineClass.index) + style.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (builder[prop] == null) + builder[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop])) + builder[prop] += " " + lineClass[2]; + } return styleToClassCache[style] || (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); } - function lineContent(cm, realLine, measure, copyWidgets) { + function buildLineContent(cm, realLine, measure, copyWidgets) { var merged, line = realLine, empty = true; while (merged = collapsedSpanAtStart(line)) line = getLine(cm.doc, merged.find().from.line); @@ -4266,7 +4396,6 @@ window.CodeMirror = (function() { var builder = {pre: elt("pre"), col: 0, pos: 0, measure: null, measuredSomething: false, cm: cm, copyWidgets: copyWidgets}; - if (line.textClass) builder.pre.className = line.textClass; do { if (line.text) empty = false; @@ -4292,7 +4421,7 @@ window.CodeMirror = (function() { // Work around problem with the reported dimensions of single-char // direction spans on IE (issue #1129). See also the comment in // cursorCoords. - if (measure && ie && (order = getOrder(line))) { + if (measure && (ie || ie_gt10) && (order = getOrder(line))) { var l = order.length - 1; if (order[l].from == order[l].to) --l; var last = order[l], prev = order[l - 1]; @@ -4303,21 +4432,30 @@ window.CodeMirror = (function() { } } + var textClass = builder.textClass ? builder.textClass + " " + (realLine.textClass || "") : realLine.textClass; + if (textClass) builder.pre.className = textClass; + signal(cm, "renderLine", cm, realLine, builder.pre); - return builder.pre; + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + return token; } - var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g; function buildToken(builder, text, style, startStyle, endStyle, title) { if (!text) return; - if (!tokenSpecialChars.test(text)) { + var special = builder.cm.options.specialChars; + if (!special.test(text)) { builder.col += text.length; var content = document.createTextNode(text); } else { var content = document.createDocumentFragment(), pos = 0; while (true) { - tokenSpecialChars.lastIndex = pos; - var m = tokenSpecialChars.exec(text); + special.lastIndex = pos; + var m = special.exec(text); var skipped = m ? m.index - pos : text.length - pos; if (skipped) { content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); @@ -4330,8 +4468,7 @@ window.CodeMirror = (function() { content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); builder.col += tabWidth; } else { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + m[0].charCodeAt(0).toString(16); + var token = builder.cm.options.specialCharPlaceholder(m[0]); content.appendChild(token); builder.col += 1; } @@ -4382,7 +4519,7 @@ window.CodeMirror = (function() { return out; } return function(builder, text, style, startStyle, endStyle, title) { - return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title); + return inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title); }; } @@ -4395,11 +4532,13 @@ window.CodeMirror = (function() { if (size) { builder.measure[builder.pos] = widget; } else { - var elt = builder.measure[builder.pos] = zeroWidthElement(builder.cm.display.measure); - if (marker.type != "bookmark" || marker.insertLeft) - builder.pre.insertBefore(elt, widget); + var elt = zeroWidthElement(builder.cm.display.measure); + if (marker.type == "bookmark" && !marker.insertLeft) + builder.measure[builder.pos] = builder.pre.appendChild(elt); + else if (builder.measure[builder.pos]) + return; else - builder.pre.appendChild(elt); + builder.measure[builder.pos] = builder.pre.insertBefore(elt, widget); } builder.measuredSomething = true; } @@ -4413,7 +4552,7 @@ window.CodeMirror = (function() { var spans = line.markedSpans, allText = line.text, at = 0; if (!spans) { for (var i = 1; i < styles.length; i+=2) - builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1])); + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder)); return; } @@ -4423,7 +4562,7 @@ window.CodeMirror = (function() { if (nextChange == pos) { // Update current marker set spanStyle = spanEndStyle = spanStartStyle = title = ""; collapsed = null; nextChange = Infinity; - var foundBookmark = null; + var foundBookmarks = []; for (var j = 0; j < spans.length; ++j) { var sp = spans[j], m = sp.marker; if (sp.from <= pos && (sp.to == null || sp.to > pos)) { @@ -4437,14 +4576,15 @@ window.CodeMirror = (function() { } else if (sp.from > pos && nextChange > sp.from) { nextChange = sp.from; } - if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmark = m; + if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmarks.push(m); } if (collapsed && (collapsed.from || 0) == pos) { buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, collapsed.marker, collapsed.from == null); if (collapsed.to == null) return collapsed.marker.find(); } - if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark); + if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]); } if (pos >= len) break; @@ -4462,7 +4602,7 @@ window.CodeMirror = (function() { spanStartStyle = ""; } text = allText.slice(at, at = styles[i++]); - style = styleToClass(styles[i++]); + style = interpretTokenStyle(styles[i++], builder); } } } @@ -4481,7 +4621,8 @@ window.CodeMirror = (function() { var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; // First adjust the line structure - if (from.ch == 0 && to.ch == 0 && lastText == "") { + if (from.ch == 0 && to.ch == 0 && lastText == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore)) { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. for (var i = 0, e = text.length - 1, added = []; i < e; ++i) @@ -4740,11 +4881,11 @@ window.CodeMirror = (function() { if (extend) extendSelection(this, pos); else setSelection(this, pos, pos); }), - setSelection: docOperation(function(anchor, head) { - setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor)); + setSelection: docOperation(function(anchor, head, bias) { + setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), bias); }), - extendSelection: docOperation(function(from, to) { - extendSelection(this, clipPos(this, from), to && clipPos(this, to)); + extendSelection: docOperation(function(from, to, bias) { + extendSelection(this, clipPos(this, from), to && clipPos(this, to), bias); }), getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);}, @@ -5368,7 +5509,7 @@ window.CodeMirror = (function() { return true; } - var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/; + var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\u1DC0–\u1DFF\u20D0–\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20–\uFE2F]/; // DOM UTILITIES @@ -5430,13 +5571,18 @@ window.CodeMirror = (function() { spanAffectsWrapping = function(str, i) { return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1)); }; - else if (webkit && !/Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)) + else if (webkit && /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)) + spanAffectsWrapping = function(str, i) { + var code = str.charCodeAt(i - 1); + return code >= 8208 && code <= 8212; + }; + else if (webkit) spanAffectsWrapping = function(str, i) { if (i > 1 && str.charCodeAt(i - 1) == 45) { if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true; if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false; } - return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); + return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); }; var knownScrollbarWidth; @@ -5792,7 +5938,7 @@ window.CodeMirror = (function() { // THE END - CodeMirror.version = "3.15.0"; + CodeMirror.version = "3.20.0"; return CodeMirror; })(); diff --git a/browser/devtools/sourceeditor/codemirror/css.js b/browser/devtools/sourceeditor/codemirror/css.js index bdcd50086813..d8c30cf33d6f 100644 --- a/browser/devtools/sourceeditor/codemirror/css.js +++ b/browser/devtools/sourceeditor/codemirror/css.js @@ -1,11 +1,9 @@ -CodeMirror.defineMode("css", function(config) { - return CodeMirror.getMode(config, "text/css"); -}); - -CodeMirror.defineMode("css-base", function(config, parserConfig) { +CodeMirror.defineMode("css", function(config, parserConfig) { "use strict"; - var indentUnit = config.indentUnit, + if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css"); + + var indentUnit = config.indentUnit || config.tabSize || 2, hooks = parserConfig.hooks || {}, atMediaTypes = parserConfig.atMediaTypes || {}, atMediaFeatures = parserConfig.atMediaFeatures || {}, @@ -39,7 +37,7 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { stream.match(/^\s*\w*/); return ret("keyword", "important"); } - else if (/\d/.test(ch)) { + else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) { stream.eatWhile(/[\w.%]/); return ret("number", "unit"); } @@ -261,8 +259,13 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { } else if (type == "}") { if (context == "interpolation") style = "operator"; - state.stack.pop(); - if (context == "propertyValue") state.stack.pop(); + // Pop off end of array until { is reached + while(state.stack.length){ + var removed = state.stack.pop(); + if(removed.indexOf("{") > -1 || removed == "block" || removed == "rule"){ + break; + } + } } else if (type == "interpolation") state.stack.push("interpolation"); else if (type == "@media") state.stack.push("@media"); @@ -277,15 +280,15 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { state.stack[state.stack.length-1] = "@mediaType"; state.stack.push("@mediaType("); } + else state.stack.push("("); } else if (type == ")") { - if (context == "propertyValue" && state.stack[state.stack.length-2] == "@mediaType(") { - // In @mediaType( without closing ; after propertyValue - state.stack.pop(); - state.stack.pop(); - } - else if (context == "@mediaType(") { - state.stack.pop(); + // Pop off end of array until ( is reached + while(state.stack.length){ + var removed = state.stack.pop(); + if(removed.indexOf("(") > -1){ + break; + } } } else if (type == ":" && state.lastToken == "property") state.stack.push("propertyValue"); @@ -366,8 +369,8 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { "drop-initial-before-align", "drop-initial-size", "drop-initial-value", "elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", - "float", "float-offset", "font", "font-feature-settings", "font-family", - "font-kerning", "font-language-override", "font-size", "font-size-adjust", + "float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings", + "font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-synthesis", "font-variant", "font-variant-alternates", "font-variant-caps", "font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric", "font-variant-position", @@ -391,10 +394,12 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { "page", "page-break-after", "page-break-before", "page-break-inside", "page-policy", "pause", "pause-after", "pause-before", "perspective", "perspective-origin", "pitch", "pitch-range", "play-during", "position", - "presentation-level", "punctuation-trim", "quotes", "rendering-intent", - "resize", "rest", "rest-after", "rest-before", "richness", "right", - "rotation", "rotation-point", "ruby-align", "ruby-overhang", - "ruby-position", "ruby-span", "size", "speak", "speak-as", "speak-header", + "presentation-level", "punctuation-trim", "quotes", "region-break-after", + "region-break-before", "region-break-inside", "region-fragment", + "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness", + "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang", + "ruby-position", "ruby-span", "shape-inside", "shape-outside", "size", + "speak", "speak-as", "speak-header", "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", "tab-size", "table-layout", "target", "target-name", "target-new", "target-position", "text-align", "text-align-last", "text-decoration", @@ -432,7 +437,7 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { "darkslateblue", "darkslategray", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", - "gold", "goldenrod", "gray", "green", "greenyellow", "honeydew", + "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink", @@ -455,22 +460,22 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { "above", "absolute", "activeborder", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", - "arabic-indic", "armenian", "asterisks", "auto", "avoid", "background", - "backwards", "baseline", "below", "bidi-override", "binary", "bengali", - "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", - "both", "bottom", "break-all", "break-word", "button", "button-bevel", + "arabic-indic", "armenian", "asterisks", "auto", "avoid", "avoid-column", "avoid-page", + "avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary", + "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", + "both", "bottom", "break", "break-all", "break-word", "button", "button-bevel", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", - "col-resize", "collapse", "compact", "condensed", "contain", "content", + "col-resize", "collapse", "column", "compact", "condensed", "contain", "content", "content-box", "context-menu", "continuous", "copy", "cover", "crop", "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", "decimal-leading-zero", "default", "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", - "element", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", + "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", @@ -486,7 +491,7 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", - "italic", "justify", "kannada", "katakana", "katakana-iroha", "khmer", + "italic", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer", "landscape", "lao", "large", "larger", "left", "level", "lighter", "line-through", "linear", "lines", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", @@ -505,11 +510,11 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", - "outside", "overlay", "overline", "padding", "padding-box", "painted", - "paused", "persian", "plus-darker", "plus-lighter", "pointer", "portrait", - "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", - "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", - "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", + "outside", "outside-shape", "overlay", "overline", "padding", "padding-box", + "painted", "page", "paused", "persian", "plus-darker", "plus-lighter", "pointer", + "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", + "radio", "read-only", "read-write", "read-write-plaintext-only", "rectangle", "region", + "relative", "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", @@ -580,7 +585,7 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { return false; } }, - name: "css-base" + name: "css" }); CodeMirror.defineMIME("text/x-scss", { @@ -591,6 +596,12 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { valueKeywords: valueKeywords, allowNested: true, hooks: { + ":": function(stream) { + if (stream.match(/\s*{/)) { + return [null, "{"]; + } + return false; + }, "$": function(stream) { stream.match(/^[\w-]+/); if (stream.peek() == ":") { @@ -598,6 +609,11 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { } return ["variable", "variable"]; }, + ",": function(stream, state) { + if (state.stack[state.stack.length - 1] == "propertyValue" && stream.match(/^ *\$/, false)) { + return ["operator", ";"]; + } + }, "/": function(stream, state) { if (stream.eat("/")) { stream.skipToEnd(); @@ -618,6 +634,6 @@ CodeMirror.defineMode("css-base", function(config, parserConfig) { } } }, - name: "css-base" + name: "css" }); })(); diff --git a/browser/devtools/sourceeditor/codemirror/dialog/dialog.js b/browser/devtools/sourceeditor/codemirror/dialog/dialog.js index 0c19036a0fd0..499964fdb2c7 100644 --- a/browser/devtools/sourceeditor/codemirror/dialog/dialog.js +++ b/browser/devtools/sourceeditor/codemirror/dialog/dialog.js @@ -12,13 +12,20 @@ } if (typeof template == "string") { dialog.innerHTML = template; - } else { + } else { // Assuming it's a detached DOM element. dialog.appendChild(template); } return dialog; } + function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; + } + CodeMirror.defineExtension("openDialog", function(template, callback, options) { + closeNotification(this, null); var dialog = dialogDiv(this, template, options && options.bottom); var closed = false, me = this; function close() { @@ -55,6 +62,7 @@ }); CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { + closeNotification(this, null); var dialog = dialogDiv(this, template, options && options.bottom); var buttons = dialog.getElementsByTagName("button"); var closed = false, me = this, blurring = 1; @@ -81,4 +89,33 @@ CodeMirror.on(b, "focus", function() { ++blurring; }); } }); + + /* + * openNotification + * Opens a notification, that can be closed with an optional timer + * (default 5000ms timer) and always closes on click. + * + * If a notification is opened while another is opened, it will close the + * currently opened one and open the new one immediately. + */ + CodeMirror.defineExtension("openNotification", function(template, options) { + closeNotification(this, close); + var dialog = dialogDiv(this, template, options && options.bottom); + var duration = options && (options.duration === undefined ? 5000 : options.duration); + var closed = false, doneTimer; + + function close() { + if (closed) return; + closed = true; + clearTimeout(doneTimer); + dialog.parentNode.removeChild(dialog); + } + + CodeMirror.on(dialog, 'click', function(e) { + CodeMirror.e_preventDefault(e); + close(); + }); + if (duration) + doneTimer = setTimeout(close, options.duration); + }); })(); diff --git a/browser/devtools/sourceeditor/codemirror/htmlmixed.js b/browser/devtools/sourceeditor/codemirror/htmlmixed.js index ec0c21d24ad8..b59ef37edbe1 100644 --- a/browser/devtools/sourceeditor/codemirror/htmlmixed.js +++ b/browser/devtools/sourceeditor/codemirror/htmlmixed.js @@ -44,7 +44,7 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { if (close > -1) stream.backUp(cur.length - close); else if (m = cur.match(/<\/?$/)) { stream.backUp(cur.length); - if (!stream.match(pat, false)) stream.match(cur[0]); + if (!stream.match(pat, false)) stream.match(cur); } return style; } diff --git a/browser/devtools/sourceeditor/codemirror/javascript.js b/browser/devtools/sourceeditor/codemirror/javascript.js index 0be9b79f7cba..f27c06346c63 100644 --- a/browser/devtools/sourceeditor/codemirror/javascript.js +++ b/browser/devtools/sourceeditor/codemirror/javascript.js @@ -21,7 +21,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), "in": operator, "typeof": operator, "instanceof": operator, "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, - "this": kw("this") + "this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"), + "yield": C, "export": kw("export"), "import": kw("import"), "extends": C }; // Extend the 'normal' keywords with the TypeScript language extensions @@ -30,7 +31,6 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var tsKeywords = { // object-like things "interface": kw("interface"), - "class": kw("class"), "extends": kw("extends"), "constructor": kw("constructor"), @@ -40,8 +40,6 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { "protected": kw("protected"), "static": kw("static"), - "super": kw("super"), - // types "string": type, "number": type, "bool": type, "any": type }; @@ -56,11 +54,6 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var isOperatorChar = /[+\-*&%=<>!?|~^]/; - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - function nextUntilUnescaped(stream, end) { var escaped = false, next; while ((next = stream.next()) != null) { @@ -78,49 +71,51 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { type = tp; content = cont; return style; } - - function jsTokenBase(stream, state) { + function tokenBase(stream, state) { var ch = stream.next(); - if (ch == '"' || ch == "'") - return chain(stream, state, jsTokenString(ch)); - else if (/[\[\]{}\(\),;\:\.]/.test(ch)) + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { + return ret("number", "number"); + } else if (ch == "." && stream.match("..")) { + return ret("spread", "meta"); + } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { return ret(ch); - else if (ch == "0" && stream.eat(/x/i)) { + } else if (ch == "=" && stream.eat(">")) { + return ret("=>"); + } else if (ch == "0" && stream.eat(/x/i)) { stream.eatWhile(/[\da-f]/i); return ret("number", "number"); - } - else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { + } else if (/\d/.test(ch)) { stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); return ret("number", "number"); - } - else if (ch == "/") { + } else if (ch == "/") { if (stream.eat("*")) { - return chain(stream, state, jsTokenComment); - } - else if (stream.eat("/")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } else if (stream.eat("/")) { stream.skipToEnd(); return ret("comment", "comment"); - } - else if (state.lastType == "operator" || state.lastType == "keyword c" || - /^[\[{}\(,;:]$/.test(state.lastType)) { + } else if (state.lastType == "operator" || state.lastType == "keyword c" || + state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) { nextUntilUnescaped(stream, "/"); stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla return ret("regexp", "string-2"); - } - else { + } else { stream.eatWhile(isOperatorChar); return ret("operator", null, stream.current()); } - } - else if (ch == "#") { + } else if (ch == "`") { + state.tokenize = tokenQuasi; + return tokenQuasi(stream, state); + } else if (ch == "#") { stream.skipToEnd(); return ret("error", "error"); - } - else if (isOperatorChar.test(ch)) { + } else if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return ret("operator", null, stream.current()); - } - else { + } else { stream.eatWhile(/[\w\$_]/); var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; return (known && state.lastType != ".") ? ret(known.type, known.style, word) : @@ -128,19 +123,19 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } } - function jsTokenString(quote) { + function tokenString(quote) { return function(stream, state) { if (!nextUntilUnescaped(stream, quote)) - state.tokenize = jsTokenBase; + state.tokenize = tokenBase; return ret("string", "string"); }; } - function jsTokenComment(stream, state) { + function tokenComment(stream, state) { var maybeEnd = false, ch; while (ch = stream.next()) { if (ch == "/" && maybeEnd) { - state.tokenize = jsTokenBase; + state.tokenize = tokenBase; break; } maybeEnd = (ch == "*"); @@ -148,6 +143,50 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return ret("comment", "comment"); } + function tokenQuasi(stream, state) { + var escaped = false, next; + while ((next = stream.next()) != null) { + if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && next == "\\"; + } + return ret("quasi", "string-2", stream.current()); + } + + var brackets = "([{}])"; + // This is a crude lookahead trick to try and notice that we're + // parsing the argument patterns for a fat-arrow function before we + // actually hit the arrow token. It only works if the arrow is on + // the same line as the arguments and there's no strange noise + // (comments) in between. Fallback is to only notice when we hit the + // arrow, and not declare the arguments as locals for the arrow + // body. + function findFatArrow(stream, state) { + if (state.fatArrowAt) state.fatArrowAt = null; + var arrow = stream.string.indexOf("=>", stream.start); + if (arrow < 0) return; + + var depth = 0, sawSomething = false; + for (var pos = arrow - 1; pos >= 0; --pos) { + var ch = stream.string.charAt(pos); + var bracket = brackets.indexOf(ch); + if (bracket >= 0 && bracket < 3) { + if (!depth) { ++pos; break; } + if (--depth == 0) break; + } else if (bracket >= 3 && bracket < 6) { + ++depth; + } else if (/[$\w]/.test(ch)) { + sawSomething = true; + } else if (sawSomething && !depth) { + ++pos; + break; + } + } + if (sawSomething && !depth) state.fatArrowAt = pos; + } + // Parser var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true}; @@ -164,6 +203,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function inScope(state, varname) { for (var v = state.localVars; v; v = v.next) if (v.name == varname) return true; + for (var cx = state.context; cx; cx = cx.prev) { + for (var v = cx.vars; v; v = v.next) + if (v.name == varname) return true; + } } function parseJS(state, style, type, content, stream) { @@ -210,7 +253,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { state.localVars = {name: varname, next: state.localVars}; } else { if (inList(state.globalVars)) return; - state.globalVars = {name: varname, next: state.globalVars}; + if (parserConfig.globalVars) + state.globalVars = {name: varname, next: state.globalVars}; } } @@ -252,16 +296,15 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { }; } - function statement(type) { - if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); + function statement(type, value) { + if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "{") return cont(pushlex("}"), block, poplex); if (type == ";") return cont(); if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse); if (type == "function") return cont(functiondef); - if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), - poplex, statement, poplex); + if (type == "for") return cont(pushlex("form"), forspec, poplex, statement, poplex); if (type == "variable") return cont(pushlex("stat"), maybelabel); if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex); @@ -269,6 +312,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "default") return cont(expect(":")); if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); + if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex); + if (type == "class") return cont(pushlex("form"), className, objlit, poplex); + if (type == "export") return cont(pushlex("form"), afterExport, poplex); + if (type == "import") return cont(pushlex("form"), afterImport, poplex); return pass(pushlex("stat"), expression, expect(";"), poplex); } function expression(type) { @@ -278,14 +325,20 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return expressionInner(type, true); } function expressionInner(type, noComma) { + if (cx.state.fatArrowAt == cx.stream.start) { + var body = noComma ? arrowBodyNoComma : arrowBody; + if (type == "(") return cont(pushcontext, commasep(pattern, ")"), expect("=>"), body, popcontext); + else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); + } + var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); if (type == "function") return cont(functiondef); if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); - if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); - if (type == "operator") return cont(noComma ? expressionNoComma : expression); - if (type == "[") return cont(pushlex("]"), commasep(expressionNoComma, "]"), poplex, maybeop); - if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeop); + if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); + if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); + if (type == "[") return cont(pushlex("]"), expressionNoComma, maybeArrayComprehension, poplex, maybeop); + if (type == "{") return cont(commasep(objprop, "}"), maybeop); return cont(); } function maybeexpression(type) { @@ -304,16 +357,40 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function maybeoperatorNoComma(type, value, noComma) { var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; var expr = noComma == false ? expression : expressionNoComma; + if (value == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); if (type == "operator") { if (/\+\+|--/.test(value)) return cont(me); if (value == "?") return cont(expression, expect(":"), expr); return cont(expr); } + if (type == "quasi") { cx.cc.push(me); return quasi(value); } if (type == ";") return; - if (type == "(") return cont(pushlex(")", "call"), commasep(expressionNoComma, ")"), poplex, me); + if (type == "(") return cont(commasep(expressionNoComma, ")", "call"), me); if (type == ".") return cont(property, me); if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); } + function quasi(value) { + if (!value) debugger; + if (value.slice(value.length - 2) != "${") return cont(); + return cont(expression, continueQuasi); + } + function continueQuasi(type) { + if (type == "}") { + cx.marked = "string-2"; + cx.state.tokenize = tokenQuasi; + return cont(); + } + } + function arrowBody(type) { + findFatArrow(cx.stream, cx.state); + if (type == "{") return pass(statement); + return pass(expression); + } + function arrowBodyNoComma(type) { + findFatArrow(cx.stream, cx.state); + if (type == "{") return pass(statement); + return pass(expressionNoComma); + } function maybelabel(type) { if (type == ":") return cont(poplex, statement); return pass(maybeoperatorComma, expect(";"), poplex); @@ -327,16 +404,21 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (value == "get" || value == "set") return cont(getterSetter); } else if (type == "number" || type == "string") { cx.marked = type + " property"; + } else if (type == "[") { + return cont(expression, expect("]"), afterprop); } - if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expressionNoComma); + if (atomicTypes.hasOwnProperty(type)) return cont(afterprop); } function getterSetter(type) { - if (type == ":") return cont(expression); - if (type != "variable") return cont(expect(":"), expression); + if (type != "variable") return pass(afterprop); cx.marked = "property"; return cont(functiondef); } - function commasep(what, end) { + function afterprop(type) { + if (type == ":") return cont(expressionNoComma); + if (type == "(") return pass(functiondef); + } + function commasep(what, end, info) { function proceed(type) { if (type == ",") { var lex = cx.state.lexical; @@ -348,7 +430,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } return function(type) { if (type == end) return cont(); - else return pass(what, proceed); + if (info === false) return pass(what, proceed); + return pass(pushlex(end, info), what, proceed, poplex); }; } function block(type) { @@ -356,67 +439,121 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { return pass(statement, block); } function maybetype(type) { - if (type == ":") return cont(typedef); - return pass(); + if (isTS && type == ":") return cont(typedef); } function typedef(type) { if (type == "variable"){cx.marked = "variable-3"; return cont();} - return pass(); } - function vardef1(type, value) { - if (type == "variable") { + function vardef() { + return pass(pattern, maybetype, maybeAssign, vardefCont); + } + function pattern(type, value) { + if (type == "variable") { register(value); return cont(); } + if (type == "[") return cont(commasep(pattern, "]")); + if (type == "{") return cont(commasep(proppattern, "}")); + } + function proppattern(type, value) { + if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { register(value); - return isTS ? cont(maybetype, vardef2) : cont(vardef2); + return cont(maybeAssign); } - return pass(); + if (type == "variable") cx.marked = "property"; + return cont(expect(":"), pattern, maybeAssign); } - function vardef2(type, value) { - if (value == "=") return cont(expressionNoComma, vardef2); - if (type == ",") return cont(vardef1); + function maybeAssign(_type, value) { + if (value == "=") return cont(expressionNoComma); + } + function vardefCont(type) { + if (type == ",") return cont(vardef); } function maybeelse(type, value) { if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex); } + function forspec(type) { + if (type == "(") return cont(pushlex(")"), forspec1, expect(")")); + } function forspec1(type) { - if (type == "var") return cont(vardef1, expect(";"), forspec2); + if (type == "var") return cont(vardef, expect(";"), forspec2); if (type == ";") return cont(forspec2); - if (type == "variable") return cont(formaybein); + if (type == "variable") return cont(formaybeinof); return pass(expression, expect(";"), forspec2); } - function formaybein(_type, value) { - if (value == "in") return cont(expression); + function formaybeinof(_type, value) { + if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } return cont(maybeoperatorComma, forspec2); } function forspec2(type, value) { if (type == ";") return cont(forspec3); - if (value == "in") return cont(expression); + if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } return pass(expression, expect(";"), forspec3); } function forspec3(type) { if (type != ")") cont(expression); } function functiondef(type, value) { + if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} if (type == "variable") {register(value); return cont(functiondef);} - if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext); + if (type == "(") return cont(pushcontext, commasep(funarg, ")"), statement, popcontext); } - function funarg(type, value) { - if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();} + function funarg(type) { + if (type == "spread") return cont(funarg); + return pass(pattern, maybetype); + } + function className(type, value) { + if (type == "variable") {register(value); return cont(classNameAfter);} + } + function classNameAfter(_type, value) { + if (value == "extends") return cont(expression); + } + function objlit(type) { + if (type == "{") return cont(commasep(objprop, "}")); + } + function afterModule(type, value) { + if (type == "string") return cont(statement); + if (type == "variable") { register(value); return cont(maybeFrom); } + } + function afterExport(_type, value) { + if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } + if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } + return pass(statement); + } + function afterImport(type) { + if (type == "string") return cont(); + return pass(importSpec, maybeFrom); + } + function importSpec(type, value) { + if (type == "{") return cont(commasep(importSpec, "}")); + if (type == "variable") register(value); + return cont(); + } + function maybeFrom(_type, value) { + if (value == "from") { cx.marked = "keyword"; return cont(expression); } + } + function maybeArrayComprehension(type) { + if (type == "for") return pass(comprehension); + if (type == ",") return cont(commasep(expressionNoComma, "]", false)); + return pass(commasep(expressionNoComma, "]", false)); + } + function comprehension(type) { + if (type == "for") return cont(forspec, comprehension); + if (type == "if") return cont(expression, comprehension); } // Interface return { startState: function(basecolumn) { - return { - tokenize: jsTokenBase, - lastType: null, + var state = { + tokenize: tokenBase, + lastType: "sof", cc: [], lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), localVars: parserConfig.localVars, - globalVars: parserConfig.globalVars, context: parserConfig.localVars && {vars: parserConfig.localVars}, indented: 0 }; + if (parserConfig.globalVars) state.globalVars = parserConfig.globalVars; + return state; }, token: function(stream, state) { @@ -424,8 +561,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (!state.lexical.hasOwnProperty("align")) state.lexical.align = false; state.indented = stream.indentation(); + findFatArrow(stream, state); } - if (state.tokenize != jsTokenComment && stream.eatSpace()) return null; + if (state.tokenize != tokenComment && stream.eatSpace()) return null; var style = state.tokenize(stream, state); if (type == "comment") return style; state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; @@ -433,21 +571,21 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { }, indent: function(state, textAfter) { - if (state.tokenize == jsTokenComment) return CodeMirror.Pass; - if (state.tokenize != jsTokenBase) return 0; + if (state.tokenize == tokenComment) return CodeMirror.Pass; + if (state.tokenize != tokenBase) return 0; var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; // Kludge to prevent 'maybelse' from blocking lexical scope pops for (var i = state.cc.length - 1; i >= 0; --i) { var c = state.cc[i]; if (c == poplex) lexical = lexical.prev; - else if (c != maybeelse || /^else\b/.test(textAfter)) break; + else if (c != maybeelse) break; } if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") lexical = lexical.prev; var type = lexical.type, closing = firstChar == type; - if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0); + if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); else if (type == "form" && firstChar == "{") return lexical.indented; else if (type == "form") return lexical.indented + indentUnit; else if (type == "stat") diff --git a/browser/devtools/sourceeditor/codemirror/matchbrackets.js b/browser/devtools/sourceeditor/codemirror/matchbrackets.js index 131fe831fdc7..9d9b3882f7c5 100644 --- a/browser/devtools/sourceeditor/codemirror/matchbrackets.js +++ b/browser/devtools/sourceeditor/codemirror/matchbrackets.js @@ -8,6 +8,7 @@ function findMatchingBracket(cm, where, strict) { var state = cm.state.matchBrackets; var maxScanLen = (state && state.maxScanLineLength) || 10000; + var maxScanLines = (state && state.maxScanLines) || 100; var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1; var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; @@ -32,7 +33,7 @@ } } } - for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) { + for (var i = cur.line, found, e = forward ? Math.min(i + maxScanLines, cm.lineCount()) : Math.max(-1, i - maxScanLines); i != e; i+=d) { if (i == cur.line) found = scan(line, i, pos); else found = scan(cm.getLineHandle(i), i); if (found) break; diff --git a/browser/devtools/sourceeditor/codemirror/search/match-highlighter.js b/browser/devtools/sourceeditor/codemirror/search/match-highlighter.js index 3df69859849c..e5cbeacab2f6 100644 --- a/browser/devtools/sourceeditor/codemirror/search/match-highlighter.js +++ b/browser/devtools/sourceeditor/codemirror/search/match-highlighter.js @@ -15,15 +15,18 @@ (function() { var DEFAULT_MIN_CHARS = 2; var DEFAULT_TOKEN_STYLE = "matchhighlight"; + var DEFAULT_DELAY = 100; function State(options) { if (typeof options == "object") { this.minChars = options.minChars; this.style = options.style; this.showToken = options.showToken; + this.delay = options.delay; } if (this.style == null) this.style = DEFAULT_TOKEN_STYLE; if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS; + if (this.delay == null) this.delay = DEFAULT_DELAY; this.overlay = this.timeout = null; } @@ -45,7 +48,7 @@ function cursorActivity(cm) { var state = cm.state.matchHighlighter; clearTimeout(state.timeout); - state.timeout = setTimeout(function() {highlightMatches(cm);}, 100); + state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay); } function highlightMatches(cm) { diff --git a/browser/devtools/sourceeditor/codemirror/search/search.js b/browser/devtools/sourceeditor/codemirror/search/search.js index f9f3c3c72063..373216b823d6 100644 --- a/browser/devtools/sourceeditor/codemirror/search/search.js +++ b/browser/devtools/sourceeditor/codemirror/search/search.js @@ -83,6 +83,7 @@ if (!cursor.find(rev)) return; } cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); state.posFrom = cursor.from(); state.posTo = cursor.to(); });} function clearSearch(cm) {cm.operation(function() { @@ -121,6 +122,7 @@ (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; } cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); confirmDialog(cm, doReplaceConfirm, "Replace?", [function() {doReplace(match);}, advance]); }; diff --git a/browser/devtools/sourceeditor/codemirror/search/searchcursor.js b/browser/devtools/sourceeditor/codemirror/search/searchcursor.js index 3da3f04e8fe7..c034d5865b92 100644 --- a/browser/devtools/sourceeditor/codemirror/search/searchcursor.js +++ b/browser/devtools/sourceeditor/codemirror/search/searchcursor.js @@ -69,8 +69,8 @@ this.matches = function(reverse, pos) { var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(doc.getLine(ln)); var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match)); - if (reverse ? offsetA >= pos.ch || offsetA != match.length - : offsetA <= pos.ch || offsetA != line.length - match.length) + if (reverse ? offsetA > pos.ch || offsetA != match.length + : offsetA < pos.ch || offsetA != line.length - match.length) return; for (;;) { if (reverse ? !ln : ln == doc.lineCount() - 1) return; diff --git a/browser/devtools/sourceeditor/codemirror/xml.js b/browser/devtools/sourceeditor/codemirror/xml.js index 84f34a293484..4f49e07faf5e 100644 --- a/browser/devtools/sourceeditor/codemirror/xml.js +++ b/browser/devtools/sourceeditor/codemirror/xml.js @@ -76,7 +76,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { tagName = ""; var c; while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; - if (!tagName) return "error"; + if (!tagName) return "tag error"; type = isClose ? "closeTag" : "openTag"; state.tokenize = inTag; return "tag"; @@ -109,7 +109,9 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { type = "equals"; return null; } else if (ch == "<") { - return "error"; + state.tokenize = inText; + var next = state.tokenize(stream, state); + return next ? next + " error" : "error"; } else if (/[\'\"]/.test(ch)) { state.tokenize = inAttribute(ch); state.stringStartCol = stream.column(); @@ -261,7 +263,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { function attribute(type) { if (type == "equals") return cont(attvalue, attributes); if (!Kludges.allowMissing) setStyle = "error"; - else if (type == "word") setStyle = "attribute"; + else if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);} return (type == "endTag" || type == "selfcloseTag") ? pass() : cont(); } function attvalue(type) { @@ -298,7 +300,9 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { } } state.startOfLine = false; - return setStyle || style; + if (setStyle) + style = setStyle == "error" ? style + " error" : setStyle; + return style; }, indent: function(state, textAfter, fullLine) { diff --git a/browser/devtools/sourceeditor/test/cm_mode_javascript_test.js b/browser/devtools/sourceeditor/test/cm_mode_javascript_test.js index a2af527beed3..760152499e8c 100644 --- a/browser/devtools/sourceeditor/test/cm_mode_javascript_test.js +++ b/browser/devtools/sourceeditor/test/cm_mode_javascript_test.js @@ -7,4 +7,66 @@ MT("comma-and-binop", "[keyword function](){ [keyword var] [def x] = [number 1] + [number 2], [def y]; }"); + + MT("destructuring", + "([keyword function]([def a], [[[def b], [def c] ]]) {", + " [keyword let] {[def d], [property foo]: [def c]=[number 10], [def x]} = [variable foo]([variable-2 a]);", + " [[[variable-2 c], [variable y] ]] = [variable-2 c];", + "})();"); + + MT("class", + "[keyword class] [variable Point] [keyword extends] [variable SuperThing] {", + " [[ [string-2 /expr/] ]]: [number 24],", + " [property constructor]([def x], [def y]) {", + " [keyword super]([string 'something']);", + " [keyword this].[property x] = [variable-2 x];", + " }", + "}"); + + MT("module", + "[keyword module] [string 'foo'] {", + " [keyword export] [keyword let] [def x] = [number 42];", + " [keyword export] [keyword *] [keyword from] [string 'somewhere'];", + "}"); + + MT("import", + "[keyword function] [variable foo]() {", + " [keyword import] [def $] [keyword from] [string 'jquery'];", + " [keyword module] [def crypto] [keyword from] [string 'crypto'];", + " [keyword import] { [def encrypt], [def decrypt] } [keyword from] [string 'crypto'];", + "}"); + + MT("const", + "[keyword function] [variable f]() {", + " [keyword const] [[ [def a], [def b] ]] = [[ [number 1], [number 2] ]];", + "}"); + + MT("for/of", + "[keyword for]([keyword let] [variable of] [keyword of] [variable something]) {}"); + + MT("generator", + "[keyword function*] [variable repeat]([def n]) {", + " [keyword for]([keyword var] [def i] = [number 0]; [variable-2 i] < [variable-2 n]; ++[variable-2 i])", + " [keyword yield] [variable-2 i];", + "}"); + + MT("fatArrow", + "[variable array].[property filter]([def a] => [variable-2 a] + [number 1]);", + "[variable a];", // No longer in scope + "[keyword let] [variable f] = ([[ [def a], [def b] ]], [def c]) => [variable-2 a] + [variable-2 c];", + "[variable c];"); + + MT("spread", + "[keyword function] [variable f]([def a], [meta ...][def b]) {", + " [variable something]([variable-2 a], [meta ...][variable-2 b]);", + "}"); + + MT("comprehension", + "[keyword function] [variable f]() {", + " [[ [variable x] + [number 1] [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];", + " ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] === [string 'blue']));", + "}"); + + MT("quasi", + "[variable re][string-2 `fofdlakj${][variable x] + ([variable re][string-2 `foo`]) + [number 1][string-2 }fdsa`] + [number 2]"); })(); diff --git a/browser/devtools/sourceeditor/test/cm_mode_test.js b/browser/devtools/sourceeditor/test/cm_mode_test.js index 1439cab32cd6..4b63cf517ca8 100644 --- a/browser/devtools/sourceeditor/test/cm_mode_test.js +++ b/browser/devtools/sourceeditor/test/cm_mode_test.js @@ -59,6 +59,13 @@ return {tokens: tokens, plain: plain}; } + test.indentation = function(name, mode, tokens, modeName) { + var data = parseTokens(tokens); + return test((modeName || mode.name) + "_indent_" + name, function() { + return compare(data.plain, data.tokens, mode, true); + }); + }; + test.mode = function(name, mode, tokens, modeName) { var data = parseTokens(tokens); return test((modeName || mode.name) + "_" + name, function() { @@ -66,7 +73,7 @@ }); }; - function compare(text, expected, mode) { + function compare(text, expected, mode, compareIndentation) { var expectedOutput = []; for (var i = 0; i < expected.length; i += 2) { @@ -75,7 +82,7 @@ expectedOutput.push(sty, expected[i + 1]); } - var observedOutput = highlight(text, mode); + var observedOutput = highlight(text, mode, compareIndentation); var pass, passStyle = ""; pass = highlightOutputsEqual(expectedOutput, observedOutput); @@ -114,7 +121,7 @@ * * @return array of [style, token] pairs */ - function highlight(string, mode) { + function highlight(string, mode, compareIndentation) { var state = mode.startState() var lines = string.replace(/\r\n/g,'\n').split('\n'); @@ -125,14 +132,15 @@ if (line == "" && mode.blankLine) mode.blankLine(state); /* Start copied code from CodeMirror.highlight */ while (!stream.eol()) { - var style = mode.token(stream, state), substr = stream.current(); - if (style && style.indexOf(" ") > -1) style = style.split(' ').sort().join(' '); + var compare = mode.token(stream, state), substr = stream.current(); + if(compareIndentation) compare = mode.indent(state) || null; + else if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' '); - stream.start = stream.pos; - if (pos && st[pos-2] == style && !newLine) { + stream.start = stream.pos; + if (pos && st[pos-2] == compare && !newLine) { st[pos-1] += substr; } else if (substr) { - st[pos++] = style; st[pos++] = substr; + st[pos++] = compare; st[pos++] = substr; } // Give up when line is ridiculously long if (stream.pos > 5000) { diff --git a/browser/devtools/sourceeditor/test/cm_test.js b/browser/devtools/sourceeditor/test/cm_test.js index 67b25d00e7ec..e5d9afdeddd9 100644 --- a/browser/devtools/sourceeditor/test/cm_test.js +++ b/browser/devtools/sourceeditor/test/cm_test.js @@ -1,5 +1,7 @@ var Pos = CodeMirror.Pos; +CodeMirror.defaults.rtlMoveVisually = true; + function forEach(arr, f) { for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); } @@ -517,6 +519,24 @@ testCM("bookmarkCursor", function(cm) { is(cm.cursorCoords(Pos(4, 1)).left > pos41.left, "single-char bug"); }, {value: "foo\nbar\n\n\nx\ny"}); +testCM("multiBookmarkCursor", function(cm) { + if (phantom) return; + var ms = [], m; + function add(insertLeft) { + for (var i = 0; i < 3; ++i) { + var node = document.createElement("span"); + node.innerHTML = "X"; + ms.push(cm.setBookmark(Pos(0, 1), {widget: node, insertLeft: insertLeft})); + } + } + var base1 = cm.cursorCoords(Pos(0, 1)).left, base4 = cm.cursorCoords(Pos(0, 4)).left; + add(true); + is(Math.abs(base1 - cm.cursorCoords(Pos(0, 1)).left) < .1); + while (m = ms.pop()) m.clear(); + add(false); + is(Math.abs(base4 - cm.cursorCoords(Pos(0, 1)).left) < .1); +}, {value: "abcdefg"}); + testCM("getAllMarks", function(cm) { addDoc(cm, 10, 10); var m1 = cm.setBookmark(Pos(0, 2)); @@ -852,6 +872,17 @@ testCM("changedInlineWidget", function(cm) { is(hScroll.scrollWidth > hScroll.clientWidth); }, {value: "hello there"}); +testCM("changedBookmark", function(cm) { + cm.setSize("10em"); + var w = document.createElement("span"); + w.innerHTML = "x"; + var m = cm.setBookmark(Pos(0, 4), {widget: w}); + w.innerHTML = "and now the widget is really really long all of a sudden and a scrollbar is needed"; + m.changed(); + var hScroll = byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; + is(hScroll.scrollWidth > hScroll.clientWidth); +}, {value: "abcdefg"}); + testCM("inlineWidget", function(cm) { var w = cm.setBookmark(Pos(0, 2), {widget: document.createTextNode("uu")}); cm.setCursor(0, 2); @@ -1130,7 +1161,7 @@ testCM("rtlMovement", function(cm) { prevX = cursor.offsetLeft; } }); -}, {rtlMoveVisually: true}); +}); // Verify that updating a line clears its bidi ordering testCM("bidiUpdate", function(cm) { @@ -1311,6 +1342,7 @@ testCM("atomicMarker", function(cm) { eqPos(cm.getCursor(), Pos(8, 3)); m.clear(); m = atom(1, 1, 3, 8); + cm.setCursor(Pos(0, 0)); cm.setCursor(Pos(2, 0)); eqPos(cm.getCursor(), Pos(3, 8)); cm.execCommand("goCharLeft"); @@ -1509,3 +1541,22 @@ testCM("change_removedText", function(cm) { eq(removedText[0].join("\n"), "abc\nd"); eq(removedText[1].join("\n"), ""); }); + +testCM("lineStyleFromMode", function(cm) { + CodeMirror.defineMode("test_mode", function() { + return {token: function(stream) { + if (stream.match(/^\[[^\]]*\]/)) return "line-brackets"; + if (stream.match(/^\([^\]]*\)/)) return "line-background-parens"; + stream.match(/^\s+|^\S+/); + }}; + }); + cm.setOption("mode", "test_mode"); + var bracketElts = byClassName(cm.getWrapperElement(), "brackets"); + eq(bracketElts.length, 1); + eq(bracketElts[0].nodeName, "PRE"); + is(!/brackets.*brackets/.test(bracketElts[0].className)); + var parenElts = byClassName(cm.getWrapperElement(), "parens"); + eq(parenElts.length, 1); + eq(parenElts[0].nodeName, "DIV"); + is(!/parens.*parens/.test(parenElts[0].className)); +}, {value: "line1: [br] [br]\nline2: (par) (par)\nline3: nothing"}); diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index 243f284e20b1..294dc56d9e9c 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -1276,17 +1276,19 @@ MediaManager::GetUserMedia(JSContext* aCx, bool aPrivileged, if (!unknownConstraintFound.IsEmpty()) { // An unsupported mandatory constraint was found. - // Things are set up enough here that we can fire Error callback. + // + // We continue to ignore these for now, because we implement just + // facingMode, which means all existing uses of mandatory width/height would + // fail on Firefox only otherwise, which is undesirable. + // + // There's also basis for always ignoring them in a new proposal. + // TODO(jib): This is a super-low-risk fix for backport. Clean up later. LOG(("Unsupported mandatory constraint: %s\n", NS_ConvertUTF16toUTF8(unknownConstraintFound).get())); - nsString errormsg(NS_LITERAL_STRING("NOT_SUPPORTED_ERR: ")); - errormsg.Append(unknownConstraintFound); - NS_DispatchToMainThread(new ErrorCallbackRunnable(onSuccess.forget(), - onError.forget(), - errormsg, windowID)); - return NS_OK; + // unknown constraints existed in aRawConstraints only, which is unused + // from here, so continuing here effectively ignores them, as is desired. } // Ensure there's a thread for gum to proxy to off main thread diff --git a/dom/media/tests/mochitest/test_getUserMedia_constraints.html b/dom/media/tests/mochitest/test_getUserMedia_constraints.html index 086c238e1c93..ff58d03b3346 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html +++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html @@ -25,14 +25,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=882145 */ var tests = [ // Each test here tests a different constraint or codepath. - { message: "unknown mandatory constraint on video fails", - constraints: { video: { mandatory: { somethingUnknown:0 } } }, - error: "NOT_SUPPORTED_ERR: somethingUnknown", - pass: false }, - { message: "unknown mandatory constraint on audio fails", - constraints: { audio: { mandatory: { somethingUnknown:0 } } }, - error: "NOT_SUPPORTED_ERR: somethingUnknown", - pass: false }, { message: "video overconstrained by facingMode fails", constraints: { video: { mandatory: { facingMode:'left' } } }, error: "NO_DEVICES_FOUND", @@ -40,7 +32,8 @@ var tests = [ { message: "Success-path: optional video facingMode + audio ignoring facingMode", constraints: { fake: true, audio: { mandatory: { facingMode:'left' } }, - video: { optional: [{ facingMode:'left' }, + video: { mandatory: { somethingUnknown:0 }, + optional: [{ facingMode:'left' }, { facingMode:'right' }, { facingMode:'environment' }, { facingMode:'user' }, diff --git a/mobile/android/base/gfx/GLController.java b/mobile/android/base/gfx/GLController.java index 6c2b7b122152..c1c419993907 100644 --- a/mobile/android/base/gfx/GLController.java +++ b/mobile/android/base/gfx/GLController.java @@ -19,6 +19,48 @@ import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; +/** + * EGLPreloadingThread is purely a preloading optimization, not something + * we rely on for anything else than performance. We will be initializing + * EGL in GLController::initEGL() when we need it, but having EGL initialization + * already previously done by EGLPreloadingThread::run() will make it much + * faster for GLController to do again. + * + * For example, here are some timings recorded on two devices: + * + * Device | EGLPreloadingThread::run() | GLController::initEGL() + * -----------------------+----------------------------+------------------------ + * Nexus S (Android 2.3) | ~ 80 ms | < 0.1 ms + * Nexus 10 (Android 4.3) | ~ 35 ms | < 0.1 ms + */ +class EGLPreloadingThread extends Thread +{ + private static final String LOGTAG = "EGLPreloadingThread"; + private EGL10 mEGL; + private EGLDisplay mEGLDisplay; + + public EGLPreloadingThread() + { + } + + @Override + public void run() + { + mEGL = (EGL10)EGLContext.getEGL(); + mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { + Log.w(LOGTAG, "Can't get EGL display!"); + return; + } + + int[] returnedVersion = new int[2]; + if (!mEGL.eglInitialize(mEGLDisplay, returnedVersion)) { + Log.w(LOGTAG, "eglInitialize failed"); + return; + } + } +} + /** * This class is a singleton that tracks EGL and compositor things over * the lifetime of Fennec running. @@ -46,6 +88,8 @@ public class GLController { private EGL10 mEGL; private EGLDisplay mEGLDisplay; private EGLConfig mEGLConfig; + private EGLPreloadingThread mEGLPreloadingThread; + private EGLSurface mEGLSurfaceForCompositor; private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4; @@ -68,6 +112,8 @@ public class GLController { }; private GLController() { + mEGLPreloadingThread = new EGLPreloadingThread(); + mEGLPreloadingThread.start(); } static GLController getInstance(LayerView view) { @@ -84,6 +130,11 @@ public class GLController { mServerSurfaceValid = false; + if (mEGLSurfaceForCompositor != null) { + mEGL.eglDestroySurface(mEGLDisplay, mEGLSurfaceForCompositor); + mEGLSurfaceForCompositor = null; + } + // We need to coordinate with Gecko when pausing composition, to ensure // that Gecko never executes a draw event while the compositor is paused. // This is sent synchronously to make sure that we don't attempt to use @@ -132,6 +183,10 @@ public class GLController { return; } + if (!AttemptPreallocateEGLSurfaceForCompositor()) { + return; + } + // Only try to create the compositor if we have a valid surface and gecko is up. When these // two conditions are satisfied, we can be relatively sure that the compositor creation will // happen without needing to block anyhwere. Do it with a sync gecko event so that the @@ -161,6 +216,18 @@ public class GLController { if (mEGL != null) { return; } + + // This join() should not be necessary, but makes this code a bit easier to think about. + // The EGLPreloadingThread should long be done by now, and even if it's not, + // it shouldn't be a problem to be initalizing EGL from two different threads. + // Still, having this join() here means that we don't have to wonder about what + // kind of caveats might exist with EGL initialization reentrancy on various drivers. + try { + mEGLPreloadingThread.join(); + } catch (InterruptedException e) { + Log.w(LOGTAG, "EGLPreloadingThread interrupted", e); + } + mEGL = (EGL10)EGLContext.getEGL(); mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); @@ -169,6 +236,20 @@ public class GLController { return; } + // while calling eglInitialize here should not be necessary as it was already called + // by the EGLPreloadingThread, it really doesn't cost much to call it again here, + // and makes this code easier to think about: EGLPreloadingThread is only a + // preloading optimization, not something we rely on for anything else. + // + // Also note that while calling eglInitialize isn't necessary on Android 4.x + // (at least Android's HardwareRenderer does it for us already), it is necessary + // on Android 2.x. + int[] returnedVersion = new int[2]; + if (!mEGL.eglInitialize(mEGLDisplay, returnedVersion)) { + Log.w(LOGTAG, "eglInitialize failed"); + return; + } + mEGLConfig = chooseConfig(); } @@ -215,10 +296,24 @@ public class GLController { throw new GLControllerException("No suitable EGL configuration found"); } + private synchronized boolean AttemptPreallocateEGLSurfaceForCompositor() { + if (mEGLSurfaceForCompositor == null) { + initEGL(); + try { + mEGLSurfaceForCompositor = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mView.getNativeWindow(), null); + } catch (Exception e) { + Log.e(LOGTAG, "eglCreateWindowSurface threw", e); + } + } + return mEGLSurfaceForCompositor != null; + } + @WrapElementForJNI(allowMultithread = true, stubName = "CreateEGLSurfaceForCompositorWrapper") - private EGLSurface createEGLSurfaceForCompositor() { - initEGL(); - return mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, mView.getNativeWindow(), null); + private synchronized EGLSurface createEGLSurfaceForCompositor() { + AttemptPreallocateEGLSurfaceForCompositor(); + EGLSurface result = mEGLSurfaceForCompositor; + mEGLSurfaceForCompositor = null; + return result; } private String getEGLError() { diff --git a/security/manager/boot/src/nsSTSPreloadList.errors b/security/manager/boot/src/nsSTSPreloadList.errors index 54e8f761a13f..59268f430e8f 100644 --- a/security/manager/boot/src/nsSTSPreloadList.errors +++ b/security/manager/boot/src/nsSTSPreloadList.errors @@ -9,6 +9,7 @@ bcrook.com: max-age too low: 86400 betnet.fr: could not connect to host bigshinylock.minazo.net: could not connect to host bitbucket.org: max-age too low: 2592000 +blog.lookout.com: did not receive HSTS header braintreegateway.com: did not receive HSTS header braintreepayments.com: did not receive HSTS header browserid.org: did not receive HSTS header @@ -26,22 +27,25 @@ dl.google.com: did not receive HSTS header docs.google.com: did not receive HSTS header drive.google.com: did not receive HSTS header dropcam.com: did not receive HSTS header +email.lookout.com: could not connect to host emailprivacytester.com: did not receive HSTS header encrypted.google.com: did not receive HSTS header epoxate.com: max-age too low: 259200 fatzebra.com.au: did not receive HSTS header fj.simple.com: did not receive HSTS header +get.zenpayroll.com: did not receive HSTS header gmail.com: did not receive HSTS header gocardless.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 124" data: no] googlemail.com: did not receive HSTS header googleplex.com: could not connect to host +goto.google.com: did not receive HSTS header greplin.com: did not receive HSTS header grepular.com: max-age too low: 8640000 groups.google.com: did not receive HSTS header history.google.com: did not receive HSTS header hostedtalkgadget.google.com: did not receive HSTS header id.atlassian.com: did not receive HSTS header -iop.intuit.com: could not connect to host +iop.intuit.com: max-age too low: 86400 irccloud.com: did not receive HSTS header jitsi.org: did not receive HSTS header jottit.com: did not receive HSTS header @@ -53,6 +57,7 @@ logentries.com: did not receive HSTS header lumi.do: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 124" data: no] mail.google.com: did not receive HSTS header market.android.com: did not receive HSTS header +mattmccutchen.net: could not connect to host medium.com: max-age too low: 2592000 my.alfresco.com: did not receive HSTS header mydigipass.com: did not receive HSTS header @@ -63,7 +68,7 @@ oraprodsso.corp.google.com: could not connect to host ottospora.nl: could not connect to host packagist.org: max-age too low: 2592000 paypal.com: max-age too low: 14400 -piratenlogin.de: could not connect to host +platform.lookout.com: could not connect to host plus.google.com: did not receive HSTS header plus.sandbox.google.com: did not receive HSTS header profiles.google.com: did not receive HSTS header diff --git a/security/manager/boot/src/nsSTSPreloadList.inc b/security/manager/boot/src/nsSTSPreloadList.inc index ec535a359e0a..cf09d88a6eb6 100644 --- a/security/manager/boot/src/nsSTSPreloadList.inc +++ b/security/manager/boot/src/nsSTSPreloadList.inc @@ -8,7 +8,7 @@ /*****************************************************************************/ #include -const PRTime gPreloadListExpirationTime = INT64_C(1394881586089000); +const PRTime gPreloadListExpirationTime = INT64_C(1396091153162000); class nsSTSPreload { @@ -32,6 +32,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "blog.torproject.org", false }, { "blueseed.co", false }, { "bugzilla.mozilla.org", true }, + { "business.lookout.com", false }, { "business.medbank.com.mt", true }, { "carezone.com", false }, { "check.torproject.org", false }, @@ -57,10 +58,13 @@ static const nsSTSPreload kSTSPreloadList[] = { { "ebanking.indovinabank.com.vn", false }, { "ecosystem.atlassian.net", true }, { "entropia.de", false }, + { "errors.zenpayroll.com", false }, { "espra.com", true }, { "factor.cc", false }, + { "faq.lookout.com", false }, { "forum.linode.com", false }, { "forum.quantifiedself.com", true }, + { "gernert-server.de", true }, { "getlantern.org", true }, { "grc.com", false }, { "haste.ch", true }, @@ -70,6 +74,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "intercom.io", false }, { "itriskltd.com", true }, { "keyerror.com", true }, + { "kinsights.com", false }, { "lastpass.com", false }, { "launchkey.com", true }, { "library.linode.com", false }, @@ -84,6 +89,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "lumi.do", false }, { "luneta.nearbuysystems.com", false }, { "makeyourlaws.org", false }, + { "manage.zenpayroll.com", false }, { "manager.linode.com", false }, { "mattmccutchen.net", true }, { "mediacru.sh", true }, @@ -110,6 +116,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "securityheaders.com", true }, { "shodan.io", true }, { "silentcircle.com", false }, + { "simbolo.co.uk", false }, { "simple.com", false }, { "squareup.com", false }, { "stocktrade.de", false }, @@ -141,8 +148,11 @@ static const nsSTSPreload kSTSPreloadList[] = { { "www.mydigipass.com", false }, { "www.mylookout.com", false }, { "www.noisebridge.net", false }, + { "www.simbolo.co.uk", false }, { "www.simple.com", false }, { "www.therapynotes.com", false }, { "www.torproject.org", false }, { "www.twitter.com", false }, + { "www.zenpayroll.com", false }, + { "zenpayroll.com", false }, };