<commit_before>iD.Map = function() {
    var connection, history,
        dimensions = [],
        dispatch = d3.dispatch('move'),
        translateStart,
        keybinding = d3.keybinding(),
        projection = d3.geo.mercator().scale(1024),
        zoom = d3.behavior.zoom()
            .translate(projection.translate())
            .scale(projection.scale())
            .scaleExtent([1024, 256 * Math.pow(2, 24)])
            .on('zoom', zoomPan),
        dblclickEnabled = true,
        fastEnabled = true,
        background = iD.Background()
            .projection(projection),
        transformProp = iD.util.prefixCSSProperty('Transform'),
        supersurface, surface, defs, tilegroup, r, g, alength;

    function map() {
        tilegroup = this.append('div')
            .attr('id', 'tile-g');

        supersurface = this.append('div')
            .style('position', 'absolute')
            .call(zoom);

        surface = supersurface.append('svg')
            .on('mouseup.reset-transform', resetTransform)
            .on('touchend.reset-transform', resetTransform)
            .on('mousedown.zoom', function() {
                if (d3.event.button == 2) {
                    d3.event.stopPropagation();
                }
            });

        defs = surface.append('defs');
        defs.append('clipPath')
                .attr('id', 'clip')
            .append('rect')
                .attr('id', 'clip-rect')
                .attr({ x: 0, y: 0 });

        r = surface.append('g')
            .attr('clip-path', 'url(#clip)');

        g = ['fill', 'casing', 'stroke', 'text', 'hit', 'temp'].reduce(function(mem, i) {
            return (mem[i] = r.append('g').attr('class', 'layer-g layer-' + i)) && mem;
        }, {});

        var arrow = surface.append('text').text('►----');
        alength = arrow.node().getComputedTextLength();
        arrow.remove();
        map.size(this.size());
        map.surface = surface;

        d3.select(document).call(keybinding);
    }

    function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }
    function getline(d) { return d._line; }
    function key(d) { return d.id; }
    function nodeline(d) {
        return 'M' + _.pluck(d.nodes, 'loc').map(projection).map(iD.util.geo.roundCoords).join('L');
    }

    function drawVector(difference) {
        if (surface.style(transformProp) != 'none') return;
        var filter, all, ways = [], lines = [], areas = [], points = [], vertices = [],
            extent = map.extent(),
            graph = history.graph();

        if (!difference) {
            all = graph.intersects(extent);
            filter = d3.functor(true);
        } else {
            var only = {};
            for (var j = 0; j < difference.length; j++) {
                var id = difference[j];
                only[id] = graph.fetch(id);
                if (only[id] && only[id].type === 'node') {
                    var parents = graph.parentWays(only[id]);
                    for (var k = 0; k < parents.length; k++) {
                        // Don't re-fetch parents
                        if (only[parents[k].id] === undefined) {
                            only[parent.id] = graph.fetch(parents[k].id);
                        }
                    }
                }
            }
            all = _.compact(_.values(only));
            filter = function(d) { return d.accuracy ? d.way in only : d.id in only; };
        }

        if (all.length > 10000) return editOff();
        else editOn();

        for (var i = 0; i < all.length; i++) {
            var entity = all[i];
            switch (entity.geometry()) {
                case 'line':
                    entity._line = nodeline(entity);
                    ways.push(entity);
                    lines.push(entity);
                    break;

                case 'area':
                    entity._line = nodeline(entity);
                    ways.push(entity);
                    areas.push(entity);
                    break;

                case 'point':
                    points.push(entity);
                    break;

                case 'vertex':
                    vertices.push(entity);
                    break;
            }
        }

        var parentStructure = graph.parentStructure(ways);
        var wayAccuracyHandles = [];
        for (i = 0; i < ways.length; i++) {
            accuracyHandles(ways[i], wayAccuracyHandles);
        }

        drawVertices(vertices, parentStructure, filter);
        drawAccuracyHandles(wayAccuracyHandles, filter);
        drawCasings(lines, filter);
        drawFills(areas, filter);
        drawStrokes(lines, filter);
        drawPoints(points, filter);
    }

    // updates handles by reference
    function accuracyHandles(way, handles) {
        for (var i = 0; i < way.nodes.length - 1; i++) {
            if (iD.util.geo.dist(way.nodes[i].loc, way.nodes[i + 1].loc) > 0.0001) {
                handles.push({
                    loc: iD.util.geo.interp(way.nodes[i].loc, way.nodes[i + 1].loc, 0.5),
                    way: way.id,
                    index: i + 1,
                    accuracy: true
                });
            }
        }
    }

    function pointTransform(entity) {
        return 'translate(' + iD.util.geo.roundCoords(projection(entity.loc)) + ')';
    }

    function drawVertices(vertices, parentStructure, filter) {
        function shared(d) { return parentStructure[d.id] > 1; }

        var circles = g.hit.selectAll('g.vertex')
            .filter(filter)
            .data(vertices, key);

        circles.exit().remove();

        var cg = circles.enter()
            .insert('g', ':first-child')
            .attr('class', 'node vertex');

        cg.append('circle')
            .attr('class', 'stroke')
            .attr('r', 6);

        cg.append('circle')
            .attr('class', 'fill')
            .attr('r', 4);

        circles.attr('transform', pointTransform)
            .classed('shared', shared);
    }

    function drawAccuracyHandles(waynodes, filter) {
        var handles = g.hit.selectAll('circle.accuracy-handle')
            .filter(filter)
            .data(waynodes, function (d) { return [d.way, d.index].join(","); });
        handles.exit().remove();
        handles.enter().append('circle')
            .attr({ r: 3, 'class': 'accuracy-handle' });
        handles.attr('transform', pointTransform);
    }

    function editOff() {
        surface.selectAll('.layer-g *').remove();
    }

    function editOn() { }

    function drawLines(data, filter, group, fixedClasses) {
        var lines = group.selectAll('path')
            .filter(filter)
            .data(data, key);

        lines.exit().remove();

        lines.enter().append('path')
            .attr('class', fixedClasses);

        lines
            .order()
            .attr('d', getline)
            .call(iD.Style.styleClasses());

        return lines;
    }

    function drawFills(areas, filter) {
        drawLines(areas, filter, g.fill, 'way area');
    }

    function drawCasings(ways, filter) {
        drawLines(ways, filter, g.casing, 'way line casing');
    }

    function drawPoints(points, filter) {
        var groups = g.hit.selectAll('g.point')
            .filter(filter)
            .data(points, key);

        groups.exit().remove();

        var group = groups.enter().append('g')
            .attr('class', 'node point');

        group.append('circle')
            .attr('class', 'stroke')
            .attr({ r: 10 });

        group.append('circle')
            .attr('class', 'fill')
            .attr({ r: 10 });

        group.append('image')
            .attr({ width: 16, height: 16 })
            .attr('transform', 'translate(-8, -8)');

        groups.attr('transform', pointTransform);

        groups.select('image').attr('xlink:href', iD.Style.pointImage);
    }

    function drawStrokes(ways, filter) {
        var strokes = drawLines(ways, filter, g.stroke, 'way line stroke');

        // Determine the lengths of oneway paths
        var lengths = {},
            oneways = strokes.filter(function (d) { return d.isOneWay(); }).each(function(d) {
                lengths[d.id] = Math.floor(this.getTotalLength() / alength);
            }).data();

        var uses = defs.selectAll('path')
            .filter(filter)
            .data(oneways, key);
        uses.exit().remove();
        uses.enter().append('path');
        uses
            .attr('id', function(d) { return 'shadow-' + d.id; })
            .attr('d', getline);

        var labels = g.text.selectAll('text')
            .filter(filter)
            .data(oneways, key);
        labels.exit().remove();
        var tp = labels.enter()
            .append('text').attr({ 'class': 'oneway', dy: 4 })
            .append('textPath').attr('class', 'textpath');
        g.text.selectAll('.textpath')
            .filter(filter)
            .attr('xlink:href', function(d, i) { return '#shadow-' + d.id; })
            .text(function(d) {
                return (new Array(Math.floor(lengths[d.id]))).join('►----');
            });
    }

    function connectionLoad(err, result) {
        history.merge(result);
        redraw(Object.keys(result.entities));
    }

    function zoomPan() {
        if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
            if (!dblclickEnabled) {
                zoom.scale(projection.scale())
                    .translate(projection.translate());
                return d3.event.sourceEvent.preventDefault();
            }
        }
        var fast = (d3.event.scale === projection.scale() && fastEnabled);
        projection
            .translate(d3.event.translate)
            .scale(d3.event.scale);
        if (fast) {
            if (!translateStart) translateStart = d3.event.translate.slice();
            var a = d3.event.translate,
                b = translateStart,
                translate = 'translate(' + ~~(a[0] - b[0]) + 'px,' +
                    ~~(a[1] - b[1]) + 'px)';
            tilegroup.style(transformProp, translate);
            surface.style(transformProp, translate);
        } else {
            redraw();
            translateStart = null;
        }
    }

    function resetTransform() {
        if (!surface.style(transformProp)) return;
        translateStart = null;
        surface.style(transformProp, '');
        tilegroup.style(transformProp, '');
        redraw();
    }

    function redraw(difference) {
        dispatch.move(map);
        surface.attr('data-zoom', ~~map.zoom());
        tilegroup.call(background);
        if (map.zoom() > 16) {
            connection.loadTiles(projection);
            drawVector(difference);
        } else {
            editOff();
        }
        return map;
    }

    function pointLocation(p) {
        var translate = projection.translate(),
            scale = projection.scale();
        return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
    }

    function locationPoint(l) {
        var translate = projection.translate(),
            scale = projection.scale();
        return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
    }

    map.mouseCoordinates = function() {
        try {
            return projection.invert(d3.mouse(surface.node()));
        } catch(e) {
            // when called with hidden elements, d3.mouse() will throw
            return [NaN, NaN];
        }
    };

    map.dblclickEnable = function(_) {
        if (!arguments.length) return dblclickEnabled;
        dblclickEnabled = _;
        return map;
    };

    map.fastEnable = function(_) {
        if (!arguments.length) return fastEnabled;
        fastEnabled = _;
        return map;
    };

    map.zoom = function(z) {
        if (!arguments.length) {
            return Math.max(Math.log(projection.scale()) / Math.LN2 - 8, 0);
        }
        var scale = 256 * Math.pow(2, z),
            center = pxCenter(),
            l = pointLocation(center);
        scale = Math.max(1024, Math.min(256 * Math.pow(2, 24), scale));
        projection.scale(scale);
        zoom.scale(projection.scale());
        var t = projection.translate();
        l = locationPoint(l);
        t[0] += center[0] - l[0];
        t[1] += center[1] - l[1];
        projection.translate(t);
        zoom.translate(projection.translate());
        return redraw();
    };

    map.size = function(_) {
        if (!arguments.length) return dimensions;
        dimensions = _;
        surface
            .size(dimensions)
            .selectAll('#clip-rect')
            .size(dimensions);
        background.size(dimensions);
        return redraw();
    };

    map.zoomIn = function() { return map.zoom(Math.ceil(map.zoom() + 1)); };
    map.zoomOut = function() { return map.zoom(Math.floor(map.zoom() - 1)); };

    map.center = function(loc) {
        if (!arguments.length) {
            return projection.invert(pxCenter());
        } else {
            var t = projection.translate(),
                c = pxCenter(),
                ll = projection(loc);
            projection.translate([
                t[0] - ll[0] + c[0],
                t[1] - ll[1] + c[1]]);
            zoom.translate(projection.translate());
            return redraw();
        }
    };

    map.centerEase = function(loc) {
        var from = map.center().slice(), t = 0;
        d3.timer(function() {
            map.center(iD.util.geo.interp(from, loc, (t += 1) / 10));
            return t == 10;
        }, 20);
    };

    map.extent = function() {
        return [projection.invert([0, 0]), projection.invert(dimensions)];
    };

    map.flush = function () {
        connection.flush();
        return map;
    };

    map.connection = function(_) {
        if (!arguments.length) return connection;
        connection = _;
        connection.on('load', connectionLoad);
        return map;
    };

    map.hint = function (_) {
        if (_ === false) {
            d3.select('div.inspector-wrap')
                .style('opacity', 0)
                .style('display', 'none');
        } else {
            d3.select('div.inspector-wrap')
                .html('')
                .style('display', 'block')
                .transition()
                .style('opacity', 1);
            d3.select('div.inspector-wrap')
                .append('div')
                .attr('class','inspector-inner')
                .text(_);
        }
    };

    map.history = function (_) {
        if (!arguments.length) return history;
        history = _;
        history.on('change.map', redraw);
        return map;
    };

    map.keybinding = function (_) {
        if (!arguments.length) return keybinding;
        keybinding = _;
        return map;
    };

    map.background = background;
    map.projection = projection;
    map.redraw = redraw;

    return d3.rebind(map, dispatch, 'on');
};
<commit_msg>Fix #351<commit_after>iD.Map = function() {
    var connection, history,
        dimensions = [],
        dispatch = d3.dispatch('move'),
        translateStart,
        keybinding = d3.keybinding(),
        projection = d3.geo.mercator().scale(1024),
        zoom = d3.behavior.zoom()
            .translate(projection.translate())
            .scale(projection.scale())
            .scaleExtent([1024, 256 * Math.pow(2, 24)])
            .on('zoom', zoomPan),
        dblclickEnabled = true,
        fastEnabled = true,
        background = iD.Background()
            .projection(projection),
        transformProp = iD.util.prefixCSSProperty('Transform'),
        supersurface, surface, defs, tilegroup, r, g, alength;

    function map() {
        tilegroup = this.append('div')
            .attr('id', 'tile-g');

        supersurface = this.append('div')
            .style('position', 'absolute')
            .call(zoom);

        surface = supersurface.append('svg')
            .on('mouseup.reset-transform', resetTransform)
            .on('touchend.reset-transform', resetTransform)
            .on('mousedown.zoom', function() {
                if (d3.event.button == 2) {
                    d3.event.stopPropagation();
                }
            });

        defs = surface.append('defs');
        defs.append('clipPath')
                .attr('id', 'clip')
            .append('rect')
                .attr('id', 'clip-rect')
                .attr({ x: 0, y: 0 });

        r = surface.append('g')
            .attr('clip-path', 'url(#clip)');

        g = ['fill', 'casing', 'stroke', 'text', 'hit', 'temp'].reduce(function(mem, i) {
            return (mem[i] = r.append('g').attr('class', 'layer-g layer-' + i)) && mem;
        }, {});

        var arrow = surface.append('text').text('►----');
        alength = arrow.node().getComputedTextLength();
        arrow.remove();
        map.size(this.size());
        map.surface = surface;

        d3.select(document).call(keybinding);
    }

    function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; }
    function getline(d) { return d._line; }
    function key(d) { return d.id; }
    function nodeline(d) {
        return 'M' + _.pluck(d.nodes, 'loc').map(projection).map(iD.util.geo.roundCoords).join('L');
    }

    function drawVector(difference) {
        if (surface.style(transformProp) != 'none') return;
        var filter, all, ways = [], lines = [], areas = [], points = [], vertices = [],
            extent = map.extent(),
            graph = history.graph();

        if (!difference) {
            all = graph.intersects(extent);
            filter = d3.functor(true);
        } else {
            var only = {};
            for (var j = 0; j < difference.length; j++) {
                var id = difference[j];
                only[id] = graph.fetch(id);
                if (only[id] && only[id].type === 'node') {
                    var parents = graph.parentWays(only[id]);
                    for (var k = 0; k < parents.length; k++) {
                        // Don't re-fetch parents
                        if (only[parents[k].id] === undefined) {
                            only[parents[k].id] = graph.fetch(parents[k].id);
                        }
                    }
                }
            }
            all = _.compact(_.values(only));
            filter = function(d) { return d.accuracy ? d.way in only : d.id in only; };
        }

        if (all.length > 10000) return editOff();
        else editOn();

        for (var i = 0; i < all.length; i++) {
            var entity = all[i];
            switch (entity.geometry()) {
                case 'line':
                    entity._line = nodeline(entity);
                    ways.push(entity);
                    lines.push(entity);
                    break;

                case 'area':
                    entity._line = nodeline(entity);
                    ways.push(entity);
                    areas.push(entity);
                    break;

                case 'point':
                    points.push(entity);
                    break;

                case 'vertex':
                    vertices.push(entity);
                    break;
            }
        }

        var parentStructure = graph.parentStructure(ways);
        var wayAccuracyHandles = [];
        for (i = 0; i < ways.length; i++) {
            accuracyHandles(ways[i], wayAccuracyHandles);
        }

        drawVertices(vertices, parentStructure, filter);
        drawAccuracyHandles(wayAccuracyHandles, filter);
        drawCasings(lines, filter);
        drawFills(areas, filter);
        drawStrokes(lines, filter);
        drawPoints(points, filter);
    }

    // updates handles by reference
    function accuracyHandles(way, handles) {
        for (var i = 0; i < way.nodes.length - 1; i++) {
            if (iD.util.geo.dist(way.nodes[i].loc, way.nodes[i + 1].loc) > 0.0001) {
                handles.push({
                    loc: iD.util.geo.interp(way.nodes[i].loc, way.nodes[i + 1].loc, 0.5),
                    way: way.id,
                    index: i + 1,
                    accuracy: true
                });
            }
        }
    }

    function pointTransform(entity) {
        return 'translate(' + iD.util.geo.roundCoords(projection(entity.loc)) + ')';
    }

    function drawVertices(vertices, parentStructure, filter) {
        function shared(d) { return parentStructure[d.id] > 1; }

        var circles = g.hit.selectAll('g.vertex')
            .filter(filter)
            .data(vertices, key);

        circles.exit().remove();

        var cg = circles.enter()
            .insert('g', ':first-child')
            .attr('class', 'node vertex');

        cg.append('circle')
            .attr('class', 'stroke')
            .attr('r', 6);

        cg.append('circle')
            .attr('class', 'fill')
            .attr('r', 4);

        circles.attr('transform', pointTransform)
            .classed('shared', shared);
    }

    function drawAccuracyHandles(waynodes, filter) {
        var handles = g.hit.selectAll('circle.accuracy-handle')
            .filter(filter)
            .data(waynodes, function (d) { return [d.way, d.index].join(","); });
        handles.exit().remove();
        handles.enter().append('circle')
            .attr({ r: 3, 'class': 'accuracy-handle' });
        handles.attr('transform', pointTransform);
    }

    function editOff() {
        surface.selectAll('.layer-g *').remove();
    }

    function editOn() { }

    function drawLines(data, filter, group, fixedClasses) {
        var lines = group.selectAll('path')
            .filter(filter)
            .data(data, key);

        lines.exit().remove();

        lines.enter().append('path')
            .attr('class', fixedClasses);

        lines
            .order()
            .attr('d', getline)
            .call(iD.Style.styleClasses());

        return lines;
    }

    function drawFills(areas, filter) {
        drawLines(areas, filter, g.fill, 'way area');
    }

    function drawCasings(ways, filter) {
        drawLines(ways, filter, g.casing, 'way line casing');
    }

    function drawPoints(points, filter) {
        var groups = g.hit.selectAll('g.point')
            .filter(filter)
            .data(points, key);

        groups.exit().remove();

        var group = groups.enter().append('g')
            .attr('class', 'node point');

        group.append('circle')
            .attr('class', 'stroke')
            .attr({ r: 10 });

        group.append('circle')
            .attr('class', 'fill')
            .attr({ r: 10 });

        group.append('image')
            .attr({ width: 16, height: 16 })
            .attr('transform', 'translate(-8, -8)');

        groups.attr('transform', pointTransform);

        groups.select('image').attr('xlink:href', iD.Style.pointImage);
    }

    function drawStrokes(ways, filter) {
        var strokes = drawLines(ways, filter, g.stroke, 'way line stroke');

        // Determine the lengths of oneway paths
        var lengths = {},
            oneways = strokes.filter(function (d) { return d.isOneWay(); }).each(function(d) {
                lengths[d.id] = Math.floor(this.getTotalLength() / alength);
            }).data();

        var uses = defs.selectAll('path')
            .filter(filter)
            .data(oneways, key);
        uses.exit().remove();
        uses.enter().append('path');
        uses
            .attr('id', function(d) { return 'shadow-' + d.id; })
            .attr('d', getline);

        var labels = g.text.selectAll('text')
            .filter(filter)
            .data(oneways, key);
        labels.exit().remove();
        var tp = labels.enter()
            .append('text').attr({ 'class': 'oneway', dy: 4 })
            .append('textPath').attr('class', 'textpath');
        g.text.selectAll('.textpath')
            .filter(filter)
            .attr('xlink:href', function(d, i) { return '#shadow-' + d.id; })
            .text(function(d) {
                return (new Array(Math.floor(lengths[d.id]))).join('►----');
            });
    }

    function connectionLoad(err, result) {
        history.merge(result);
        redraw(Object.keys(result.entities));
    }

    function zoomPan() {
        if (d3.event && d3.event.sourceEvent.type === 'dblclick') {
            if (!dblclickEnabled) {
                zoom.scale(projection.scale())
                    .translate(projection.translate());
                return d3.event.sourceEvent.preventDefault();
            }
        }
        var fast = (d3.event.scale === projection.scale() && fastEnabled);
        projection
            .translate(d3.event.translate)
            .scale(d3.event.scale);
        if (fast) {
            if (!translateStart) translateStart = d3.event.translate.slice();
            var a = d3.event.translate,
                b = translateStart,
                translate = 'translate(' + ~~(a[0] - b[0]) + 'px,' +
                    ~~(a[1] - b[1]) + 'px)';
            tilegroup.style(transformProp, translate);
            surface.style(transformProp, translate);
        } else {
            redraw();
            translateStart = null;
        }
    }

    function resetTransform() {
        if (!surface.style(transformProp)) return;
        translateStart = null;
        surface.style(transformProp, '');
        tilegroup.style(transformProp, '');
        redraw();
    }

    function redraw(difference) {
        dispatch.move(map);
        surface.attr('data-zoom', ~~map.zoom());
        tilegroup.call(background);
        if (map.zoom() > 16) {
            connection.loadTiles(projection);
            drawVector(difference);
        } else {
            editOff();
        }
        return map;
    }

    function pointLocation(p) {
        var translate = projection.translate(),
            scale = projection.scale();
        return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
    }

    function locationPoint(l) {
        var translate = projection.translate(),
            scale = projection.scale();
        return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
    }

    map.mouseCoordinates = function() {
        try {
            return projection.invert(d3.mouse(surface.node()));
        } catch(e) {
            // when called with hidden elements, d3.mouse() will throw
            return [NaN, NaN];
        }
    };

    map.dblclickEnable = function(_) {
        if (!arguments.length) return dblclickEnabled;
        dblclickEnabled = _;
        return map;
    };

    map.fastEnable = function(_) {
        if (!arguments.length) return fastEnabled;
        fastEnabled = _;
        return map;
    };

    map.zoom = function(z) {
        if (!arguments.length) {
            return Math.max(Math.log(projection.scale()) / Math.LN2 - 8, 0);
        }
        var scale = 256 * Math.pow(2, z),
            center = pxCenter(),
            l = pointLocation(center);
        scale = Math.max(1024, Math.min(256 * Math.pow(2, 24), scale));
        projection.scale(scale);
        zoom.scale(projection.scale());
        var t = projection.translate();
        l = locationPoint(l);
        t[0] += center[0] - l[0];
        t[1] += center[1] - l[1];
        projection.translate(t);
        zoom.translate(projection.translate());
        return redraw();
    };

    map.size = function(_) {
        if (!arguments.length) return dimensions;
        dimensions = _;
        surface
            .size(dimensions)
            .selectAll('#clip-rect')
            .size(dimensions);
        background.size(dimensions);
        return redraw();
    };

    map.zoomIn = function() { return map.zoom(Math.ceil(map.zoom() + 1)); };
    map.zoomOut = function() { return map.zoom(Math.floor(map.zoom() - 1)); };

    map.center = function(loc) {
        if (!arguments.length) {
            return projection.invert(pxCenter());
        } else {
            var t = projection.translate(),
                c = pxCenter(),
                ll = projection(loc);
            projection.translate([
                t[0] - ll[0] + c[0],
                t[1] - ll[1] + c[1]]);
            zoom.translate(projection.translate());
            return redraw();
        }
    };

    map.centerEase = function(loc) {
        var from = map.center().slice(), t = 0;
        d3.timer(function() {
            map.center(iD.util.geo.interp(from, loc, (t += 1) / 10));
            return t == 10;
        }, 20);
    };

    map.extent = function() {
        return [projection.invert([0, 0]), projection.invert(dimensions)];
    };

    map.flush = function () {
        connection.flush();
        return map;
    };

    map.connection = function(_) {
        if (!arguments.length) return connection;
        connection = _;
        connection.on('load', connectionLoad);
        return map;
    };

    map.hint = function (_) {
        if (_ === false) {
            d3.select('div.inspector-wrap')
                .style('opacity', 0)
                .style('display', 'none');
        } else {
            d3.select('div.inspector-wrap')
                .html('')
                .style('display', 'block')
                .transition()
                .style('opacity', 1);
            d3.select('div.inspector-wrap')
                .append('div')
                .attr('class','inspector-inner')
                .text(_);
        }
    };

    map.history = function (_) {
        if (!arguments.length) return history;
        history = _;
        history.on('change.map', redraw);
        return map;
    };

    map.keybinding = function (_) {
        if (!arguments.length) return keybinding;
        keybinding = _;
        return map;
    };

    map.background = background;
    map.projection = projection;
    map.redraw = redraw;

    return d3.rebind(map, dispatch, 'on');
};
<|endoftext|>


<commit_before>// Copyright 2014-2018, University of Colorado Boulder

/**
 * A type that represents the air in the model.  Air can hold heat, and can exchange thermal energy with other model
 * objects.
 *
 * @author John Blanco
 */

define( function( require ) {
  'use strict';

  // modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkWanderController = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunkWanderController' );
  var EnergyContainerCategory = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/EnergyContainerCategory' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var HeatTransferConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/model/HeatTransferConstants' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ObservableArray = require( 'AXON/ObservableArray' );
  var Property = require( 'AXON/Property' );
  var ThermalContactArea = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/ThermalContactArea' );
  var Vector2 = require( 'DOT/Vector2' );

  // constants

  // 2D size of the air.  It is sized such that it will extend off the left, right, and top edges of screen for the
  // most common aspect ratios of the view.
  var SIZE = new Dimension2( 0.7, 0.5 );

  // The thickness of the slice of air being modeled.  This is basically the z dimension, and is used solely for
  // volume calculations.
  var DEPTH = 0.1; // in meters

  // constants that define the heat carrying capacity of the air.
  var SPECIFIC_HEAT = 1012; // In J/kg-K, source = design document.
  var DENSITY = 10; // In kg/m^3, far more dense than real air (real air is 0.001), done to make things cool faster

  // derived constants
  var VOLUME = SIZE.width * SIZE.height * DEPTH;
  var MASS = VOLUME * DENSITY;
  var INITIAL_ENERGY = MASS * SPECIFIC_HEAT * EFACConstants.ROOM_TEMPERATURE;
  var THERMAL_CONTACT_AREA = new ThermalContactArea( new Bounds2( -SIZE.width / 2, 0, SIZE.width, SIZE.height ), true );

  // instance counter used for creating unique IDs
  var instanceCounter = 0;

  /**
   * @param {BooleanProperty} energyChunksVisibleProperty - visibility of energy chunks, used when creating new ones
   * @constructor
   */
  function Air( energyChunksVisibleProperty ) {

    // @private {BooleanProperty}
    this.energyChunksVisibleProperty = energyChunksVisibleProperty;

    // @public (read-only) - list of energy chunks owned by this model element
    this.energyChunkList = new ObservableArray();

    // @public (read-only) {string} - unique ID
    this.id = 'air-' + instanceCounter++;

    // @private {Array<EnergyChunkWanderController>} - wander controolers for energy chunks that are owned by this model
    // element but are wandering outside of it
    this.energyChunkWanderControllers = [];

    // @private {number} - total energy of the air, accessible through getters/setters defined below
    this.energy = INITIAL_ENERGY;
  }

  energyFormsAndChanges.register( 'Air', Air );

  return inherit( Object, Air, {

    /**
     * step function for this model element
     * @param {number} dt - delta time, in seconds
     * @public
     */
    step: function( dt ) {

      var self = this;
      var wanderControllers = this.energyChunkWanderControllers.slice();

      wanderControllers.forEach( function( wanderController ) {
        wanderController.updatePosition( dt );
        if ( wanderController.isDestinationReached() ) {

          // The energy chuck has reached its destination, which for the air means it has risen out of view, so remove
          // it from the model.
          self.energyChunkList.remove( wanderController.energyChunk );
          _.pull( self.energyChunkWanderControllers, wanderController );
        }
      } );

      this.equalizeWithSurroundingAir( dt );
    },

    /**
     * This method simulates how air in a particular area loses heat over time as it equalizes with other air nearby.
     * It assumes that the surrounding air is infinite and is at room temperature.
     * @param {number} dt - delta time, in seconds
     * @private
     */
    equalizeWithSurroundingAir: function( dt ) {
      var self = this;
      if ( Math.abs( this.getTemperature() - EFACConstants.ROOM_TEMPERATURE ) >
           EFACConstants.SIGNIFICANT_TEMPERATURE_DIFFERENCE ) {

        var numFullTimeStepExchanges = Math.floor( dt / EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP );
        var leftoverTime = dt - ( numFullTimeStepExchanges * EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP );
        _.times( numFullTimeStepExchanges + 1, function( index ) {
          var timeStep = index < numFullTimeStepExchanges ? EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP : leftoverTime;
          var thermalEnergyLost = ( self.getTemperature() - EFACConstants.ROOM_TEMPERATURE ) *
                                  HeatTransferConstants.getAirToSurroundingAirHeatTransferFactor() * timeStep;
          self.changeEnergy( -thermalEnergyLost );

        } );
      }
    },

    /**
     * @param {number} deltaEnergy
     * @public
     */
    changeEnergy: function( deltaEnergy ) {
      this.energy += deltaEnergy;
    },

    /**
     * @returns {number}
     * @public
     */
    getEnergy: function() {
      return this.energy;
    },

    /**
     * @public
     */
    reset: function() {
      this.energy = INITIAL_ENERGY;
      this.energyChunkList.clear();
    },

    /**
     * exchange thermal energy with the provided energy container
     * @param {RectangularThermalMovableModelElement} energyContainer
     * @param {number} dt - time in seconds
     * @returns {number} - energy, in joules, that is transferred from air to the object, negative if energy was absosrbed
     * @public
     */
    exchangeEnergyWith: function( energyContainer, dt ) {
      var energyToExchange = 0;
      var thermalContactLength = this.thermalContactArea.getThermalContactLength( energyContainer.thermalContactArea );
      if ( thermalContactLength > 0 ) {

        // calculate the amount of energy to exchange based on the thermal differential
        var heatTransferConstant = HeatTransferConstants.getHeatTransferFactor(
          this.energyContainerCategory,
          energyContainer.energyContainerCategory
        );
        var numFullTimeStepExchanges = Math.floor( dt / EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP );
        var leftoverTime = dt - ( numFullTimeStepExchanges * EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP );
        for ( var i = 0; i < numFullTimeStepExchanges + 1; i++ ) {
          var timeStep = i < numFullTimeStepExchanges ? EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP : leftoverTime;
          var thermalEnergyGained = ( energyContainer.getTemperature() - this.getTemperature() ) *
                                    thermalContactLength * heatTransferConstant * timeStep;
          energyToExchange += thermalEnergyGained;
        }

        // If the container has "excess energy", which can happen in cases such as where energy has been added to water
        // that is already at the boiling point, all of that energy should be dumped into the air.
        if ( energyToExchange >= 0 ) {
          energyToExchange = Math.max( energyToExchange, energyContainer.getEnergyBeyondMaxTemperature() );
        }

        // calculations are complete, do the actual exchange
        energyContainer.changeEnergy( -energyToExchange );
        this.changeEnergy( energyToExchange );
      }
      return -energyToExchange;
    },

    /**
     * @param {EnergyChunk} energyChunk
     * @param {Range} horizontalWanderConstraint
     */
    addEnergyChunk: function( energyChunk, horizontalWanderConstraint ) {
      energyChunk.zPositionProperty.value = 0;
      this.energyChunkList.push( energyChunk );
      this.energyChunkWanderControllers.push( new EnergyChunkWanderController(
        energyChunk,
        new Property( new Vector2( energyChunk.positionProperty.value.x, SIZE.height ) ),
        { horizontalWanderConstraint: horizontalWanderConstraint }
      ) );
    },

    /**
     * @returns {Vector2}
     * @public
     */
    getCenterPoint: function() {
      return new Vector2( 0, SIZE.height / 2 );
    },

    /**
     * create a new energy chunk at the top of the air above the specified point
     * @param {Vector2} point
     * @returns {EnergyChunk}
     * @public
     */
    requestEnergyChunk: function( point ) {

      // create a new chunk at the top of the air above the specified point
      return new EnergyChunk(
        EnergyType.THERMAL,
        new Vector2( point.x, SIZE.height ),
        new Vector2( 0, 0 ),
        this.energyChunksVisibleProperty
      );
    },

    /**
     * get the thermal contact area for air, where thermal energy may be exchanged with other objects
     * @returns {ThermalContactArea}
     * @public
     */
    get thermalContactArea() {
      return THERMAL_CONTACT_AREA;
    },

    /**
     * get the air temperature
     * @returns {number}
     * @public
     */
    getTemperature: function() {
      return this.energy / ( MASS * SPECIFIC_HEAT );
    },

    /**
     * @returns {EnergyContainerCategory}
     * @public
     */
    get energyContainerCategory() {
      return EnergyContainerCategory.AIR;
    },

    /**
     * @returns {number}
     * @public
     */
    getEnergyBeyondMaxTemperature: function() {

      // air temperature is unlimited
      return 0;
    }
  } );
} );<commit_msg>Convert to ES6, see https://github.com/phetsims/energy-forms-and-changes/issues/211<commit_after>// Copyright 2014-2019, University of Colorado Boulder

/**
 * A type that represents the air in the model. Air can hold heat and exchange thermal energy with other model objects.
 *
 * @author John Blanco
 */

define( require => {
  'use strict';

  // modules
  const Bounds2 = require( 'DOT/Bounds2' );
  const Dimension2 = require( 'DOT/Dimension2' );
  const EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  const EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  const EnergyChunkWanderController = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunkWanderController' );
  const EnergyContainerCategory = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/EnergyContainerCategory' );
  const energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  const EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  const HeatTransferConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/model/HeatTransferConstants' );
  const ObservableArray = require( 'AXON/ObservableArray' );
  const Property = require( 'AXON/Property' );
  const ThermalContactArea = require( 'ENERGY_FORMS_AND_CHANGES/intro/model/ThermalContactArea' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants

  // 2D size of the air.  It is sized such that it will extend off the left, right, and top edges of screen for the
  // most common aspect ratios of the view.
  const SIZE = new Dimension2( 0.7, 0.5 );

  // The thickness of the slice of air being modeled.  This is basically the z dimension, and is used solely for
  // volume calculations.
  const DEPTH = 0.1; // in meters

  // constants that define the heat carrying capacity of the air.
  const SPECIFIC_HEAT = 1012; // In J/kg-K, source = design document.
  const DENSITY = 10; // In kg/m^3, far more dense than real air (real air is 0.001), done to make things cool faster

  // derived constants
  const VOLUME = SIZE.width * SIZE.height * DEPTH;
  const MASS = VOLUME * DENSITY;
  const INITIAL_ENERGY = MASS * SPECIFIC_HEAT * EFACConstants.ROOM_TEMPERATURE;
  const THERMAL_CONTACT_AREA = new ThermalContactArea( new Bounds2( -SIZE.width / 2, 0, SIZE.width, SIZE.height ), true );

  // instance counter used for creating unique IDs
  let instanceCounter = 0;

  class Air {

    /**
     * @param {BooleanProperty} energyChunksVisibleProperty - visibility of energy chunks, used when creating new ones
     */
    constructor( energyChunksVisibleProperty ) {

      // @private {BooleanProperty}
      this.energyChunksVisibleProperty = energyChunksVisibleProperty;

      // @public (read-only) - list of energy chunks owned by this model element
      this.energyChunkList = new ObservableArray();

      // @public (read-only) {string} - unique ID
      this.id = `air-${instanceCounter++}`;

      // @private {Array<EnergyChunkWanderController>} - wander controolers for energy chunks that are owned by this model
      // element but are wandering outside of it
      this.energyChunkWanderControllers = [];

      // @private {number} - total energy of the air, accessible through getters/setters defined below
      this.energy = INITIAL_ENERGY;
    }

    /**
     * step function for this model element
     * @param {number} dt - delta time, in seconds
     * @public
     */
    step( dt ) {
      const wanderControllers = this.energyChunkWanderControllers.slice();

      wanderControllers.forEach( wanderController => {
        wanderController.updatePosition( dt );
        if ( wanderController.isDestinationReached() ) {

          // The energy chuck has reached its destination, which for the air means it has risen out of view, so remove
          // it from the model.
          this.energyChunkList.remove( wanderController.energyChunk );
          _.pull( this.energyChunkWanderControllers, wanderController );
        }
      } );

      this.equalizeWithSurroundingAir( dt );
    }

    /**
     * This method simulates how air in a particular area loses heat over time as it equalizes with other air nearby.
     * It assumes that the surrounding air is infinite and is at room temperature.
     * @param {number} dt - delta time, in seconds
     * @private
     */
    equalizeWithSurroundingAir( dt ) {
      if ( Math.abs( this.getTemperature() - EFACConstants.ROOM_TEMPERATURE ) >
           EFACConstants.SIGNIFICANT_TEMPERATURE_DIFFERENCE ) {

        const numFullTimeStepExchanges = Math.floor( dt / EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP );
        const leftoverTime = dt - ( numFullTimeStepExchanges * EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP );
        _.times( numFullTimeStepExchanges + 1, index => {
          const timeStep = index < numFullTimeStepExchanges ? EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP : leftoverTime;
          const thermalEnergyLost = ( this.getTemperature() - EFACConstants.ROOM_TEMPERATURE ) *
                                  HeatTransferConstants.getAirToSurroundingAirHeatTransferFactor() * timeStep;
          this.changeEnergy( -thermalEnergyLost );

        } );
      }
    }

    /**
     * @param {number} deltaEnergy
     * @public
     */
    changeEnergy( deltaEnergy ) {
      this.energy += deltaEnergy;
    }

    /**
     * @returns {number}
     * @public
     */
    getEnergy() {
      return this.energy;
    }

    /**
     * @public
     */
    reset() {
      this.energy = INITIAL_ENERGY;
      this.energyChunkList.clear();
    }

    /**
     * exchange thermal energy with the provided energy container
     * @param {RectangularThermalMovableModelElement} energyContainer
     * @param {number} dt - time in seconds
     * @returns {number} - energy, in joules, that is transferred from air to the object, negative if energy was absosrbed
     * @public
     */
    exchangeEnergyWith( energyContainer, dt ) {
      let energyToExchange = 0;
      const thermalContactLength = this.thermalContactArea.getThermalContactLength( energyContainer.thermalContactArea );
      if ( thermalContactLength > 0 ) {

        // calculate the amount of energy to exchange based on the thermal differential
        const heatTransferConstant = HeatTransferConstants.getHeatTransferFactor(
          this.energyContainerCategory,
          energyContainer.energyContainerCategory
        );
        const numFullTimeStepExchanges = Math.floor( dt / EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP );
        const leftoverTime = dt - ( numFullTimeStepExchanges * EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP );
        for ( let i = 0; i < numFullTimeStepExchanges + 1; i++ ) {
          const timeStep = i < numFullTimeStepExchanges ? EFACConstants.MAX_HEAT_EXCHANGE_TIME_STEP : leftoverTime;
          const thermalEnergyGained = ( energyContainer.getTemperature() - this.getTemperature() ) *
                                    thermalContactLength * heatTransferConstant * timeStep;
          energyToExchange += thermalEnergyGained;
        }

        // If the container has "excess energy", which can happen in cases such as where energy has been added to water
        // that is already at the boiling point, all of that energy should be dumped into the air.
        if ( energyToExchange >= 0 ) {
          energyToExchange = Math.max( energyToExchange, energyContainer.getEnergyBeyondMaxTemperature() );
        }

        // calculations are complete, do the actual exchange
        energyContainer.changeEnergy( -energyToExchange );
        this.changeEnergy( energyToExchange );
      }
      return -energyToExchange;
    }

    /**
     * @param {EnergyChunk} energyChunk
     * @param {Range} horizontalWanderConstraint
     */
    addEnergyChunk( energyChunk, horizontalWanderConstraint ) {
      energyChunk.zPositionProperty.value = 0;
      this.energyChunkList.push( energyChunk );
      this.energyChunkWanderControllers.push( new EnergyChunkWanderController(
        energyChunk,
        new Property( new Vector2( energyChunk.positionProperty.value.x, SIZE.height ) ),
        { horizontalWanderConstraint: horizontalWanderConstraint }
      ) );
    }

    /**
     * @returns {Vector2}
     * @public
     */
    getCenterPoint() {
      return new Vector2( 0, SIZE.height / 2 );
    }

    /**
     * create a new energy chunk at the top of the air above the specified point
     * @param {Vector2} point
     * @returns {EnergyChunk}
     * @public
     */
    requestEnergyChunk( point ) {

      // create a new chunk at the top of the air above the specified point
      return new EnergyChunk(
        EnergyType.THERMAL,
        new Vector2( point.x, SIZE.height ),
        new Vector2( 0, 0 ),
        this.energyChunksVisibleProperty
      );
    }

    /**
     * get the thermal contact area for air, where thermal energy may be exchanged with other objects
     * @returns {ThermalContactArea}
     * @public
     */
    get thermalContactArea() {
      return THERMAL_CONTACT_AREA;
    }

    /**
     * get the air temperature
     * @returns {number}
     * @public
     */
    getTemperature() {
      return this.energy / ( MASS * SPECIFIC_HEAT );
    }

    /**
     * @returns {EnergyContainerCategory}
     * @public
     */
    get energyContainerCategory() {
      return EnergyContainerCategory.AIR;
    }

    /**
     * @returns {number}
     * @public
     */
    getEnergyBeyondMaxTemperature() {

      // air temperature is unlimited
      return 0;
    }
  }

  return energyFormsAndChanges.register( 'Air', Air );
} );<|endoftext|>


<commit_before>/*! jQuery slabtext plugin v2 MIT/GPL2 @freqdec */
(function( $ ){

    $.fn.slabText = function(options) {

        var settings = {
            // The ratio used when calculating the characters per line
            // (parent width / (font-size * fontRatio)).
            "fontRatio"             : 0.78,
            // Always recalculate the characters per line, not just when the
            // font-size changes? Defaults to true (CPU intensive)
            "forceNewCharCount"     : true,
            // Do we wrap ampersands in  ?
            "wrapAmpersand"         : true,
            // Under what pixel width do we remove the slabtext styling?
            "headerBreakpoint"      : null,
            "viewportBreakpoint"    : null,
            // Don't attach a resize event
            "noResizeEvent"         : false
            };

        // Add the slabtexted classname to the body to initiate the styling of
        // the injected spans
        $("body").addClass("slabtexted");

        return this.each(function(){

            // Extend options if necessary
            if(options) {
                    $.extend(settings, options);
            };

            var $this               = $(this),
                keepSpans           = $("span.slabtext", $this).length,
                words               = keepSpans ? [] : String($.trim($this.text())).replace(/\s{2,}/g, " ").split(" "),
                origFontSize        = null,
                idealCharPerLine    = null,
                fontRatio           = settings.fontRatio,
                forceNewCharCount   = settings.forceNewCharCount,
                headerBreakpoint    = settings.headerBreakpoint,
                viewportBreakpoint  = settings.viewportBreakpoint,
                resizeThrottle      = null,
                viewportWidth       = $(window).width();

            // Calculates the pixel equivalent of 1em within the current header
            var grabPixelFontSize = function() {
                var dummy = jQuery('

').appendTo($this),
                    emH   = dummy.height();
                dummy.remove();
                return emH;
            };

            // Most of this function is a (very) stripped down AS3 to JS port of the slabtype
            // algorithm by Eric Loyer with the original comments left intact
            // http://erikloyer.com/index.php/blog/the_slabtype_algorithm_part_1_background/
            var resizeSlabs = function resizeSlabs() {

                // Cache the parent containers width
                var parentWidth = $this.width(),
                    fs;

                // Remove the slabtextdone and slabtextinactive classnames to enable the inline-block shrink-wrap effect
                $this.removeClass("slabtextdone slabtextinactive");

                if(viewportBreakpoint && viewportBreakpoint > viewportWidth
                   ||
                   headerBreakpoint && headerBreakpoint > parentWidth) {
                    // Add the slabtextinactive classname to set the spans as inline
                    // and to reset the font-size to 1em (inherit won't work in IE7)
                    $this.addClass("slabtextinactive");
                    return;
                };

                fs = grabPixelFontSize();
                // If the parent containers font-size has changed or the "forceNewCharCount" option is true (the default),
                // then recalculate the "characters per line" count and re-render the inner spans
                // Setting "forceNewCharCount" to false will save CPU cycles...
                if(!keepSpans && (forceNewCharCount || fs != origFontSize)) {

                    origFontSize = fs;

                    var newCharPerLine      = Math.min(60, Math.floor(parentWidth / (origFontSize * fontRatio))),
                        wordIndex           = 0,
                        lineText            = [],
                        counter             = 0,
                        preText             = "",
                        postText            = "",
                        finalText           = "",
                        preDiff,
                        postDiff;

                    if(newCharPerLine != idealCharPerLine) {
                        idealCharPerLine = newCharPerLine;

                        while (wordIndex < words.length) {

                            postText = "";

                            // build two strings (preText and postText) word by word, with one
                            // string always one word behind the other, until
                            // the length of one string is less than the ideal number of characters
                            // per line, while the length of the other is greater than that ideal
                            while (postText.length < idealCharPerLine) {
                                preText   = postText;
                                postText += words[wordIndex] + " ";
                                if(++wordIndex >= words.length) {
                                    break;
                                };
                            };

                            // calculate the character difference between the two strings and the
                            // ideal number of characters per line
                            preDiff  = idealCharPerLine - preText.length;
                            postDiff = postText.length - idealCharPerLine;

                            // if the smaller string is closer to the length of the ideal than
                            // the longer string, and doesn’t contain just a single character, then
                            // use that one for the line
                            if((preDiff < postDiff) && (preText.length > 2)) {
                                finalText = preText;
                                wordIndex--;
                            // otherwise, use the longer string for the line
                            } else {
                                finalText = postText;
                            };

                            lineText.push('' + $.trim(settings.wrapAmpersand ? finalText.replace("&", '&') : finalText) + "");
                        };

                        $this.html(lineText.join(" "));
                    };
                } else {
                    // We only need the font-size for the resize-to-fit functionality
                    // if not injecting the spans
                    origFontSize = fs;
                };

                // Loop through the spans changing font-size accordingly
                $("span.slabtext", $this).each(function() {
                    var $span       = $(this),
                        innerText   = $span.text(),
                        wordSpacing = innerText.split(" ").length > 1,
                        diff,
                        ratio,
                        fontSize;

                    $span.css('word-spacing', 0).css('letter-spacing', 0);

                    ratio    = parentWidth / $span.width();
                    fontSize = parseFloat(this.style.fontSize) || origFontSize;

                    // Resize font
                    $span.css('font-size', (fontSize * ratio).toFixed(3) + "px");

                    // Do we still have space to try to fill or crop
                    diff = parentWidth - $span.width();

                    // A "dumb" tweak in the blind hope that the browser will
                    // resize the text to better fit the available space.
                    // Better "dumb" and fast...
                    if(diff) {
                        $span.css((wordSpacing ? 'word' : 'letter') + '-spacing', (diff / (wordSpacing ? innerText.split(" ").length - 1 : innerText.length)).toFixed(3) + "px");
                    };
                });

                // Add the class slabtextdone to set a display:block on the child spans
                // and avoid styling & layout issues associated with inline-block
                $this.addClass("slabtextdone");
            };

            // Immediate resize
            resizeSlabs();

            if(!settings.noResizeEvent) {
                // Window resize event
                $(window).resize(function() {
                    // Only run the resize code if the viewport width has changed.
                    // we ignore the viewport height as it will be constantly changing
                    // due to the font-size resizing and IE fires a resize event whenever
                    // vertical height has changed leading to an endless loop and
                    // locked browser.
                    if($(window).width() == viewportWidth) {
                        return;
                    };

                    viewportWidth = $(window).width();

                    // Throttle the resize event to 300ms
                    clearTimeout(resizeThrottle);
                    resizeThrottle = setTimeout(resizeSlabs, 300);
                });
            };
        });
    };
})(jQuery);<commit_msg>Maximum font size added to prevent layout breaking with extremely short slabs (e.g. 1-char words)<commit_after>/*! jQuery slabtext plugin v2 MIT/GPL2 @freqdec */
(function( $ ){

    $.fn.slabText = function(options) {

        var settings = {
            // The ratio used when calculating the characters per line
            // (parent width / (font-size * fontRatio)).
            "fontRatio"             : 0.78,
            // Always recalculate the characters per line, not just when the
            // font-size changes? Defaults to true (CPU intensive)
            "forceNewCharCount"     : true,
            // Do we wrap ampersands in  ?
            "wrapAmpersand"         : true,
            // Under what pixel width do we remove the slabtext styling?
            "headerBreakpoint"      : null,
            "viewportBreakpoint"    : null,
            // Don't attach a resize event
            "noResizeEvent"         : false,
            "maxFontSize"           : 80
            };

        // Add the slabtexted classname to the body to initiate the styling of
        // the injected spans
        $("body").addClass("slabtexted");

        return this.each(function(){

            // Extend options if necessary
            if(options) {
                    $.extend(settings, options);
            };

            var $this               = $(this),
                keepSpans           = $("span.slabtext", $this).length,
                words               = keepSpans ? [] : String($.trim($this.text())).replace(/\s{2,}/g, " ").split(" "),
                origFontSize        = null,
                idealCharPerLine    = null,
                fontRatio           = settings.fontRatio,
                forceNewCharCount   = settings.forceNewCharCount,
                headerBreakpoint    = settings.headerBreakpoint,
                viewportBreakpoint  = settings.viewportBreakpoint,
                resizeThrottle      = null,
                viewportWidth       = $(window).width();

            // Calculates the pixel equivalent of 1em within the current header
            var grabPixelFontSize = function() {
                var dummy = jQuery('

').appendTo($this),
                    emH   = dummy.height();
                dummy.remove();
                return emH;
            };

            // Most of this function is a (very) stripped down AS3 to JS port of the slabtype
            // algorithm by Eric Loyer with the original comments left intact
            // http://erikloyer.com/index.php/blog/the_slabtype_algorithm_part_1_background/
            var resizeSlabs = function resizeSlabs() {

                // Cache the parent containers width
                var parentWidth = $this.width(),
                    fs;

                // Remove the slabtextdone and slabtextinactive classnames to enable the inline-block shrink-wrap effect
                $this.removeClass("slabtextdone slabtextinactive");

                if(viewportBreakpoint && viewportBreakpoint > viewportWidth
                   ||
                   headerBreakpoint && headerBreakpoint > parentWidth) {
                    // Add the slabtextinactive classname to set the spans as inline
                    // and to reset the font-size to 1em (inherit won't work in IE7)
                    $this.addClass("slabtextinactive");
                    return;
                };

                fs = grabPixelFontSize();
                // If the parent containers font-size has changed or the "forceNewCharCount" option is true (the default),
                // then recalculate the "characters per line" count and re-render the inner spans
                // Setting "forceNewCharCount" to false will save CPU cycles...
                if(!keepSpans && (forceNewCharCount || fs != origFontSize)) {

                    origFontSize = fs;

                    var newCharPerLine      = Math.min(60, Math.floor(parentWidth / (origFontSize * fontRatio))),
                        wordIndex           = 0,
                        lineText            = [],
                        counter             = 0,
                        preText             = "",
                        postText            = "",
                        finalText           = "",
                        preDiff,
                        postDiff;

                    if(newCharPerLine != idealCharPerLine) {
                        idealCharPerLine = newCharPerLine;

                        while (wordIndex < words.length) {

                            postText = "";

                            // build two strings (preText and postText) word by word, with one
                            // string always one word behind the other, until
                            // the length of one string is less than the ideal number of characters
                            // per line, while the length of the other is greater than that ideal
                            while (postText.length < idealCharPerLine) {
                                preText   = postText;
                                postText += words[wordIndex] + " ";
                                if(++wordIndex >= words.length) {
                                    break;
                                };
                            };

                            // calculate the character difference between the two strings and the
                            // ideal number of characters per line
                            preDiff  = idealCharPerLine - preText.length;
                            postDiff = postText.length - idealCharPerLine;

                            // if the smaller string is closer to the length of the ideal than
                            // the longer string, and doesn’t contain just a single character, then
                            // use that one for the line
                            if((preDiff < postDiff) && (preText.length > 2)) {
                                finalText = preText;
                                wordIndex--;
                            // otherwise, use the longer string for the line
                            } else {
                                finalText = postText;
                            };

                            lineText.push('' + $.trim(settings.wrapAmpersand ? finalText.replace("&", '&') : finalText) + "");
                        };

                        $this.html(lineText.join(" "));
                    };
                } else {
                    // We only need the font-size for the resize-to-fit functionality
                    // if not injecting the spans
                    origFontSize = fs;
                };

                // Loop through the spans changing font-size accordingly
                $("span.slabtext", $this).each(function() {
                    var $span       = $(this),
                        innerText   = $span.text(),
                        wordSpacing = innerText.split(" ").length > 1,
                        diff,
                        ratio,
                        fontSize;

                    $span.css('word-spacing', 0).css('letter-spacing', 0);

                    ratio    = parentWidth / $span.width();
                    fontSize = parseFloat(this.style.fontSize) || origFontSize;

                    // Resize font
                    $span.css('font-size', Math.min((fontSize * ratio).toFixed(3), settings.maxFontSize) + "px");

                    // Do we still have space to try to fill or crop
                    diff = parentWidth - $span.width();

                    // A "dumb" tweak in the blind hope that the browser will
                    // resize the text to better fit the available space.
                    // Better "dumb" and fast...
                    if(diff) {
                        $span.css((wordSpacing ? 'word' : 'letter') + '-spacing', (diff / (wordSpacing ? innerText.split(" ").length - 1 : innerText.length)).toFixed(3) + "px");
                    };
                });

                // Add the class slabtextdone to set a display:block on the child spans
                // and avoid styling & layout issues associated with inline-block
                $this.addClass("slabtextdone");
            };

            // Immediate resize
            resizeSlabs();

            if(!settings.noResizeEvent) {
                // Window resize event
                $(window).resize(function() {
                    // Only run the resize code if the viewport width has changed.
                    // we ignore the viewport height as it will be constantly changing
                    // due to the font-size resizing and IE fires a resize event whenever
                    // vertical height has changed leading to an endless loop and
                    // locked browser.
                    if($(window).width() == viewportWidth) {
                        return;
                    };

                    viewportWidth = $(window).width();

                    // Throttle the resize event to 300ms
                    clearTimeout(resizeThrottle);
                    resizeThrottle = setTimeout(resizeSlabs, 300);
                });
            };
        });
    };
})(jQuery);<|endoftext|>


<commit_before><commit_msg>Update fetch mock<commit_after><|endoftext|>


<commit_before>

/*****************************
 * SERIALIZING DOM AS SCRIPT *
 *****************************/

function serializeDOMAsScript(root, splitTextNodes, splitStyleAttributes)
{
  var cs = serializeTreeAsScript(root, splitTextNodes, splitStyleAttributes);
  var c;
  for (var i = 0; (c = cs[i]); ++i) {
    dumpln(fuzzRecord(oPrefix2, 0, "fun: function() { " + c + " }"));
  }
  //dumpln(fuzzRecord(oPrefix2, 0, "rest: true"));
  //dumpln(fuzzRecord(oPrefix2, 0, "fun: function() { document.documentElement.offsetHeight; }"));
}

// Walks the tree in document order, not in o[...] order, mostly
// because we want it to work after the DOM has been modified heavily.
// This means it will fail for weird DOMs involving XBL or frames,
// for example.

// XXX do something about CSS @import with relative URLs (as used by wikipedia)
// (add a base href?)

function serializeTreeAsScript(root, splitTextNodes, splitStyleAttributes)
{
  function warn(s) {
    dumpln(s);
  }

  var rootStr = null;
  var magic = false;

  if (root == null) {
    root = document.documentElement;
    rootStr = "document.documentElement";
    magic = true;
  } else if (typeof root == "string") {
    // in case the page redefined eval, clobber. amazingly, this "works".
    if (typeof eval != "function")
      delete window.eval;
    rootStr = root;
    root = eval(root);
  }

  var cs = [];
  var createRoot = !magic;

  if (magic) {
    // Clear out the DOM.
    cs.push("var root = " + rootStr + "; while(root.firstChild) { root.removeChild(root.firstChild); }");

    if (root == document.documentElement) {
      cs.push(ensureIndexed(document.documentElement) + " = document.documentElement;");
    } else if (root == null) {
      cs.push("document.removeChild(document.documentElement);");
    } else {
      warn("Missing root!?");
      createRoot = true;
    }

    cs.push("/* DD" + "BEGIN */");
  }

  if (root) {
    serializeSubtreeAsScript(root, "null /* root has no ancestor chain! */", rootStr, true, createRoot);
  }

  if (magic) {
    // Look for additional disconnected subtrees in o.
    // XXX Maybe it should crawl up the parentNode chain all
    // the way to the document or document fragment, and serialize *that*.
    for (var i = 0; i < o.length; ++i) {
      var n = o[i];
      if (n && n instanceof Node && n != document && n != root) {
        if (!n.parentNode) {
          cs.push("/* o[" + i + "] has no parent (disconnected) */");
          serializeSubtreeAsScript(n, "null", "o[" + i + "]", true, true);
        } else if (n.parentNode.nodeType == 11) {
          cs.push("/* o[" + i + "] has a document fragment as its parent */");
          serializeSubtreeAsScript(n, "null", "o[" + i + "]", true, true);
        } else if (n.parentNode.nodeType == 9) {
          // Covered better by case 9 below, assuming the document is indexed
          //cs.push("/* o[" + i + "] has a document as its parent */");
          //serializeSubtreeAsScript(n, "null", "o[" + i + "]", true, true);
        }
      }
    }

    // Add selections
    var sel = window.getSelection();
    cs.push("window.getSelection().removeAllRanges();");
    for (var i = 0; i < sel.rangeCount; ++i) {
      var range = sel.getRangeAt(i);
      var rangeStr = ensureIndexed(range);
      cs.push(rangeStr + " = document.createRange();");
      cs.push(rangeStr + ".setStart(" + ensureIndexed(range.startContainer) + ", " + range.startOffset + ");");
      cs.push(rangeStr + ".setEnd(" + ensureIndexed(range.endContainer) + ", " + range.endOffset + ");");
      cs.push("window.getSelection().addRange(" + rangeStr + ");");
    }
  }

  /*
  // Untested
  var rootix = Things.findIndex(root);
  if (rootix > 0)
    cs.push("o[" + rootix + "] = " + rootStr);
  */

  return cs;

  function ensureIndexed(n)
  {
    var ix = Things.findIndex(n);
    if (ix === -1) {
      try {
        if (n.getAttribute && n.getAttribute("fuzzskip")) {
          // e.g. for adding a base href tag
          return null;
        }
      } catch(e) {
        /* A native anonymous node? */
      }
      var description = "???";
      try {
        description = Object.prototype.toString.call(n);
      } catch(e) {
      }
      ix = o.length;
      o[ix] = n;
      warn("serializeDOMAsScript indexed a " + description + " as o[" + ix + "].");
    }
    return "o[" + ix + "]";
  }

  // No return value; side effect is to push commands into |cs| in its closure.
  function serializeText(nodeStr, funName, text)
  {
    // Try to keep "document.write" in inline-script from being preserved,
    // because document.write blows away the document in other contexts.
    text = text.replace(/document\.write/g, "tnemucod.write");

    if (!splitTextNodes) {
      cs.push(nodeStr + " = document." + funName + "(" + simpleSource(text) + ");");
    } else {
      cs.push(nodeStr + " = document." + funName + "(\"\");");
      for (var i = 0; i < text.length; ++i) {
        cs.push(nodeStr + ".data += " + simpleSource(text[i]) + ";");
      }
    }
  }

  // No return value; side effect is to push commands into |cs| in its closure.
  function serializeSubtreeAsScript(n, ancestryPathStr, rootStr, isRoot, createRoot)
  {
    var i, a, c;

    var nodeStr = (isRoot && !createRoot && rootStr) ? rootStr : ensureIndexed(n);
    if (!nodeStr) {
      return;
    }
    if (createRoot)
      ancestryPathStr = nodeStr;

    switch (n.nodeType) {
      case 1:

        // Creating the element.
        if (!isRoot || createRoot) {
          if (n.namespaceURI) {
            // We use localName instead of tagName because the capitalization for HTML is correct.  But this throws away the XML prefix :(
            cs.push(nodeStr + " = document.createElementNS(" + simpleSource(n.namespaceURI) + ", " + simpleSource(n.localName)  + ");");
          } else {
            // In old versions of Gecko (and possibly in other older browsers), only createElement can create working HTML elements. (See bug 393340.)
            cs.push(nodeStr + " = document.createElement(" + simpleSource(n.tagName).toLowerCase() + ");");
          }
        }

        // Adding each of the element's attributes.
        var nodeIsSVG = (n.namespaceURI == "http://www.w3.org/2000/svg");
        for (i = 0; (a = n.attributes[i]); i++) {
          if (a.namespaceURI != "http://www.w3.org/2000/xmlns/") {
            if (splitStyleAttributes && a.name == "style" && n.style) {
              for (var prop, j = 0; (prop = n.style.item(j)); ++j) {
                var value = n.style.getPropertyValue(prop);
                var priority = n.style.getPropertyPriority(prop);

                // This should be a more precise regexp so it doesn't catch things like "inline-box"
                if (value.indexOf("e+") != -1 || value.indexOf("e-") != -1) {
                  warn("A style property has exponential notation (bug 373875), " + value);
                }

                cs.push(nodeStr + ".style.setProperty(" + simpleSource(prop) + ", " + simpleSource(value) + ", " + simpleSource(priority) + ");");
              }
            } else { // not splitting a style attribute
              var val;
              if (!nodeIsSVG && (a.name.toLowerCase() == "src" || a.name.toLowerCase() == "href")) {
                // For these URI attributes, use the corresponding getter
                // (which is always an absolute URI) rather than the
                // attribute value (which is often a relative URI)
                // for easier reduction.
                // But don't use this for SVG, because it will give you a useless SVGAnimatedString!
                val = n[a.name];
              } else {
                val = a.value;
                if (a.localName == "href" && val.charAt(0) != "#") {
                  // Turn the URL absolute.  Useful for xlink:href in SVG documents.
                  var tempLink = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
                  tempLink.href = val;
                  val = tempLink.href;
                }
              }

              cs.push(nodeStr + ".setAttributeNS(" + simpleSource(a.namespaceURI) + ", " + simpleSource(a.name) + ", " + simpleSource(val) + ");");
            }
          }
        }

        // Adding each of the element's children (recurse).
        var newAncestryPathStr = isRoot ? (nodeStr) : (nodeStr + " || " + ancestryPathStr);

        for (i = 0; (c = n.childNodes[i]); ++i) {
          serializeSubtreeAsScript(c, newAncestryPathStr, null, false, false);
        }

        break;

      case 8:
        cs.push(nodeStr + " = document.createComment(" + simpleSource(n.data) + ");");
        break;

      case 3:
        serializeText(nodeStr, "createTextNode", n.data);
        break;

      case 4:
        serializeText(nodeStr, "createCDATASection", n.data);
        break;

      case 9:
        cs.push(nodeStr + " = document.implementation.createDocument('', '', null);");
        var newAncestryPathStr = isRoot ? (rootStr) : (nodeStr + " || " + ancestryPathStr);
        for (i = 0; (c = n.childNodes[i]); ++i) {
          serializeSubtreeAsScript(c, newAncestryPathStr, null, false, false);
        }
        break;

      default:
        warn("serializeDOMAsScript: " + nodeStr + " has unrecognized node type " + n.nodeType + ".");
    }

    // Finally, add this node to its parent (or closest surviving ancestor).
    // Using the closest surviving ancestor helps reduction.
    if (!isRoot)
      cs.push("(" + ancestryPathStr + ").appendChild(" + nodeStr + ");");
  }
}

<commit_msg>Only call window.getSelection().removeAllRanges() when specifying a selection. This fixes a problem where veto.js was classifying all repro testcases as 'allowed to render inconsistently' due to a 'select' entry in veto.js.<commit_after>

/*****************************
 * SERIALIZING DOM AS SCRIPT *
 *****************************/

function serializeDOMAsScript(root, splitTextNodes, splitStyleAttributes)
{
  var cs = serializeTreeAsScript(root, splitTextNodes, splitStyleAttributes);
  var c;
  for (var i = 0; (c = cs[i]); ++i) {
    dumpln(fuzzRecord(oPrefix2, 0, "fun: function() { " + c + " }"));
  }
  //dumpln(fuzzRecord(oPrefix2, 0, "rest: true"));
  //dumpln(fuzzRecord(oPrefix2, 0, "fun: function() { document.documentElement.offsetHeight; }"));
}

// Walks the tree in document order, not in o[...] order, mostly
// because we want it to work after the DOM has been modified heavily.
// This means it will fail for weird DOMs involving XBL or frames,
// for example.

// XXX do something about CSS @import with relative URLs (as used by wikipedia)
// (add a base href?)

function serializeTreeAsScript(root, splitTextNodes, splitStyleAttributes)
{
  function warn(s) {
    dumpln(s);
  }

  var rootStr = null;
  var magic = false;

  if (root == null) {
    root = document.documentElement;
    rootStr = "document.documentElement";
    magic = true;
  } else if (typeof root == "string") {
    // in case the page redefined eval, clobber. amazingly, this "works".
    if (typeof eval != "function")
      delete window.eval;
    rootStr = root;
    root = eval(root);
  }

  var cs = [];
  var createRoot = !magic;

  if (magic) {
    // Clear out the DOM.
    cs.push("var root = " + rootStr + "; while(root.firstChild) { root.removeChild(root.firstChild); }");

    if (root == document.documentElement) {
      cs.push(ensureIndexed(document.documentElement) + " = document.documentElement;");
    } else if (root == null) {
      cs.push("document.removeChild(document.documentElement);");
    } else {
      warn("Missing root!?");
      createRoot = true;
    }

    cs.push("/* DD" + "BEGIN */");
  }

  if (root) {
    serializeSubtreeAsScript(root, "null /* root has no ancestor chain! */", rootStr, true, createRoot);
  }

  if (magic) {
    // Look for additional disconnected subtrees in o.
    // XXX Maybe it should crawl up the parentNode chain all
    // the way to the document or document fragment, and serialize *that*.
    for (var i = 0; i < o.length; ++i) {
      var n = o[i];
      if (n && n instanceof Node && n != document && n != root) {
        if (!n.parentNode) {
          cs.push("/* o[" + i + "] has no parent (disconnected) */");
          serializeSubtreeAsScript(n, "null", "o[" + i + "]", true, true);
        } else if (n.parentNode.nodeType == 11) {
          cs.push("/* o[" + i + "] has a document fragment as its parent */");
          serializeSubtreeAsScript(n, "null", "o[" + i + "]", true, true);
        } else if (n.parentNode.nodeType == 9) {
          // Covered better by case 9 below, assuming the document is indexed
          //cs.push("/* o[" + i + "] has a document as its parent */");
          //serializeSubtreeAsScript(n, "null", "o[" + i + "]", true, true);
        }
      }
    }

    // Add selections
    var sel = window.getSelection();
    if (sel.rangeCount > 0) {
      cs.push("window.getSelection().removeAllRanges();");
      for (var i = 0; i < sel.rangeCount; ++i) {
        var range = sel.getRangeAt(i);
        var rangeStr = ensureIndexed(range);
        cs.push(rangeStr + " = document.createRange();");
        cs.push(rangeStr + ".setStart(" + ensureIndexed(range.startContainer) + ", " + range.startOffset + ");");
        cs.push(rangeStr + ".setEnd(" + ensureIndexed(range.endContainer) + ", " + range.endOffset + ");");
        cs.push("window.getSelection().addRange(" + rangeStr + ");");
      }
    }
  }

  /*
  // Untested
  var rootix = Things.findIndex(root);
  if (rootix > 0)
    cs.push("o[" + rootix + "] = " + rootStr);
  */

  return cs;

  function ensureIndexed(n)
  {
    var ix = Things.findIndex(n);
    if (ix === -1) {
      try {
        if (n.getAttribute && n.getAttribute("fuzzskip")) {
          // e.g. for adding a base href tag
          return null;
        }
      } catch(e) {
        /* A native anonymous node? */
      }
      var description = "???";
      try {
        description = Object.prototype.toString.call(n);
      } catch(e) {
      }
      ix = o.length;
      o[ix] = n;
      warn("serializeDOMAsScript indexed a " + description + " as o[" + ix + "].");
    }
    return "o[" + ix + "]";
  }

  // No return value; side effect is to push commands into |cs| in its closure.
  function serializeText(nodeStr, funName, text)
  {
    // Try to keep "document.write" in inline-script from being preserved,
    // because document.write blows away the document in other contexts.
    text = text.replace(/document\.write/g, "tnemucod.write");

    if (!splitTextNodes) {
      cs.push(nodeStr + " = document." + funName + "(" + simpleSource(text) + ");");
    } else {
      cs.push(nodeStr + " = document." + funName + "(\"\");");
      for (var i = 0; i < text.length; ++i) {
        cs.push(nodeStr + ".data += " + simpleSource(text[i]) + ";");
      }
    }
  }

  // No return value; side effect is to push commands into |cs| in its closure.
  function serializeSubtreeAsScript(n, ancestryPathStr, rootStr, isRoot, createRoot)
  {
    var i, a, c;

    var nodeStr = (isRoot && !createRoot && rootStr) ? rootStr : ensureIndexed(n);
    if (!nodeStr) {
      return;
    }
    if (createRoot)
      ancestryPathStr = nodeStr;

    switch (n.nodeType) {
      case 1:

        // Creating the element.
        if (!isRoot || createRoot) {
          if (n.namespaceURI) {
            // We use localName instead of tagName because the capitalization for HTML is correct.  But this throws away the XML prefix :(
            cs.push(nodeStr + " = document.createElementNS(" + simpleSource(n.namespaceURI) + ", " + simpleSource(n.localName)  + ");");
          } else {
            // In old versions of Gecko (and possibly in other older browsers), only createElement can create working HTML elements. (See bug 393340.)
            cs.push(nodeStr + " = document.createElement(" + simpleSource(n.tagName).toLowerCase() + ");");
          }
        }

        // Adding each of the element's attributes.
        var nodeIsSVG = (n.namespaceURI == "http://www.w3.org/2000/svg");
        for (i = 0; (a = n.attributes[i]); i++) {
          if (a.namespaceURI != "http://www.w3.org/2000/xmlns/") {
            if (splitStyleAttributes && a.name == "style" && n.style) {
              for (var prop, j = 0; (prop = n.style.item(j)); ++j) {
                var value = n.style.getPropertyValue(prop);
                var priority = n.style.getPropertyPriority(prop);

                // This should be a more precise regexp so it doesn't catch things like "inline-box"
                if (value.indexOf("e+") != -1 || value.indexOf("e-") != -1) {
                  warn("A style property has exponential notation (bug 373875), " + value);
                }

                cs.push(nodeStr + ".style.setProperty(" + simpleSource(prop) + ", " + simpleSource(value) + ", " + simpleSource(priority) + ");");
              }
            } else { // not splitting a style attribute
              var val;
              if (!nodeIsSVG && (a.name.toLowerCase() == "src" || a.name.toLowerCase() == "href")) {
                // For these URI attributes, use the corresponding getter
                // (which is always an absolute URI) rather than the
                // attribute value (which is often a relative URI)
                // for easier reduction.
                // But don't use this for SVG, because it will give you a useless SVGAnimatedString!
                val = n[a.name];
              } else {
                val = a.value;
                if (a.localName == "href" && val.charAt(0) != "#") {
                  // Turn the URL absolute.  Useful for xlink:href in SVG documents.
                  var tempLink = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
                  tempLink.href = val;
                  val = tempLink.href;
                }
              }

              cs.push(nodeStr + ".setAttributeNS(" + simpleSource(a.namespaceURI) + ", " + simpleSource(a.name) + ", " + simpleSource(val) + ");");
            }
          }
        }

        // Adding each of the element's children (recurse).
        var newAncestryPathStr = isRoot ? (nodeStr) : (nodeStr + " || " + ancestryPathStr);

        for (i = 0; (c = n.childNodes[i]); ++i) {
          serializeSubtreeAsScript(c, newAncestryPathStr, null, false, false);
        }

        break;

      case 8:
        cs.push(nodeStr + " = document.createComment(" + simpleSource(n.data) + ");");
        break;

      case 3:
        serializeText(nodeStr, "createTextNode", n.data);
        break;

      case 4:
        serializeText(nodeStr, "createCDATASection", n.data);
        break;

      case 9:
        cs.push(nodeStr + " = document.implementation.createDocument('', '', null);");
        var newAncestryPathStr = isRoot ? (rootStr) : (nodeStr + " || " + ancestryPathStr);
        for (i = 0; (c = n.childNodes[i]); ++i) {
          serializeSubtreeAsScript(c, newAncestryPathStr, null, false, false);
        }
        break;

      default:
        warn("serializeDOMAsScript: " + nodeStr + " has unrecognized node type " + n.nodeType + ".");
    }

    // Finally, add this node to its parent (or closest surviving ancestor).
    // Using the closest surviving ancestor helps reduction.
    if (!isRoot)
      cs.push("(" + ancestryPathStr + ").appendChild(" + nodeStr + ");");
  }
}

<|endoftext|>