<commit_before>
let ExtRichText = {};

ExtRichText.name = "UIRichText";
ExtRichText.icon = "app://res/control/Label.png";
ExtRichText.tag = 2;

ExtRichText.GenEmptyNode = function() {
    let str = "整个游戏周期，仅需花费￥" + "[c240:237:0]6[/c](原价￥100)即可获得[c240:237:0]稀有英雄-炮姐[/c]和丰厚返利哦~好划算有木有~~"
    let node = new NodeRichText("V[cr]i[f30]su[/c]al[/f]UI", {}, {});
    node._className = ExtRichText.name;
    return node;
};

ExtRichText.GenNodeByData = function(data, parent) {
    return this.GenEmptyNode();
};

ExtRichText.SetNodePropByData = function(node, data, parent) {
    data.textAlign && (node.setTextHorizontalAlignment(data.textAlign));
    data.verticalAlign && (node.setTextVerticalAlignment(data.verticalAlign));
    data.lineBreakOnSpace && (node.setLineBreakOnSpace(data.lineBreakOnSpace));
    if(covertToColor(data.oriColor)) {
        node._originConfig.color = covertToColor(data.oriColor);
    }

    if(covertToColor(data.oriOColor)) {
        node._originConfig.ocolor = covertToColor(data.oriOColor);
    }
    data.oriFontSize && (node._originConfig.oriFontSize = data.oriFontSize);
    data.oriOSize && (node._originConfig.osize = data.oriOSize);
    node._text = data.text;
    node.forceUpdate();
};

ExtRichText.ExportNodeData = function(node, data) {
    node._textHorizontalAlignment != cc.TEXT_ALIGNMENT_LEFT && (data["textAlign"] = node._textHorizontalAlignment);
    node._textVerticalAlignment != cc.VERTICAL_TEXT_ALIGNMENT_TOP && (data["verticalAlign"] = node._textVerticalAlignment);
    node._lineBreakOnSpace && (data["lineBreakOnSpace"] = node._lineBreakOnSpace);
    if(!cc.colorEqual(node._originConfig.color, cc.color.WHITE)) {
        data["oriColor"] = [node._originConfig.color.r, node._originConfig.color.g, node._originConfig.color.b, node._originConfig.color.a]
    }
    data["oriFontSize"] = node._originConfig.fontSize;
    data["oriOSize"] = node._originConfig.osize;
    if(!cc.colorEqual(node._originConfig.ocolor, cc.color.BLACK)) {
        data["oriOColor"] = [node._originConfig.ocolor.r, node._originConfig.ocolor.g, node._originConfig.ocolor.b, node._originConfig.ocolor.a]
    }
    node._text && (data["text"] = node._text);
};

ExtRichText.SetPropChange = function(control, path, value) {
    let node = control._node;
    if(path == "text") {
        node.setText(value);
    } else if(path == "textAlign") {
        node.setTextHorizontalAlignment(parseFloat(value));
    } else if(path == "verticalAlign") {
        node.setTextVerticalAlignment(parseFloat(value));
    } else if(path == "oriFontSize") {
        node._originConfig.fontSize = value;
        node.forceUpdate();
    }
    //  else if(path == "fontName") {
    //     node.fontName = value;
    // }
     else if(path == "oriOColor") {
        node._originConfig.ocolor = new cc.Color(value.r, value.g, value.b, value.a);
        node.forceUpdate();
    } else if(path == "oriOSize") {
        node._originConfig.osize = value;
        node.forceUpdate();
    } else if(path == "oriColor") {
        node._originConfig.color = new cc.Color(value.r, value.g, value.b, value.a);
        node.forceUpdate();
    }
};

function RichTextData(node) {
    this._node = node;
}

RichTextData.prototype = {
    __displayName__: "UIRichText",
    __type__: "UIRichText",

    get text() {
        return {
            path: "text",
            type: "textarea",
            name: "text",
            attrs: {
            },
            value: this._node._text,
        };
    },

    get oriFontSize() {
        return {
            path: "oriFontSize",
            type: "unit-input",
            name: "oriFontSize",
            attrs: {
                expand: true,
                step: 1,
                precision: 0,
                min: 0,
                max: 72,
            },
            value: this._node._originConfig.fontSize,
        };
    },

    get horizontalAlign() {
        return {
            path: "textAlign",
            type: "select",
            name: "HorAlign",
            attrs: {
                selects: {
                    0: "LEFT",
                    1: "CENTER",
                    2: "RIGHT",
                }
            },
            value: this._node._textHorizontalAlignment,
        };
    },

    get verticalAlign() {
        return {
            path: "verticalAlign",
            type: "select",
            name: "VerAlign",
            attrs: {
                selects: {
                    0: "TOP",
                    1: "CENTER",
                    2: "BOTTOM",
                }
            },
            value: this._node._textVerticalAlignment,
        };
    },

    get oriOSize() {
        return {
            path: "oriOSize",
            type: "unit-input",
            name: "oriOSize",
            attrs: {
            },
            value: this._node._originConfig.osize,
        };
    },

    get oriColor() {
        let color = this._node._originConfig.color || cc.color.WHITE
        return {
            path: "oriColor",
            type: "color",
            name: "oriColor",
            attrs: {
            },
            value: {
                r: color.r,
                g: color.g,
                b: color.b,
                a: color.a
            }
        };
    },

    get oriOColor() {
        let color = this._node._originConfig.ocolor || cc.color.WHITE
        return {
            path: "oriOColor",
            type: "color",
            name: "oriOColor",
            attrs: {
            },
            value: {
                r: color.r,
                g: color.g,
                b: color.b,
                a: color.a
            }
        };
    },

    get __props__() {
        return [
            this.text,
            // this.fontName,
            this.oriFontSize,
            this.horizontalAlign,
            this.verticalAlign,
            this.oriColor,
            this.oriOSize,
            this.oriOColor,
        ];
    }
}

ExtRichText.RichTextData = RichTextData;

ExtRichText.PropComps = function(node) {
    let datas = [ new WidgetData(node) ];
    datas.push(new TouchData(node));
    datas.push(new RichTextData(node));
    return datas;
};

module.exports = ExtRichText;

RegisterExtNodeControl(ExtRichText.name, ExtRichText);
<commit_msg>RichText fixto<commit_after>
let ExtRichText = {};

ExtRichText.name = "UIRichText";
ExtRichText.icon = "app://res/control/Label.png";
ExtRichText.tag = 2;

ExtRichText.GenEmptyNode = function() {
    let str = "整个游戏周期，仅需花费￥" + "[c240:237:0]6[/c](原价￥100)即可获得[c240:237:0]稀有英雄-炮姐[/c]和丰厚返利哦~好划算有木有~~"
    let node = new NodeRichText("V[cr]i[f30]su[/c]al[/f]UI", {}, {});
    node._className = ExtRichText.name;
    return node;
};

ExtRichText.GenNodeByData = function(data, parent) {
    return this.GenEmptyNode();
};

ExtRichText.SetNodeString = function(node, string) {
    if(!string) {
        return;
    }
    let analyse = tryAnalyseLang(string);
    if(analyse.isKey) {
        node._text = analyse.value;
        node._key = analyse.key;
    } else {
        node._text = string;
        node._key = undefined;
    }
};

ExtRichText.SetNodePropByData = function(node, data, parent) {
    data.textAlign && (node.setTextHorizontalAlignment(data.textAlign));
    data.verticalAlign && (node.setTextVerticalAlignment(data.verticalAlign));
    data.lineBreakOnSpace && (node.setLineBreakOnSpace(data.lineBreakOnSpace));
    if(covertToColor(data.oriColor)) {
        node._originConfig.color = covertToColor(data.oriColor);
    }

    if(covertToColor(data.oriOColor)) {
        node._originConfig.ocolor = covertToColor(data.oriOColor);
    }
    data.oriFontSize && (node._originConfig.oriFontSize = data.oriFontSize);
    data.oriOSize && (node._originConfig.osize = data.oriOSize);
    ExtRichText.SetNodeString(node, data.text);
    node.forceUpdate();
};

ExtRichText.ExportNodeData = function(node, data) {
    node._textHorizontalAlignment != cc.TEXT_ALIGNMENT_LEFT && (data["textAlign"] = node._textHorizontalAlignment);
    node._textVerticalAlignment != cc.VERTICAL_TEXT_ALIGNMENT_TOP && (data["verticalAlign"] = node._textVerticalAlignment);
    node._lineBreakOnSpace && (data["lineBreakOnSpace"] = node._lineBreakOnSpace);
    if(!cc.colorEqual(node._originConfig.color, cc.color.WHITE)) {
        data["oriColor"] = [node._originConfig.color.r, node._originConfig.color.g, node._originConfig.color.b, node._originConfig.color.a]
    }
    data["oriFontSize"] = node._originConfig.fontSize;
    data["oriOSize"] = node._originConfig.osize;
    if(!cc.colorEqual(node._originConfig.ocolor, cc.color.BLACK)) {
        data["oriOColor"] = [node._originConfig.ocolor.r, node._originConfig.ocolor.g, node._originConfig.ocolor.b, node._originConfig.ocolor.a]
    }
    data["text"] = node._key ? ("@" + node._key) : node._text;
};

ExtRichText.SetPropChange = function(control, path, value) {
    let node = control._node;
    if(path == "text") {
        ExtRichText.SetNodeString(node, value);
        node.forceUpdate();
    } else if(path == "textAlign") {
        node.setTextHorizontalAlignment(parseFloat(value));
    } else if(path == "verticalAlign") {
        node.setTextVerticalAlignment(parseFloat(value));
    } else if(path == "oriFontSize") {
        node._originConfig.fontSize = value;
        node.forceUpdate();
    }
    //  else if(path == "fontName") {
    //     node.fontName = value;
    // }
     else if(path == "oriOColor") {
        node._originConfig.ocolor = new cc.Color(value.r, value.g, value.b, value.a);
        node.forceUpdate();
    } else if(path == "oriOSize") {
        node._originConfig.osize = value;
        node.forceUpdate();
    } else if(path == "oriColor") {
        node._originConfig.color = new cc.Color(value.r, value.g, value.b, value.a);
        node.forceUpdate();
    }
};

function RichTextData(node) {
    this._node = node;
}

RichTextData.prototype = {
    __displayName__: "UIRichText",
    __type__: "UIRichText",

    get text() {
        return {
            path: "text",
            type: "textarea",
            name: "text",
            attrs: {
            },
            value: this._node._key ? ("@" + this._node._key) : this._node._text,
        };
    },

    get oriFontSize() {
        return {
            path: "oriFontSize",
            type: "unit-input",
            name: "oriFontSize",
            attrs: {
                expand: true,
                step: 1,
                precision: 0,
                min: 0,
                max: 72,
            },
            value: this._node._originConfig.fontSize,
        };
    },

    get horizontalAlign() {
        return {
            path: "textAlign",
            type: "select",
            name: "HorAlign",
            attrs: {
                selects: {
                    0: "LEFT",
                    1: "CENTER",
                    2: "RIGHT",
                }
            },
            value: this._node._textHorizontalAlignment,
        };
    },

    get verticalAlign() {
        return {
            path: "verticalAlign",
            type: "select",
            name: "VerAlign",
            attrs: {
                selects: {
                    0: "TOP",
                    1: "CENTER",
                    2: "BOTTOM",
                }
            },
            value: this._node._textVerticalAlignment,
        };
    },

    get oriOSize() {
        return {
            path: "oriOSize",
            type: "unit-input",
            name: "oriOSize",
            attrs: {
            },
            value: this._node._originConfig.osize,
        };
    },

    get oriColor() {
        let color = this._node._originConfig.color || cc.color.WHITE
        return {
            path: "oriColor",
            type: "color",
            name: "oriColor",
            attrs: {
            },
            value: {
                r: color.r,
                g: color.g,
                b: color.b,
                a: color.a
            }
        };
    },

    get oriOColor() {
        let color = this._node._originConfig.ocolor || cc.color.WHITE
        return {
            path: "oriOColor",
            type: "color",
            name: "oriOColor",
            attrs: {
            },
            value: {
                r: color.r,
                g: color.g,
                b: color.b,
                a: color.a
            }
        };
    },

    get __props__() {
        return [
            this.text,
            // this.fontName,
            this.oriFontSize,
            this.horizontalAlign,
            this.verticalAlign,
            this.oriColor,
            this.oriOSize,
            this.oriOColor,
        ];
    }
}

ExtRichText.RichTextData = RichTextData;

ExtRichText.PropComps = function(node) {
    let datas = [ new WidgetData(node) ];
    datas.push(new TouchData(node));
    datas.push(new RichTextData(node));
    return datas;
};

module.exports = ExtRichText;

RegisterExtNodeControl(ExtRichText.name, ExtRichText);
<|endoftext|>


<commit_before>'use strict';

/** @method score
 * @description Calculate credibility score
 * @param {Object} criteria - Object containing score criteria
 */
function score(criteria) {
	const https = (criteria.https) ? 10 : 0;
	const capitalization = (criteria.capitalization.title.flag) ? 0 : 10;
	const punctuation = (criteria.punctuation.flag) ? 0 : 10;
	const slander = (criteria.slander.flag) ? 0 : 10;
	const opensources = (criteria.opensourcesFlagged) ? 0 : 30;
	const webOfTrust = criteria.webOfTrust.reputation * 0.3;

	return https +
    capitalization +
    punctuation +
    slander +
    opensources +
    webOfTrust;
}

module.exports = {
	score: score
};
<commit_msg>Round score to nearest integer<commit_after>'use strict';

/** @method score
 * @description Calculate credibility score
 * @param {Object} criteria - Object containing score criteria
 */
function score(criteria) {
	const https = (criteria.https) ? 10 : 0;
	const capitalization = (criteria.capitalization.title.flag) ? 0 : 10;
	const punctuation = (criteria.punctuation.flag) ? 0 : 10;
	const slander = (criteria.slander.flag) ? 0 : 10;
	const opensources = (criteria.opensourcesFlagged) ? 0 : 30;
	const webOfTrust = criteria.webOfTrust.reputation * 0.3;

	const aggregate = Math.round(
		https +
		capitalization +
		punctuation +
		slander +
		opensources +
		webOfTrust
	);

	return aggregate;
}

module.exports = {
	score: score
};
<|endoftext|>


<commit_before>
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

define(["app/dom", "app/i18n", "app/utils", "diff_match_patch"], function($, i18n, utils) {

    "use strict";

    var mode = "reading";
    var article = $("article");
    var original_article = utils.clean_html(article.innerHTML);
    var new_article = null;

    var original_button = $.htmlify("<button>" + i18n.translate("show-original") + "</button>");

    // make a diff_match_patch object once and for all
    var dmp = new diff_match_patch();

    var init = function(comment_field) {
        // let's curry!
        return function() {
            if (mode === "reading" && comment_field.innerHTML !== "") {
                mode = "commenting";
                article.setAttribute("contenteditable", true);
            }
            else if (mode === "commenting" && comment_field.innerHTML === ""
                                           && new_article === null) {
                mode = "reading";
                article.setAttribute("contenteditable", false);
            }
        };
    };

    var maybe_article_just_changed = function() {
        var current = utils.clean_html(article.innerHTML);
        if (current !== original_article) {
            new_article = current;
        }
        else {
            new_article = null;
        }
    };

    var cancel = function() {
        if (mode === "commenting") {
            article.setAttribute("contenteditable", false);
            if (new_article !== null) {
                article.innerHTML = original_article;
                new_article = null;
            }
            mode = "reading";
        }
    };

    var show = function(comment) {
        // let's curry!
        return function() {
            if (mode === "reading" || mode === "reading_modification") {
                var array = JSON.parse(utils.tags_from_text(comment.edit));
                var html = dmp.diff_prettyHtml(array);
                article.innerHTML = utils.tags_from_text(html);

                // gray mask on everything other than current comment and article text
                $("body").style.background = "gray";
                article.style.background = "white";

		original_button.show();

                mode = "reading_modification";
            }
        };
    };

    var show_original = function() {
        if (mode === "reading_modification") {
            article.innerHTML = original_article;
            original_button.hide();
            mode = "reading";
        }
    };

    // define events
    original_button.on("click", show_original);
    article.on("keyup", maybe_article_just_changed);

    // add html elements
    original_button.hide();
    article.insertAfter(original_button);

    return {
        init: init,
        new_article: function() {
            var new_text = utils.tags_from_text(new_article);
            return JSON.stringify(dmp.diff_main(original_article,new_text));
        },
        cancel: cancel,
        show: show
    };
});

<commit_msg>Revert "gray out everything that is not modified text"<commit_after>
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

define(["app/dom", "app/i18n", "app/utils", "diff_match_patch"], function($, i18n, utils) {

    "use strict";

    var mode = "reading";
    var article = $("article");
    var original_article = utils.clean_html(article.innerHTML);
    var new_article = null;

    var original_button = $.htmlify("<button>" + i18n.translate("show-original") + "</button>");

    // make a diff_match_patch object once and for all
    var dmp = new diff_match_patch();

    var init = function(comment_field) {
        // let's curry!
        return function() {
            if (mode === "reading" && comment_field.innerHTML !== "") {
                mode = "commenting";
                article.setAttribute("contenteditable", true);
            }
            else if (mode === "commenting" && comment_field.innerHTML === ""
                                           && new_article === null) {
                mode = "reading";
                article.setAttribute("contenteditable", false);
            }
        };
    };

    var maybe_article_just_changed = function() {
        var current = utils.clean_html(article.innerHTML);
        if (current !== original_article) {
            new_article = current;
        }
        else {
            new_article = null;
        }
    };

    var cancel = function() {
        if (mode === "commenting") {
            article.setAttribute("contenteditable", false);
            if (new_article !== null) {
                article.innerHTML = original_article;
                new_article = null;
            }
            mode = "reading";
        }
    };

    var show = function(comment) {
        // let's curry!
        return function() {
            if (mode === "reading" || mode === "reading_modification") {
                var array = JSON.parse(utils.tags_from_text(comment.edit));
                var html = dmp.diff_prettyHtml(array);
                article.innerHTML = utils.tags_from_text(html);
		original_button.show();
                mode = "reading_modification";
            }
        };
    };

    var show_original = function() {
        if (mode === "reading_modification") {
            article.innerHTML = original_article;
            original_button.hide();
            mode = "reading";
        }
    };

    // define events
    original_button.on("click", show_original);
    article.on("keyup", maybe_article_just_changed);

    // add html elements
    original_button.hide();
    article.insertAfter(original_button);

    return {
        init: init,
        new_article: function() {
            var new_text = utils.tags_from_text(new_article);
            return JSON.stringify(dmp.diff_main(original_article,new_text));
        },
        cancel: cancel,
        show: show
    };
});

<|endoftext|>


<commit_before>/* jshint latedef:nofunc */
'use strict';

var $ = require('../util/jquery');
var oo = require('../util/oo');
var Coordinate = require('../model/Coordinate');
var Range = require('../model/Range');
var TextPropertyComponent = require('./TextPropertyComponent');

/*
 * A class that maps DOM selections to model selections.
 *
 * There are some difficulties with mapping model selections:
 * 1. DOM selections can not model discontinuous selections.
 * 2. Not all positions reachable via ContentEditable can be mapped to model selections. For instance,
 *    there are extra positions before and after non-editable child elements.
 * 3. Some native cursor behaviors need to be overidden.
 *
 * @class DOMSelection
 * @constructor
 * @param {Element} rootElement
 */
function DOMSelection(surface) {
  this.surface = surface;
}

function isElementInViewport (el) {
    if (typeof $ === "function" && el instanceof $) {
        el = el[0];
    }
    var rect = el.getBoundingClientRect();
    return (
        rect.top >= 70 && // 70 is adjustment for top navbar etc
        rect.bottom <= el.offsetParent.getBoundingClientRect().bottom - 20 // 20 is adjustment for bottom padding etc
    );
}

DOMSelection.Prototype = function() {

  /**
    Create a model selection by mapping the current DOM selection
    to model coordinates.

    @param {object} options
      - `direction`: `left` or `right`; a hint for disambiguations, used by Surface during cursor navigation.
    @returns {model/Selection}
  */
  this.getSelection = function(options) {
    var range = this.mapDOMSelection(options);
    var doc = this.surface.getDocument();
    return doc.createSelection(range);
  };

  /**
    Transfer a given model selection into the DOM.

    @param {model/Selection} sel
  */
  this.setSelection = function(sel) {
    // console.log('### renderSelection', sel.toString());
    var wSel = window.getSelection();
    if (sel.isNull() || sel.isTableSelection()) {
      this.clear();
      return;
    }
    var startComp = this.surface._getTextPropertyComponent(sel.startPath);
    if (!startComp) {
      console.warn('FIXME: selection seems to be invalid.');
      this.clear();
      return;
    }
    var start = startComp.getDOMCoordinate(sel.startOffset);
    var end;
    if (sel.isCollapsed()) {
      end = start;
    } else {
      var endComp = this.surface._getTextPropertyComponent(sel.endPath);
      if (!endComp) {
        console.warn('FIXME: selection seems to be invalid.');
        this.clear();
        return;
      }
      end = endComp.getDOMCoordinate(sel.endOffset);
    }
    // console.log('mapped to DOM coordinates', start.container, start.offset, end.container, end.offset, 'isReverse?', sel.isReverse());
    // if there is a range then set replace the window selection accordingly
    wSel.removeAllRanges();
    var wRange = window.document.createRange();
    if (sel.isCollapsed()) {
      wRange.setStart(start.container, start.offset);
      wSel.addRange(wRange);
      var el = document.createElement("span");
      el.id = 'curr-pos-span';
      wRange.insertNode(el);
      var visible = isElementInViewport(el);
      var offsetParent = $("#curr-pos-span").offsetParent()[0];
      var scrollAdjustment = 200;
      if ($('#curr-pos-span')[0].offsetTop < offsetParent.scrollTop){
        // scrolling up
        if (!visible) offsetParent.scrollTop = $('#curr-pos-span')[0].offsetTop - scrollAdjustment;
      } else {
        // scrolling down
        if (!visible) offsetParent.scrollTop = $('#curr-pos-span')[0].offsetTop - parseInt($(window).height()) + scrollAdjustment;
      }
      $('#curr-pos-span').remove();
    } else {
      if (sel.isReverse()) {
        // console.log('DOMSelection: rendering a reverse selection.');
        var tmp = start;
        start = end;
        end = tmp;
      }
      // HACK: using wRange setEnd does not work reliably
      // so we set just the start anchor
      // and then use window.Selection.extend()
      // unfortunately we are not able to test this behavior as it needs
      // triggering native keyboard events
      wRange.setStart(start.container, start.offset);
      wSel.addRange(wRange);
      wSel.extend(end.container, end.offset);
    }
    // console.log('DOMSelection.setSelection()', 'anchorNode:', wSel.anchorNode, 'anchorOffset:', wSel.anchorOffset, 'focusNode:', wSel.focusNode, 'focusOffset:', wSel.focusOffset, 'collapsed:', wSel.collapsed);
  };

  /*
    Map a DOM range to a model range.

    @param {Range} range
    @returns {model/Range}
  */
  this.mapDOMRange = function(wRange) {
    return this._getRange(wRange.startContainer, wRange.startOffset,
      wRange.endContainer, wRange.endOffset);
  };

  /*
    Maps the current DOM selection to a model range.

    @param {object} [options]
      - `direction`: `left` or `right`; a hint for disambiguations, used by Surface during cursor navigation.
    @returns {model/Range}
  */
  this.mapDOMSelection = function(options) {
    var range;
    var wSel = window.getSelection();
    // Use this log whenever the mapping goes wrong to analyze what
    // is actually being provided by the browser
    // console.log('DOMSelection.getSelection()', 'anchorNode:', wSel.anchorNode, 'anchorOffset:', wSel.anchorOffset, 'focusNode:', wSel.focusNode, 'focusOffset:', wSel.focusOffset, 'collapsed:', wSel.collapsed);
    if (wSel.rangeCount === 0) {
      return null;
    }
    if (wSel.isCollapsed) {
      var coor = this._getCoordinate(wSel.anchorNode, wSel.anchorOffset, options);
      range = _createRange(coor, coor, false, this.getContainerId());
    }
    // HACK: special treatment for edge cases as addressed by #354.
    // Sometimes anchorNode and focusNodes are the surface
    if ($(wSel.anchorNode).is('.surface')) {
      range = this._getEnclosingRange(wSel.getRangeAt(0));
    } else {
      range = this._getRange(wSel.anchorNode, wSel.anchorOffset, wSel.focusNode, wSel.focusOffset);
    }
    // console.log('### extracted range from DOM', range.toString());
    return range;
  };

  /*
    Clear the DOM selection.
  */
  this.clear = function() {
    window.getSelection().removeAllRanges();
  };

  this.getContainerId = function() {
    if (this.surface.isContainerEditor()) {
      return this.surface.getContainerId();
    } else {
      return null;
    }
  };

  /*
    Extract a model range from given DOM elements.

    @param {Node} anchorNode
    @param {number} anchorOffset
    @param {Node} focusNode
    @param {number} focusOffset
    @returns {model/Range}
  */
  this._getRange = function(anchorNode, anchorOffset, focusNode, focusOffset) {
    var start = this._getCoordinate(anchorNode, anchorOffset);
    var end;
    if (anchorNode === focusNode && anchorOffset === focusOffset) {
      end = start;
    } else {
      end = this._getCoordinate(focusNode, focusOffset);
    }
    var isReverse = _isReverse(anchorNode, anchorOffset, focusNode, focusOffset);
    if (start && end) {
      return _createRange(start, end, isReverse, this.getContainerId());
    } else {
      return null;
    }
  };

  /*
    Map a DOM coordinate to a model coordinate.

    @param {Node} node
    @param {number} offset
    @param {object} options
    @param {object} [options]
      - `direction`: `left` or `right`; a hint for disambiguation.
    @returns {model/Coordinate}

    @info

    `options.direction` can be used to control the result when this function is called
    after cursor navigation. The root problem is that we are using ContentEditable on
    Container level (as opposed to TextProperty level). The native ContentEditable allows
    cursor positions which do not make sense in the model sense.

    For example,

    ```
    <div contenteditable=true>
      <p data-path="p1.content">foo</p>
      <img>
      <p data-path="p1.content">bar</p>
    </div>
    ```
    would allow to set the cursor directly before or after the image, which
    we want to prevent, as it is not a valid insert position for text.
    Instead, if we find the DOM selection in such a situation, then we map it to the
    closest valid model address. And this depends on the direction of movement.
    Moving `left` would provide the previous address, `right` would provide the next address.
    The default direction is `right`.
  */
  this._getCoordinate = function(node, offset, options) {
    // Trying to apply the most common situation first
    // and after that covering known edge cases
    var coor = TextPropertyComponent.getCoordinate(this.surface.el, node, offset);
    if (!coor) {
      coor = this._searchForCoordinate(node, offset, options);
    }
    return coor;
  };

  /*
    Map a DOM coordinate to a model coordinate via a brute-force search
    on all properties.

    This is used as a backup strategy for delicate DOM selections.

    @param {Node} node
    @param {number} offset
    @param {object} options
    @param {'left'|'right'} options.direction
    @returns {model/Coordinate} the coordinate
  */
  this._searchForCoordinate = function(node, offset, options) {
    options = options || {};
    var elements = this.surface.el.querySelectorAll('*[data-path]');
    var idx, idx1, idx2, cmp1, cmp2;
    idx1 = 0;
    idx2 = elements.length-1;
    cmp1 = _compareNodes(elements[idx1], node);
    cmp2 = _compareNodes(elements[idx2], node);
    while(true) {
      var l = idx2-idx1+1;
      if (cmp2 < 0) {
        idx = idx2;
        break;
      } else if (cmp1 > 0) {
        idx = idx1;
        break;
      } else if (l<=2) {
        idx = idx2;
        break;
      }
      var pivotIdx = idx1 + Math.floor(l/2);
      var pivotCmp = _compareNodes(elements[pivotIdx], node);
      if (pivotCmp < 0) {
        idx1 = pivotIdx;
        cmp1 = pivotCmp;
      } else {
        idx2 = pivotIdx;
        cmp2 = pivotCmp;
      }
    }
    var charPos;
    var doc = this.surface.getDocument();
    var path = _getPath(elements[idx]);
    var text = doc.get(path);
    if (options.direction === "left") {
      if (idx === 0) {
        charPos = 0;
      } else {
        path = _getPath(elements[idx-1]);
        text = doc.get(path);
        charPos = text.length;
      }
    } else if (cmp2<0) {
      charPos = text.length;
    } else {
      charPos = 0;
    }
    return new Coordinate(path, charPos);
  };

  /*
    Computes a model range that encloses all properties
    spanned by a given DOM range.

    This is used in edge cases, where DOM selection anchors are not
    within TextProperties.

    @param {Range} range
    @returns {model/Range}
  */
  this._getEnclosingRange = function(wRange) {
    var frag = wRange.cloneContents();
    var props = frag.querySelectorAll('*[data-path]');
    if (props.length === 0) {
      return null;
    } else {
      var doc = this.surface.getDocument();
      var first = props[0];
      var last = props[props.length-1];
      var startPath = _getPath(first);
      var text;
      if (first === last) {
        text = doc.get(startPath);
        return new Range(
          new Coordinate(startPath, 0),
          new Coordinate(startPath, text.length),
          false
        );
      } else {
        var endPath = _getPath(last);
        text = doc.get(endPath);
        return new Range(
          new Coordinate(startPath, 0),
          new Coordinate(endPath, text.length),
          false
        );
      }
    }
  };

  function _compareNodes(node1, node2) {
    var cmp = node1.compareDocumentPosition(node2);
    // Note: the first two cases are necessary because POSITION_FOLLOWING
    // has strange semantics when the relationship is actually hierarchical
    if (cmp & window.document.DOCUMENT_POSITION_FOLLOWING) {
      return -1;
    } else if (cmp&window.document.DOCUMENT_POSITION_PRECEDING) {
      return 1;
    } else {
      return 0;
    }
  }

  var _r1, _r2;

  function _isReverse(anchorNode, anchorOffset, focusNode, focusOffset) {
    // the selection is reversed when the focus propertyEl is before
    // the anchor el or the computed charPos is in reverse order
    if (focusNode && anchorNode) {
      if (!_r1) {
        _r1 = window.document.createRange();
        _r2 = window.document.createRange();
      }
      _r1.setStart(anchorNode, anchorOffset);
      _r2.setStart(focusNode, focusOffset);
      var cmp = _r1.compareBoundaryPoints(window.Range.START_TO_START, _r2);
      if (cmp === 1) {
        return true;
      }
    }
    return false;
  }

  function _getPath(el) {
    if (el && el.dataset && el.dataset.path) {
      return el.dataset.path.split('.');
    }
  }

  /*
   Helper for creating a model range correctly
   as for model/Range start should be before end.

   In contrast to that, DOM selections are described with anchor and focus coordinates,
   i.e. bearing the information of direction implicitly.
   To simplify the implementation we treat anchor and focus equally
   and only at the end exploit the fact deriving an isReverse flag
   and bringing start and end in the correct order.
  */
  function _createRange(start, end, isReverse, containerId) {
    if (isReverse) {
      var tmp = start;
      start = end;
      end = tmp;
    }
    return new Range(start, end, isReverse, containerId);
  }

};

oo.initClass(DOMSelection);

module.exports = DOMSelection;
<commit_msg>Fix for selection update while navigating with arrow keys<commit_after>/* jshint latedef:nofunc */
'use strict';

var $ = require('../util/jquery');
var oo = require('../util/oo');
var Coordinate = require('../model/Coordinate');
var Range = require('../model/Range');
var TextPropertyComponent = require('./TextPropertyComponent');

/*
 * A class that maps DOM selections to model selections.
 *
 * There are some difficulties with mapping model selections:
 * 1. DOM selections can not model discontinuous selections.
 * 2. Not all positions reachable via ContentEditable can be mapped to model selections. For instance,
 *    there are extra positions before and after non-editable child elements.
 * 3. Some native cursor behaviors need to be overidden.
 *
 * @class DOMSelection
 * @constructor
 * @param {Element} rootElement
 */
function DOMSelection(surface) {
  this.surface = surface;
}

function isElementInViewport (el) {
    if (typeof $ === "function" && el instanceof $) {
        el = el[0];
    }
    var rect = el.getBoundingClientRect();
    return (
        rect.top >= 70 && // 70 is adjustment for top navbar etc
        rect.bottom <= el.offsetParent.getBoundingClientRect().bottom - 20 // 20 is adjustment for bottom padding etc
    );
}

DOMSelection.Prototype = function() {

  /**
    Create a model selection by mapping the current DOM selection
    to model coordinates.

    @param {object} options
      - `direction`: `left` or `right`; a hint for disambiguations, used by Surface during cursor navigation.
    @returns {model/Selection}
  */
  this.getSelection = function(options) {
    var range = this.mapDOMSelection(options);
    var doc = this.surface.getDocument();
    return doc.createSelection(range);
  };

  /**
    Transfer a given model selection into the DOM.

    @param {model/Selection} sel
  */
  this.setSelection = function(sel) {
    // console.log('### renderSelection', sel.toString());
    var wSel = window.getSelection();
    if (sel.isNull() || sel.isTableSelection()) {
      this.clear();
      return;
    }
    var startComp = this.surface._getTextPropertyComponent(sel.startPath);
    if (!startComp) {
      console.warn('FIXME: selection seems to be invalid.');
      this.clear();
      return;
    }
    var start = startComp.getDOMCoordinate(sel.startOffset);
    var end;
    if (sel.isCollapsed()) {
      end = start;
    } else {
      var endComp = this.surface._getTextPropertyComponent(sel.endPath);
      if (!endComp) {
        console.warn('FIXME: selection seems to be invalid.');
        this.clear();
        return;
      }
      end = endComp.getDOMCoordinate(sel.endOffset);
    }
    // console.log('mapped to DOM coordinates', start.container, start.offset, end.container, end.offset, 'isReverse?', sel.isReverse());
    // if there is a range then set replace the window selection accordingly
    wSel.removeAllRanges();
    var wRange = window.document.createRange();
    if (sel.isCollapsed()) {
      wRange.setStart(start.container, start.offset);
      wSel.addRange(wRange);
      var el = document.createElement("span");
      el.id = 'curr-pos-span';
      wRange.insertNode(el);
      var visible = isElementInViewport(el);
      var offsetParent = $("#curr-pos-span").offsetParent()[0];
      var scrollAdjustment = 200;
      if ($('#curr-pos-span')[0].offsetTop < offsetParent.scrollTop){
        // scrolling up
        if (!visible) offsetParent.scrollTop = $('#curr-pos-span')[0].offsetTop - scrollAdjustment;
      } else {
        // scrolling down
        if (!visible) offsetParent.scrollTop = $('#curr-pos-span')[0].offsetTop - parseInt($(window).height()) + scrollAdjustment;
      }
      $('#curr-pos-span').remove();
    } else {
      if (sel.isReverse()) {
        // console.log('DOMSelection: rendering a reverse selection.');
        var tmp = start;
        start = end;
        end = tmp;
      }
      // HACK: using wRange setEnd does not work reliably
      // so we set just the start anchor
      // and then use window.Selection.extend()
      // unfortunately we are not able to test this behavior as it needs
      // triggering native keyboard events
      wRange.setStart(start.container, start.offset);
      wSel.addRange(wRange);
      wSel.extend(end.container, end.offset);
    }
    // console.log('DOMSelection.setSelection()', 'anchorNode:', wSel.anchorNode, 'anchorOffset:', wSel.anchorOffset, 'focusNode:', wSel.focusNode, 'focusOffset:', wSel.focusOffset, 'collapsed:', wSel.collapsed);
  };

  /*
    Map a DOM range to a model range.

    @param {Range} range
    @returns {model/Range}
  */
  this.mapDOMRange = function(wRange) {
    return this._getRange(wRange.startContainer, wRange.startOffset,
      wRange.endContainer, wRange.endOffset);
  };

  /*
    Maps the current DOM selection to a model range.

    @param {object} [options]
      - `direction`: `left` or `right`; a hint for disambiguations, used by Surface during cursor navigation.
    @returns {model/Range}
  */
  this.mapDOMSelection = function(options) {
    var range;
    var wSel = window.getSelection();
    // Use this log whenever the mapping goes wrong to analyze what
    // is actually being provided by the browser
    // console.log('DOMSelection.getSelection()', 'anchorNode:', wSel.anchorNode, 'anchorOffset:', wSel.anchorOffset, 'focusNode:', wSel.focusNode, 'focusOffset:', wSel.focusOffset, 'collapsed:', wSel.collapsed);
    if (wSel.rangeCount === 0) {
      return null;
    }
    if (wSel.isCollapsed) {
      var coor = this._getCoordinate(wSel.anchorNode, wSel.anchorOffset, options);
      range = _createRange(coor, coor, false, this.getContainerId());
    }
    // HACK: special treatment for edge cases as addressed by #354.
    // Sometimes anchorNode and focusNodes are the surface
    if ($(wSel.anchorNode).is('.surface')) {
      range = this._getEnclosingRange(wSel.getRangeAt(0));
    }
    // console.log('### extracted range from DOM', range.toString());
    return range;
  };

  /*
    Clear the DOM selection.
  */
  this.clear = function() {
    window.getSelection().removeAllRanges();
  };

  this.getContainerId = function() {
    if (this.surface.isContainerEditor()) {
      return this.surface.getContainerId();
    } else {
      return null;
    }
  };

  /*
    Extract a model range from given DOM elements.

    @param {Node} anchorNode
    @param {number} anchorOffset
    @param {Node} focusNode
    @param {number} focusOffset
    @returns {model/Range}
  */
  this._getRange = function(anchorNode, anchorOffset, focusNode, focusOffset) {
    var start = this._getCoordinate(anchorNode, anchorOffset);
    var end;
    if (anchorNode === focusNode && anchorOffset === focusOffset) {
      end = start;
    } else {
      end = this._getCoordinate(focusNode, focusOffset);
    }
    var isReverse = _isReverse(anchorNode, anchorOffset, focusNode, focusOffset);
    if (start && end) {
      return _createRange(start, end, isReverse, this.getContainerId());
    } else {
      return null;
    }
  };

  /*
    Map a DOM coordinate to a model coordinate.

    @param {Node} node
    @param {number} offset
    @param {object} options
    @param {object} [options]
      - `direction`: `left` or `right`; a hint for disambiguation.
    @returns {model/Coordinate}

    @info

    `options.direction` can be used to control the result when this function is called
    after cursor navigation. The root problem is that we are using ContentEditable on
    Container level (as opposed to TextProperty level). The native ContentEditable allows
    cursor positions which do not make sense in the model sense.

    For example,

    ```
    <div contenteditable=true>
      <p data-path="p1.content">foo</p>
      <img>
      <p data-path="p1.content">bar</p>
    </div>
    ```
    would allow to set the cursor directly before or after the image, which
    we want to prevent, as it is not a valid insert position for text.
    Instead, if we find the DOM selection in such a situation, then we map it to the
    closest valid model address. And this depends on the direction of movement.
    Moving `left` would provide the previous address, `right` would provide the next address.
    The default direction is `right`.
  */
  this._getCoordinate = function(node, offset, options) {
    // Trying to apply the most common situation first
    // and after that covering known edge cases
    var coor = TextPropertyComponent.getCoordinate(this.surface.el, node, offset);
    if (!coor) {
      coor = this._searchForCoordinate(node, offset, options);
    }
    return coor;
  };

  /*
    Map a DOM coordinate to a model coordinate via a brute-force search
    on all properties.

    This is used as a backup strategy for delicate DOM selections.

    @param {Node} node
    @param {number} offset
    @param {object} options
    @param {'left'|'right'} options.direction
    @returns {model/Coordinate} the coordinate
  */
  this._searchForCoordinate = function(node, offset, options) {
    options = options || {};
    var elements = this.surface.el.querySelectorAll('*[data-path]');
    var idx, idx1, idx2, cmp1, cmp2;
    idx1 = 0;
    idx2 = elements.length-1;
    cmp1 = _compareNodes(elements[idx1], node);
    cmp2 = _compareNodes(elements[idx2], node);
    while(true) {
      var l = idx2-idx1+1;
      if (cmp2 < 0) {
        idx = idx2;
        break;
      } else if (cmp1 > 0) {
        idx = idx1;
        break;
      } else if (l<=2) {
        idx = idx2;
        break;
      }
      var pivotIdx = idx1 + Math.floor(l/2);
      var pivotCmp = _compareNodes(elements[pivotIdx], node);
      if (pivotCmp < 0) {
        idx1 = pivotIdx;
        cmp1 = pivotCmp;
      } else {
        idx2 = pivotIdx;
        cmp2 = pivotCmp;
      }
    }
    var charPos;
    var doc = this.surface.getDocument();
    var path = _getPath(elements[idx]);
    var text = doc.get(path);
    if (options.direction === "left") {
      if (idx === 0) {
        charPos = 0;
      } else {
        path = _getPath(elements[idx-1]);
        text = doc.get(path);
        charPos = text.length;
      }
    } else if (cmp2<0) {
      charPos = text.length;
    } else {
      charPos = 0;
    }
    return new Coordinate(path, charPos);
  };

  /*
    Computes a model range that encloses all properties
    spanned by a given DOM range.

    This is used in edge cases, where DOM selection anchors are not
    within TextProperties.

    @param {Range} range
    @returns {model/Range}
  */
  this._getEnclosingRange = function(wRange) {
    var frag = wRange.cloneContents();
    var props = frag.querySelectorAll('*[data-path]');
    if (props.length === 0) {
      return null;
    } else {
      var doc = this.surface.getDocument();
      var first = props[0];
      var last = props[props.length-1];
      var startPath = _getPath(first);
      var text;
      if (first === last) {
        text = doc.get(startPath);
        return new Range(
          new Coordinate(startPath, 0),
          new Coordinate(startPath, text.length),
          false
        );
      } else {
        var endPath = _getPath(last);
        text = doc.get(endPath);
        return new Range(
          new Coordinate(startPath, 0),
          new Coordinate(endPath, text.length),
          false
        );
      }
    }
  };

  function _compareNodes(node1, node2) {
    var cmp = node1.compareDocumentPosition(node2);
    // Note: the first two cases are necessary because POSITION_FOLLOWING
    // has strange semantics when the relationship is actually hierarchical
    if (cmp & window.document.DOCUMENT_POSITION_FOLLOWING) {
      return -1;
    } else if (cmp&window.document.DOCUMENT_POSITION_PRECEDING) {
      return 1;
    } else {
      return 0;
    }
  }

  var _r1, _r2;

  function _isReverse(anchorNode, anchorOffset, focusNode, focusOffset) {
    // the selection is reversed when the focus propertyEl is before
    // the anchor el or the computed charPos is in reverse order
    if (focusNode && anchorNode) {
      if (!_r1) {
        _r1 = window.document.createRange();
        _r2 = window.document.createRange();
      }
      _r1.setStart(anchorNode, anchorOffset);
      _r2.setStart(focusNode, focusOffset);
      var cmp = _r1.compareBoundaryPoints(window.Range.START_TO_START, _r2);
      if (cmp === 1) {
        return true;
      }
    }
    return false;
  }

  function _getPath(el) {
    if (el && el.dataset && el.dataset.path) {
      return el.dataset.path.split('.');
    }
  }

  /*
   Helper for creating a model range correctly
   as for model/Range start should be before end.

   In contrast to that, DOM selections are described with anchor and focus coordinates,
   i.e. bearing the information of direction implicitly.
   To simplify the implementation we treat anchor and focus equally
   and only at the end exploit the fact deriving an isReverse flag
   and bringing start and end in the correct order.
  */
  function _createRange(start, end, isReverse, containerId) {
    if (isReverse) {
      var tmp = start;
      start = end;
      end = tmp;
    }
    return new Range(start, end, isReverse, containerId);
  }

};

oo.initClass(DOMSelection);

module.exports = DOMSelection;
<|endoftext|>


<commit_before>import SimpleSchema from 'simpl-schema';

// canAutofillDefault: Marks a field where, if its value is null, it should
// be auto-replaced with defaultValue in migration scripts.
SimpleSchema.extendOptions([ 'canAutofillDefault' ]);

// denormalized: In a schema entry, denormalized:true means that this field can
// (in principle) be regenerated from other fields. For now, it's a glorified
// machine-readable comment; in the future, it may have other infrastructure
// attached.
SimpleSchema.extendOptions([ 'denormalized' ]);

// foreignKey: In a schema entry, this is either an object {collection,field},
// or just a string, in which case the string is the collection name and field
// is _id. Indicates that if this field is present and not null, its value
// must correspond to an existing row in the named collection. For example,
//
//   foreignKey: 'Users'
//   means that the value of this field must be the _id of a user;
//
//   foreignKey: {
//     collection: 'Posts',
//     field: 'slug'
//   }
//   means that the value of this field must be the slug of a post.
//
SimpleSchema.extendOptions([ 'foreignKey' ]);

export const expectedIndexes = {};

// Returns true if the specified index has a name, and the collection has an
// existing index with the same name but different columns or options.
async function conflictingIndexExists(collection, index, options)
{
  if (!options.name)
    return false;

  let existingIndexes = await collection.rawCollection().indexes();

  for (let existingIndex of existingIndexes) {
    if (existingIndex.name === options.name) {
      if (!_.isEqual(existingIndex.key, index)
         || !_.isEqual(existingIndex.partialFilterExpression, options.partialFilterExpression))
      {
        return true;
      }
    }
  }

  return false;
}

export async function ensureIndex(collection, index, options={})
{
  if (Meteor.isServer) {
    const buildIndex = async () => {
      try {
        if (options.name && await conflictingIndexExists(collection, index, options)) {
          //eslint-disable-next-line no-console
          console.log(`Differing index exists with the same name: ${options.name}. Dropping.`);
          collection.rawCollection().dropIndex(options.name);
        }

        const mergedOptions = {background: true, ...options};
        collection._ensureIndex(index, mergedOptions);

        if (!expectedIndexes[collection.collectionName])
          expectedIndexes[collection.collectionName] = [];
        expectedIndexes[collection.collectionName].push({
          key: index,
          partialFilterExpression: options.partialFilterExpression,
        });
      } catch(e) {
        //eslint-disable-next-line no-console
        console.error(`Error in ${collection.collectionName}.ensureIndex: ${e}`);
      }
    };

    // If running a normal server, defer index creation until 15s after
    // startup. This speeds up testing in the common case, where indexes haven't
    // meaningfully changed (but sending a bunch of no-op ensureIndex commands
    // to the database is still expensive).
    // In unit tests, build indexes immediately, because (a) indexes probably
    // don't exist yet, and (b) building indexes in the middle of a later test
    // risks making that test time out.
    if (Meteor.isTest || Meteor.isAppTest || Meteor.isPackageTest) {
      await buildIndex();
    } else {
      Meteor.setTimeout(buildIndex, 15000);
    }
  }
}

// Given an index partial definition for a collection's default view,
// represented as an index field-list prefix and suffix, plus an index partial
// definition for a specific view on the same collection, combine them into
// a full index definition.
//
// When defining an index prefix/suffix for a default view, every field that is
// in the selector should be in either the prefix or the suffix. If the
// selector is a simple one (a regular value), it should be in the prefix; if
// it's a complex one (an operator), it should be in the suffix. If a field
// appears twice (in both the prefix and the view-specific index, or both the
// view-specific index and the suffix), it will be included only in the first
// position where it appears.
//
//   viewFields: [ordered dictionary] Collection fields from a specific view
//   prefix: [ordered dictionary] Collection fields from the default view
//   suffix: [ordered dictionary] Collection fields from the default view
//
export function combineIndexWithDefaultViewIndex({viewFields, prefix, suffix})
{
  let combinedIndex = {...prefix};
  for (let key in viewFields) {
    if (!(key in combinedIndex))
      combinedIndex[key] = viewFields[key];
  }
  for (let key in suffix) {
    if (!(key in combinedIndex))
      combinedIndex[key] = suffix[key];
  }
  return combinedIndex;
}

export function schemaDefaultValue(defaultValue) {
  // Used for both onCreate and onUpdate
  const fillIfMissing = ({newDocument, fieldName}) => {
    if (newDocument[fieldName] === undefined) {
      return defaultValue;
    } else {
      return undefined;
    }
  };
  const throwIfSetToNull = ({oldDocument, document, fieldName}) => {
    const wasValid = (oldDocument[fieldName] !== undefined && oldDocument[fieldName] !== null);
    const isValid = (document[fieldName] !== undefined && document[fieldName] !== null);
    if (wasValid && !isValid) {
      throw new Error(`Error updating: ${fieldName} cannot be null or missing`);
    }
  };

  return {
    defaultValue: defaultValue,
    onCreate: fillIfMissing,
    onUpdate: throwIfSetToNull,
    canAutofillDefault: true,
  }
}

export function addUniversalFields({ collection, schemaVersion=1 }) {
  collection.addField([
    {
      fieldName: 'schemaVersion',
      fieldSchema: {
        type: Number,
        canRead: ['guests'],
        optional: true,
        ...schemaDefaultValue(schemaVersion),
        onUpdate: () => schemaVersion
      }
    }
  ])
  ensureIndex(collection, {schemaVersion: 1});
}

export function isUnbackedCollection(collection)
{
  if (collection.collectionName === 'Settings' || collection.collectionName === 'Callbacks') {
    // Vulcan collections with no backing database table
    return true;
  }

  return false;
}
<commit_msg>Fix error when creating indexes in an unitialized DB<commit_after>import SimpleSchema from 'simpl-schema';

// canAutofillDefault: Marks a field where, if its value is null, it should
// be auto-replaced with defaultValue in migration scripts.
SimpleSchema.extendOptions([ 'canAutofillDefault' ]);

// denormalized: In a schema entry, denormalized:true means that this field can
// (in principle) be regenerated from other fields. For now, it's a glorified
// machine-readable comment; in the future, it may have other infrastructure
// attached.
SimpleSchema.extendOptions([ 'denormalized' ]);

// foreignKey: In a schema entry, this is either an object {collection,field},
// or just a string, in which case the string is the collection name and field
// is _id. Indicates that if this field is present and not null, its value
// must correspond to an existing row in the named collection. For example,
//
//   foreignKey: 'Users'
//   means that the value of this field must be the _id of a user;
//
//   foreignKey: {
//     collection: 'Posts',
//     field: 'slug'
//   }
//   means that the value of this field must be the slug of a post.
//
SimpleSchema.extendOptions([ 'foreignKey' ]);

export const expectedIndexes = {};

// Returns true if the specified index has a name, and the collection has an
// existing index with the same name but different columns or options.
async function conflictingIndexExists(collection, index, options)
{
  if (!options.name)
    return false;

  let existingIndexes;
  try {
    existingIndexes = await collection.rawCollection().indexes();
  } catch(e) {
    // If the database is uninitialized (eg, running unit tests starting with a
    // blank DB), this will fail. But the collection will be created by the
    // ensureIndex operation.
    return false;
  }

  for (let existingIndex of existingIndexes) {
    if (existingIndex.name === options.name) {
      if (!_.isEqual(existingIndex.key, index)
         || !_.isEqual(existingIndex.partialFilterExpression, options.partialFilterExpression))
      {
        return true;
      }
    }
  }

  return false;
}

export async function ensureIndex(collection, index, options={})
{
  if (Meteor.isServer) {
    const buildIndex = async () => {
      try {
        if (options.name && await conflictingIndexExists(collection, index, options)) {
          //eslint-disable-next-line no-console
          console.log(`Differing index exists with the same name: ${options.name}. Dropping.`);
          collection.rawCollection().dropIndex(options.name);
        }

        const mergedOptions = {background: true, ...options};
        collection._ensureIndex(index, mergedOptions);

        if (!expectedIndexes[collection.collectionName])
          expectedIndexes[collection.collectionName] = [];
        expectedIndexes[collection.collectionName].push({
          key: index,
          partialFilterExpression: options.partialFilterExpression,
        });
      } catch(e) {
        //eslint-disable-next-line no-console
        console.error(`Error in ${collection.collectionName}.ensureIndex: ${e}`);
      }
    };

    // If running a normal server, defer index creation until 15s after
    // startup. This speeds up testing in the common case, where indexes haven't
    // meaningfully changed (but sending a bunch of no-op ensureIndex commands
    // to the database is still expensive).
    // In unit tests, build indexes immediately, because (a) indexes probably
    // don't exist yet, and (b) building indexes in the middle of a later test
    // risks making that test time out.
    if (Meteor.isTest || Meteor.isAppTest || Meteor.isPackageTest) {
      await buildIndex();
    } else {
      Meteor.setTimeout(buildIndex, 15000);
    }
  }
}

// Given an index partial definition for a collection's default view,
// represented as an index field-list prefix and suffix, plus an index partial
// definition for a specific view on the same collection, combine them into
// a full index definition.
//
// When defining an index prefix/suffix for a default view, every field that is
// in the selector should be in either the prefix or the suffix. If the
// selector is a simple one (a regular value), it should be in the prefix; if
// it's a complex one (an operator), it should be in the suffix. If a field
// appears twice (in both the prefix and the view-specific index, or both the
// view-specific index and the suffix), it will be included only in the first
// position where it appears.
//
//   viewFields: [ordered dictionary] Collection fields from a specific view
//   prefix: [ordered dictionary] Collection fields from the default view
//   suffix: [ordered dictionary] Collection fields from the default view
//
export function combineIndexWithDefaultViewIndex({viewFields, prefix, suffix})
{
  let combinedIndex = {...prefix};
  for (let key in viewFields) {
    if (!(key in combinedIndex))
      combinedIndex[key] = viewFields[key];
  }
  for (let key in suffix) {
    if (!(key in combinedIndex))
      combinedIndex[key] = suffix[key];
  }
  return combinedIndex;
}

export function schemaDefaultValue(defaultValue) {
  // Used for both onCreate and onUpdate
  const fillIfMissing = ({newDocument, fieldName}) => {
    if (newDocument[fieldName] === undefined) {
      return defaultValue;
    } else {
      return undefined;
    }
  };
  const throwIfSetToNull = ({oldDocument, document, fieldName}) => {
    const wasValid = (oldDocument[fieldName] !== undefined && oldDocument[fieldName] !== null);
    const isValid = (document[fieldName] !== undefined && document[fieldName] !== null);
    if (wasValid && !isValid) {
      throw new Error(`Error updating: ${fieldName} cannot be null or missing`);
    }
  };

  return {
    defaultValue: defaultValue,
    onCreate: fillIfMissing,
    onUpdate: throwIfSetToNull,
    canAutofillDefault: true,
  }
}

export function addUniversalFields({ collection, schemaVersion=1 }) {
  collection.addField([
    {
      fieldName: 'schemaVersion',
      fieldSchema: {
        type: Number,
        canRead: ['guests'],
        optional: true,
        ...schemaDefaultValue(schemaVersion),
        onUpdate: () => schemaVersion
      }
    }
  ])
  ensureIndex(collection, {schemaVersion: 1});
}

export function isUnbackedCollection(collection)
{
  if (collection.collectionName === 'Settings' || collection.collectionName === 'Callbacks') {
    // Vulcan collections with no backing database table
    return true;
  }

  return false;
}
<|endoftext|>


<commit_before>// Copyright 2010 WebDriver committers
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for working with errors as defined by WebDriver's
 * wire protocol: http://code.google.com/p/selenium/wiki/JsonWireProtocol.
 */

goog.provide('bot.Error');
goog.provide('bot.ErrorCode');

goog.require('goog.debug.Error');
goog.require('goog.object');

/**
 * Error codes from the WebDriver wire protocol:
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
 *
 * @enum {number}
 */
bot.ErrorCode = {
  SUCCESS: 0,  // Included for completeness

  NO_SUCH_ELEMENT: 7,
  NO_SUCH_FRAME: 8,
  UNKNOWN_COMMAND: 9,
  UNSUPPORTED_OPERATION: 9,  // Alias.
  STALE_ELEMENT_REFERENCE: 10,
  ELEMENT_NOT_VISIBLE: 11,
  INVALID_ELEMENT_STATE: 12,
  UNKNOWN_ERROR: 13,
  ELEMENT_NOT_SELECTABLE: 15,
  JAVASCRIPT_ERROR: 17,
  XPATH_LOOKUP_ERROR: 19,
  NO_SUCH_WINDOW: 23,
  INVALID_COOKIE_DOMAIN: 24,
  UNABLE_TO_SET_COOKIE: 25,
  MODAL_DIALOG_OPENED: 26,
  NO_MODAL_DIALOG_OPEN: 27,
  SCRIPT_TIMEOUT: 28,
  INVALID_SELECTOR_ERROR: 32,
  NO_ALERT_PRESENT: 33
};

/**
 * Error extension that includes error status codes from the WebDriver wire
 * protocol:
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
 *
 * @param {!bot.ErrorCode} code The error's status code.
 * @param {string} opt_message Optional error message.
 * @constructor
 * @extends {goog.debug.Error}
 */
bot.Error = function(code, opt_message) {
  goog.base(this, opt_message);

  /**
   * This error's status code.
   * @type {!bot.ErrorCode}
   */
  this.code = code;

  /** @inheritDoc */
  this.name = (/**@type {string}*/ bot.Error.NAMES_[code] ||
      bot.Error.NAMES_[bot.ErrorCode.UNKNOWN_ERROR]);
};
goog.inherits(bot.Error, goog.debug.Error);

/**
 * A map of error codes to error names.
 * @type {Object.<bot.ErrorCode, String>}
 * @const
 * @private
 */
bot.Error.NAMES_ = goog.object.transpose({
  'NoSuchElementError': bot.ErrorCode.NO_SUCH_ELEMENT,
  'NoSuchFrameError': bot.ErrorCode.NO_SUCH_FRAME,
  'UnknownCommandError': bot.ErrorCode.UNKNOWN_COMMAND,
  'StaleElementReferenceError': bot.ErrorCode.STALE_ELEMENT_REFERENCE,
  'ElementNotVisibleError': bot.ErrorCode.ELEMENT_NOT_VISIBLE,
  'InvalidElementStateError': bot.ErrorCode.INVALID_ELEMENT_STATE,
  'UnknownError': bot.ErrorCode.UNKNOWN_ERROR,
  'ElementNotSelectableError': bot.ErrorCode.ELEMENT_NOT_SELECTABLE,
  'XPathLookupError': bot.ErrorCode.XPATH_LOOKUP_ERROR,
  'NoSuchWindowError': bot.ErrorCode.NO_SUCH_WINDOW,
  'InvalidCookieDomainError': bot.ErrorCode.INVALID_COOKIE_DOMAIN,
  'UnableToSetCookieError': bot.ErrorCode.UNABLE_TO_SET_COOKIE,
  'ModalDialogOpenedError': bot.ErrorCode.MODAL_DIALOG_OPENED,
  'NoModalDialogOpenError': bot.ErrorCode.NO_MODAL_DIALOG_OPEN,
  'ScriptTimeoutError': bot.ErrorCode.SCRIPT_TIMEOUT
});

/**
 * Flag used for duck-typing when this code is embedded in a Firefox extension.
 * This is required since an Error thrown in one component and the reported
 * to another will fail instanceof checks in the second component.
 * @type {boolean}
 */
bot.Error.prototype.isAutomationError = true;

if (goog.DEBUG) {
  /** @return {string} The string representation of this error. */
  bot.Error.prototype.toString = function() {
    return '[' + this.name + '] ' + this.message;
  };
}
<commit_msg>DanielWagnerHall: Removing spurious error code<commit_after>// Copyright 2010 WebDriver committers
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for working with errors as defined by WebDriver's
 * wire protocol: http://code.google.com/p/selenium/wiki/JsonWireProtocol.
 */

goog.provide('bot.Error');
goog.provide('bot.ErrorCode');

goog.require('goog.debug.Error');
goog.require('goog.object');

/**
 * Error codes from the WebDriver wire protocol:
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
 *
 * @enum {number}
 */
bot.ErrorCode = {
  SUCCESS: 0,  // Included for completeness

  NO_SUCH_ELEMENT: 7,
  NO_SUCH_FRAME: 8,
  UNKNOWN_COMMAND: 9,
  UNSUPPORTED_OPERATION: 9,  // Alias.
  STALE_ELEMENT_REFERENCE: 10,
  ELEMENT_NOT_VISIBLE: 11,
  INVALID_ELEMENT_STATE: 12,
  UNKNOWN_ERROR: 13,
  ELEMENT_NOT_SELECTABLE: 15,
  JAVASCRIPT_ERROR: 17,
  XPATH_LOOKUP_ERROR: 19,
  NO_SUCH_WINDOW: 23,
  INVALID_COOKIE_DOMAIN: 24,
  UNABLE_TO_SET_COOKIE: 25,
  MODAL_DIALOG_OPENED: 26,
  NO_MODAL_DIALOG_OPEN: 27,
  SCRIPT_TIMEOUT: 28,
  INVALID_SELECTOR_ERROR: 32
};

/**
 * Error extension that includes error status codes from the WebDriver wire
 * protocol:
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
 *
 * @param {!bot.ErrorCode} code The error's status code.
 * @param {string} opt_message Optional error message.
 * @constructor
 * @extends {goog.debug.Error}
 */
bot.Error = function(code, opt_message) {
  goog.base(this, opt_message);

  /**
   * This error's status code.
   * @type {!bot.ErrorCode}
   */
  this.code = code;

  /** @inheritDoc */
  this.name = (/**@type {string}*/ bot.Error.NAMES_[code] ||
      bot.Error.NAMES_[bot.ErrorCode.UNKNOWN_ERROR]);
};
goog.inherits(bot.Error, goog.debug.Error);

/**
 * A map of error codes to error names.
 * @type {Object.<bot.ErrorCode, String>}
 * @const
 * @private
 */
bot.Error.NAMES_ = goog.object.transpose({
  'NoSuchElementError': bot.ErrorCode.NO_SUCH_ELEMENT,
  'NoSuchFrameError': bot.ErrorCode.NO_SUCH_FRAME,
  'UnknownCommandError': bot.ErrorCode.UNKNOWN_COMMAND,
  'StaleElementReferenceError': bot.ErrorCode.STALE_ELEMENT_REFERENCE,
  'ElementNotVisibleError': bot.ErrorCode.ELEMENT_NOT_VISIBLE,
  'InvalidElementStateError': bot.ErrorCode.INVALID_ELEMENT_STATE,
  'UnknownError': bot.ErrorCode.UNKNOWN_ERROR,
  'ElementNotSelectableError': bot.ErrorCode.ELEMENT_NOT_SELECTABLE,
  'XPathLookupError': bot.ErrorCode.XPATH_LOOKUP_ERROR,
  'NoSuchWindowError': bot.ErrorCode.NO_SUCH_WINDOW,
  'InvalidCookieDomainError': bot.ErrorCode.INVALID_COOKIE_DOMAIN,
  'UnableToSetCookieError': bot.ErrorCode.UNABLE_TO_SET_COOKIE,
  'ModalDialogOpenedError': bot.ErrorCode.MODAL_DIALOG_OPENED,
  'NoModalDialogOpenError': bot.ErrorCode.NO_MODAL_DIALOG_OPEN,
  'ScriptTimeoutError': bot.ErrorCode.SCRIPT_TIMEOUT
});

/**
 * Flag used for duck-typing when this code is embedded in a Firefox extension.
 * This is required since an Error thrown in one component and the reported
 * to another will fail instanceof checks in the second component.
 * @type {boolean}
 */
bot.Error.prototype.isAutomationError = true;

if (goog.DEBUG) {
  /** @return {string} The string representation of this error. */
  bot.Error.prototype.toString = function() {
    return '[' + this.name + '] ' + this.message;
  };
}
<|endoftext|>


<commit_before>// Copyright 2010 WebDriver committers
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for working with errors as defined by WebDriver's
 * wire protocol: http://code.google.com/p/selenium/wiki/JsonWireProtocol.
 */

goog.provide('bot.Error');
goog.provide('bot.ErrorCode');

goog.require('goog.object');

/**
 * Error codes from the WebDriver wire protocol:
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
 *
 * @enum {number}
 */
bot.ErrorCode = {
  SUCCESS: 0,  // Included for completeness

  NO_SUCH_ELEMENT: 7,
  NO_SUCH_FRAME: 8,
  UNKNOWN_COMMAND: 9,
  UNSUPPORTED_OPERATION: 9,  // Alias.
  STALE_ELEMENT_REFERENCE: 10,
  ELEMENT_NOT_VISIBLE: 11,
  INVALID_ELEMENT_STATE: 12,
  UNKNOWN_ERROR: 13,
  ELEMENT_NOT_SELECTABLE: 15,
  JAVASCRIPT_ERROR: 17,
  XPATH_LOOKUP_ERROR: 19,
  TIMEOUT: 21,
  NO_SUCH_WINDOW: 23,
  INVALID_COOKIE_DOMAIN: 24,
  UNABLE_TO_SET_COOKIE: 25,
  MODAL_DIALOG_OPENED: 26,
  NO_MODAL_DIALOG_OPEN: 27,
  SCRIPT_TIMEOUT: 28,
  INVALID_ELEMENT_COORDINATES: 29,
  INVALID_SELECTOR_ERROR: 32,
  SQL_DATABASE_ERROR: 33,
  MOVE_TARGET_OUT_OF_BOUNDS: 34,
  IME_ENGINE_ACTIVATION_FAILED: 35,
  IME_NOT_AVAILABLE: 36
};

/**
 * Error extension that includes error status codes from the WebDriver wire
 * protocol:
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
 *
 * @param {!bot.ErrorCode} code The error's status code.
 * @param {string=} opt_message Optional error message.
 * @constructor
 * @extends {Error}
 */
bot.Error = function(code, opt_message) {

  /**
   * This error's status code.
   * @type {!bot.ErrorCode}
   */
  this.code = code;

  /** @override */
  this.message = opt_message || '';

  /** @override */
  this.name = (/**@type {string}*/ bot.Error.NAMES_[code] ||
      bot.Error.NAMES_[bot.ErrorCode.UNKNOWN_ERROR]);

  // Generate a stacktrace for our custom error; ensure the error has our
  // custom name and message so the stack prints correctly in all browsers.
  var template = new Error(this.message);
  template.name = this.name;

  /** @override */
  this.stack = template.stack || '';
};
goog.inherits(bot.Error, Error);

/**
 * A map of error codes to error names.
 * @type {!Object.<string>}
 * @const
 * @private
 */
bot.Error.NAMES_ = goog.object.create(
    bot.ErrorCode.NO_SUCH_ELEMENT, 'NoSuchElementError',
    bot.ErrorCode.NO_SUCH_FRAME, 'NoSuchFrameError',
    bot.ErrorCode.UNKNOWN_COMMAND, 'UnknownCommandError',
    bot.ErrorCode.STALE_ELEMENT_REFERENCE, 'StaleElementReferenceError',
    bot.ErrorCode.ELEMENT_NOT_VISIBLE, 'ElementNotVisibleError',
    bot.ErrorCode.INVALID_ELEMENT_STATE, 'InvalidElementStateError',
    bot.ErrorCode.UNKNOWN_ERROR, 'UnknownError',
    bot.ErrorCode.ELEMENT_NOT_SELECTABLE, 'ElementNotSelectableError',
    bot.ErrorCode.XPATH_LOOKUP_ERROR, 'XPathLookupError',
    bot.ErrorCode.NO_SUCH_WINDOW, 'NoSuchWindowError',
    bot.ErrorCode.INVALID_COOKIE_DOMAIN, 'InvalidCookieDomainError',
    bot.ErrorCode.UNABLE_TO_SET_COOKIE, 'UnableToSetCookieError',
    bot.ErrorCode.MODAL_DIALOG_OPENED, 'ModalDialogOpenedError',
    bot.ErrorCode.NO_MODAL_DIALOG_OPEN, 'NoModalDialogOpenError',
    bot.ErrorCode.SCRIPT_TIMEOUT, 'ScriptTimeoutError',
    bot.ErrorCode.INVALID_SELECTOR_ERROR, 'InvalidSelectorError',
    bot.ErrorCode.SQL_DATABASE_ERROR, 'SqlDatabaseError',
    bot.ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS, 'MoveTargetOutOfBoundsError');

/**
 * Flag used for duck-typing when this code is embedded in a Firefox extension.
 * This is required since an Error thrown in one component and then reported
 * to another will fail instanceof checks in the second component.
 * @type {boolean}
 */
bot.Error.prototype.isAutomationError = true;

if (goog.DEBUG) {
  /** @return {string} The string representation of this error. */
  bot.Error.prototype.toString = function() {
    return '[' + this.name + '] ' + this.message;
  };
}
<commit_msg>DanielWagnerHall for EmmaSoderberg: Updated error codes in the firefox driver<commit_after>// Copyright 2010 WebDriver committers
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview Utilities for working with errors as defined by WebDriver's
 * wire protocol: http://code.google.com/p/selenium/wiki/JsonWireProtocol.
 */

goog.provide('bot.Error');
goog.provide('bot.ErrorCode');

goog.require('goog.object');

/**
 * Error codes from the WebDriver wire protocol:
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
 *
 * @enum {number}
 */
bot.ErrorCode = {
  SUCCESS: 0,  // Included for completeness

  NO_SUCH_ELEMENT: 7,
  NO_SUCH_FRAME: 8,
  UNKNOWN_COMMAND: 9,
  UNSUPPORTED_OPERATION: 9,  // Alias.
  STALE_ELEMENT_REFERENCE: 10,
  ELEMENT_NOT_VISIBLE: 11,
  INVALID_ELEMENT_STATE: 12,
  UNKNOWN_ERROR: 13,
  ELEMENT_NOT_SELECTABLE: 15,
  JAVASCRIPT_ERROR: 17,
  XPATH_LOOKUP_ERROR: 19,
  TIMEOUT: 21,
  NO_SUCH_WINDOW: 23,
  INVALID_COOKIE_DOMAIN: 24,
  UNABLE_TO_SET_COOKIE: 25,
  MODAL_DIALOG_OPENED: 26,
  NO_MODAL_DIALOG_OPEN: 27,
  SCRIPT_TIMEOUT: 28,
  INVALID_ELEMENT_COORDINATES: 29,
  IME_NOT_AVAILABLE: 30,
  IME_ENGINE_ACTIVATION_FAILED: 31,
  INVALID_SELECTOR_ERROR: 32,
  SESSION_NOT_CREATED: 33,
  MOVE_TARGET_OUT_OF_BOUNDS: 34,
  SQL_DATABASE_ERROR: 35
};

/**
 * Error extension that includes error status codes from the WebDriver wire
 * protocol:
 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
 *
 * @param {!bot.ErrorCode} code The error's status code.
 * @param {string=} opt_message Optional error message.
 * @constructor
 * @extends {Error}
 */
bot.Error = function(code, opt_message) {

  /**
   * This error's status code.
   * @type {!bot.ErrorCode}
   */
  this.code = code;

  /** @override */
  this.message = opt_message || '';

  /** @override */
  this.name = (/**@type {string}*/ bot.Error.NAMES_[code] ||
      bot.Error.NAMES_[bot.ErrorCode.UNKNOWN_ERROR]);

  // Generate a stacktrace for our custom error; ensure the error has our
  // custom name and message so the stack prints correctly in all browsers.
  var template = new Error(this.message);
  template.name = this.name;

  /** @override */
  this.stack = template.stack || '';
};
goog.inherits(bot.Error, Error);

/**
 * A map of error codes to error names.
 * @type {!Object.<string>}
 * @const
 * @private
 */
bot.Error.NAMES_ = goog.object.create(
    bot.ErrorCode.NO_SUCH_ELEMENT, 'NoSuchElementError',
    bot.ErrorCode.NO_SUCH_FRAME, 'NoSuchFrameError',
    bot.ErrorCode.UNKNOWN_COMMAND, 'UnknownCommandError',
    bot.ErrorCode.STALE_ELEMENT_REFERENCE, 'StaleElementReferenceError',
    bot.ErrorCode.ELEMENT_NOT_VISIBLE, 'ElementNotVisibleError',
    bot.ErrorCode.INVALID_ELEMENT_STATE, 'InvalidElementStateError',
    bot.ErrorCode.UNKNOWN_ERROR, 'UnknownError',
    bot.ErrorCode.ELEMENT_NOT_SELECTABLE, 'ElementNotSelectableError',
    bot.ErrorCode.XPATH_LOOKUP_ERROR, 'XPathLookupError',
    bot.ErrorCode.NO_SUCH_WINDOW, 'NoSuchWindowError',
    bot.ErrorCode.INVALID_COOKIE_DOMAIN, 'InvalidCookieDomainError',
    bot.ErrorCode.UNABLE_TO_SET_COOKIE, 'UnableToSetCookieError',
    bot.ErrorCode.MODAL_DIALOG_OPENED, 'ModalDialogOpenedError',
    bot.ErrorCode.NO_MODAL_DIALOG_OPEN, 'NoModalDialogOpenError',
    bot.ErrorCode.SCRIPT_TIMEOUT, 'ScriptTimeoutError',
    bot.ErrorCode.INVALID_SELECTOR_ERROR, 'InvalidSelectorError',
    bot.ErrorCode.SQL_DATABASE_ERROR, 'SqlDatabaseError',
    bot.ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS, 'MoveTargetOutOfBoundsError');

/**
 * Flag used for duck-typing when this code is embedded in a Firefox extension.
 * This is required since an Error thrown in one component and then reported
 * to another will fail instanceof checks in the second component.
 * @type {boolean}
 */
bot.Error.prototype.isAutomationError = true;

if (goog.DEBUG) {
  /** @return {string} The string representation of this error. */
  bot.Error.prototype.toString = function() {
    return '[' + this.name + '] ' + this.message;
  };
}
<|endoftext|>