/
githubmirror
/
brackets
Обзор
Документация
Войти
/
githubmirror
/
brackets
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/LiveDevelopment/Agents/RemoteFunctions.js
1 077 строк
38 KB
Wojciech Połowniak
Fix box model regression (transform) (#13357)
24 май 2017, 20:28
24 май 2017, 20:28
881b648
Код
Авторство
О чём код?
/* * Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /*jslint forin: true */ /*global Node, MessageEvent */ /*theseus instrument: false */ /** * RemoteFunctions define the functions to be executed in the browser. This * modules should define a single function that returns an object of all * exported functions. */ function RemoteFunctions(config, remoteWSPort) { "use strict"; var experimental; if (!config) { experimental = false; } else { experimental = config.experimental; } var lastKeepAliveTime = Date.now(); var req, timeout; var animateHighlight = function (time) { if(req) { window.cancelAnimationFrame(req); window.clearTimeout(timeout); } req = window.requestAnimationFrame(redrawHighlights); timeout = setTimeout(function () { window.cancelAnimationFrame(req); req = null; }, time * 1000); }; /** * @type {DOMEditHandler} */ var _editHandler; var HIGHLIGHT_CLASSNAME = "__brackets-ld-highlight", KEEP_ALIVE_TIMEOUT = 3000; // Keep alive timeout value, in milliseconds // determine whether an event should be processed for Live Development function _validEvent(event) { if (window.navigator.platform.substr(0, 3) === "Mac") { // Mac return event.metaKey; } else { // Windows return event.ctrlKey; } } // determine the color for a type function _typeColor(type, highlight) { switch (type) { case "html": return highlight ? "#eec" : "#ffe"; case "css": return highlight ? "#cee" : "#eff"; case "js": return highlight ? "#ccf" : "#eef"; default: return highlight ? "#ddd" : "#eee"; } } // compute the screen offset of an element function _screenOffset(element) { var elemBounds = element.getBoundingClientRect(), body = window.document.body, offsetTop, offsetLeft; if (window.getComputedStyle(body).position === "static") { offsetLeft = elemBounds.left + window.pageXOffset; offsetTop = elemBounds.top + window.pageYOffset; } else { var bodyBounds = body.getBoundingClientRect(); offsetLeft = elemBounds.left - bodyBounds.left; offsetTop = elemBounds.top - bodyBounds.top; } return { left: offsetLeft, top: offsetTop }; } // set an event on a element function _trigger(element, name, value, autoRemove) { var key = "data-ld-" + name; if (value !== undefined && value !== null) { element.setAttribute(key, value); if (autoRemove) { window.setTimeout(element.removeAttribute.bind(element, key)); } } else { element.removeAttribute(key); } } // Checks if the element is in Viewport in the client browser function isInViewport(element) { var rect = element.getBoundingClientRect(); var html = window.document.documentElement; return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || html.clientHeight) && rect.right <= (window.innerWidth || html.clientWidth) ); } // returns the distance from the top of the closest relatively positioned parent element function getDocumentOffsetTop(element) { return element.offsetTop + (element.offsetParent ? getDocumentOffsetTop(element.offsetParent) : 0); } // construct the info menu function Menu(element) { this.element = element; _trigger(this.element, "showgoto", 1, true); window.setTimeout(window.remoteShowGoto); this.remove = this.remove.bind(this); } Menu.prototype = { onClick: function (url, event) { event.preventDefault(); _trigger(this.element, "goto", url, true); this.remove(); }, createBody: function () { if (this.body) { return; } // compute the position on screen var offset = _screenOffset(this.element), x = offset.left, y = offset.top + this.element.offsetHeight; // create the container this.body = window.document.createElement("div"); this.body.style.setProperty("z-index", 2147483647); this.body.style.setProperty("position", "absolute"); this.body.style.setProperty("left", x + "px"); this.body.style.setProperty("top", y + "px"); this.body.style.setProperty("font-size", "11pt"); // draw the background this.body.style.setProperty("background", "#fff"); this.body.style.setProperty("border", "1px solid #888"); this.body.style.setProperty("-webkit-box-shadow", "2px 2px 6px 0px #ccc"); this.body.style.setProperty("border-radius", "6px"); this.body.style.setProperty("padding", "6px"); }, addItem: function (target) { var item = window.document.createElement("div"); item.style.setProperty("padding", "2px 6px"); if (this.body.childNodes.length > 0) { item.style.setProperty("border-top", "1px solid #ccc"); } item.style.setProperty("cursor", "pointer"); item.style.setProperty("background", _typeColor(target.type)); item.innerHTML = target.name; item.addEventListener("click", this.onClick.bind(this, target.url)); if (target.file) { var file = window.document.createElement("i"); file.style.setProperty("float", "right"); file.style.setProperty("margin-left", "12px"); file.innerHTML = " " + target.file; item.appendChild(file); } this.body.appendChild(item); }, show: function () { if (!this.body) { this.body = this.createBody(); } if (!this.body.parentNode) { window.document.body.appendChild(this.body); } window.document.addEventListener("click", this.remove); }, remove: function () { if (this.body && this.body.parentNode) { window.document.body.removeChild(this.body); } window.document.removeEventListener("click", this.remove); } }; function Editor(element) { this.onBlur = this.onBlur.bind(this); this.onKeyPress = this.onKeyPress.bind(this); this.element = element; this.element.setAttribute("contenteditable", "true"); this.element.focus(); this.element.addEventListener("blur", this.onBlur); this.element.addEventListener("keypress", this.onKeyPress); this.revertText = this.element.innerHTML; _trigger(this.element, "edit", 1); } Editor.prototype = { onBlur: function (event) { this.element.removeAttribute("contenteditable"); this.element.removeEventListener("blur", this.onBlur); this.element.removeEventListener("keypress", this.onKeyPress); _trigger(this.element, "edit", 0, true); }, onKeyPress: function (event) { switch (event.which) { case 13: // return this.element.blur(); break; case 27: // esc this.element.innerHTML = this.revertText; this.element.blur(); break; } } }; function Highlight(color, trigger) { this.color = color; this.trigger = !!trigger; this.elements = []; this.selector = ""; } Highlight.prototype = { _elementExists: function (element) { var i; for (i in this.elements) { if (this.elements[i] === element) { return true; } } return false; }, _makeHighlightDiv: function (element, doAnimation) { var elementBounds = element.getBoundingClientRect(), highlight = window.document.createElement("div"), elementStyling = window.getComputedStyle(element), transitionDuration = parseFloat(elementStyling.getPropertyValue('transition-duration')), animationDuration = parseFloat(elementStyling.getPropertyValue('animation-duration')); if (transitionDuration) { animateHighlight(transitionDuration); } if (animationDuration) { animateHighlight(animationDuration); } // Don't highlight elements with 0 width & height if (elementBounds.width === 0 && elementBounds.height === 0) { return; } var realElBorder = { right: elementStyling.getPropertyValue('border-right-width'), left: elementStyling.getPropertyValue('border-left-width'), top: elementStyling.getPropertyValue('border-top-width'), bottom: elementStyling.getPropertyValue('border-bottom-width') }; var borderBox = elementStyling.boxSizing === 'border-box'; var innerWidth = parseFloat(elementStyling.width), innerHeight = parseFloat(elementStyling.height), outerHeight = innerHeight, outerWidth = innerWidth; if (!borderBox) { innerWidth += parseFloat(elementStyling.paddingLeft) + parseFloat(elementStyling.paddingRight); innerHeight += parseFloat(elementStyling.paddingTop) + parseFloat(elementStyling.paddingBottom); outerWidth = innerWidth + parseFloat(realElBorder.right) + parseFloat(realElBorder.left), outerHeight = innerHeight + parseFloat(realElBorder.bottom) + parseFloat(realElBorder.top); } var visualisations = { horizontal: "left, right", vertical: "top, bottom" }; var drawPaddingRect = function(side) { var elStyling = {}; if (visualisations.horizontal.indexOf(side) >= 0) { elStyling['width'] = elementStyling.getPropertyValue('padding-' + side); elStyling['height'] = innerHeight + "px"; elStyling['top'] = 0; if (borderBox) { elStyling['height'] = innerHeight - parseFloat(realElBorder.top) - parseFloat(realElBorder.bottom) + "px"; } } else { elStyling['height'] = elementStyling.getPropertyValue('padding-' + side); elStyling['width'] = innerWidth + "px"; elStyling['left'] = 0; if (borderBox) { elStyling['width'] = innerWidth - parseFloat(realElBorder.left) - parseFloat(realElBorder.right) + "px"; } } elStyling[side] = 0; elStyling['position'] = 'absolute'; return elStyling; }; var drawMarginRect = function(side) { var elStyling = {}; var margin = []; margin['right'] = parseFloat(elementStyling.getPropertyValue('margin-right')); margin['top'] = parseFloat(elementStyling.getPropertyValue('margin-top')); margin['bottom'] = parseFloat(elementStyling.getPropertyValue('margin-bottom')); margin['left'] = parseFloat(elementStyling.getPropertyValue('margin-left')); if(visualisations['horizontal'].indexOf(side) >= 0) { elStyling['width'] = elementStyling.getPropertyValue('margin-' + side); elStyling['height'] = outerHeight + margin['top'] + margin['bottom'] + "px"; elStyling['top'] = "-" + (margin['top'] + parseFloat(realElBorder.top)) + "px"; } else { elStyling['height'] = elementStyling.getPropertyValue('margin-' + side); elStyling['width'] = outerWidth + "px"; elStyling['left'] = "-" + realElBorder.left; } elStyling[side] = "-" + (margin[side] + parseFloat(realElBorder[side])) + "px"; elStyling['position'] = 'absolute'; return elStyling; }; var setVisibility = function (el) { if ( !config.remoteHighlight.showPaddingMargin || parseInt(el.height, 10) <= 0 || parseInt(el.width, 10) <= 0 ) { el.display = 'none'; } else { el.display = 'block'; } }; var mainBoxStyles = config.remoteHighlight.stylesToSet; var paddingVisualisations = [ drawPaddingRect('top'), drawPaddingRect('right'), drawPaddingRect('bottom'), drawPaddingRect('left') ]; var marginVisualisations = [ drawMarginRect('top'), drawMarginRect('right'), drawMarginRect('bottom'), drawMarginRect('left') ]; var setupVisualisations = function (arr, config) { var i; for (i = 0; i < arr.length; i++) { setVisibility(arr[i]); // Applies to every visualisationElement (padding or margin div) arr[i]["transform"] = "none"; var el = window.document.createElement("div"), styles = Object.assign( {}, config, arr[i] ); _setStyleValues(styles, el.style); highlight.appendChild(el); } }; setupVisualisations( marginVisualisations, config.remoteHighlight.marginStyling ); setupVisualisations( paddingVisualisations, config.remoteHighlight.paddingStyling ); highlight.className = HIGHLIGHT_CLASSNAME; var offset = _screenOffset(element); var el = element, offsetLeft = 0, offsetTop = 0; // Probably the easiest way to get elements position without including transform do { offsetLeft += el.offsetLeft; offsetTop += el.offsetTop; el = el.offsetParent; } while(el); var stylesToSet = { "left": offsetLeft + "px", "top": offsetTop + "px", "width": innerWidth + "px", "height": innerHeight + "px", "z-index": 2000000, "margin": 0, "padding": 0, "position": "absolute", "pointer-events": "none", "box-shadow": "0 0 1px #fff", "box-sizing": elementStyling.getPropertyValue('box-sizing'), "border-right": elementStyling.getPropertyValue('border-right'), "border-left": elementStyling.getPropertyValue('border-left'), "border-top": elementStyling.getPropertyValue('border-top'), "border-bottom": elementStyling.getPropertyValue('border-bottom'), "transform": elementStyling.getPropertyValue('transform'), "transform-origin": elementStyling.getPropertyValue('transform-origin'), "border-color": config.remoteHighlight.borderColor }; var mergedStyles = Object.assign({}, stylesToSet, config.remoteHighlight.stylesToSet); var animateStartValues = config.remoteHighlight.animateStartValue; var animateEndValues = config.remoteHighlight.animateEndValue; var transitionValues = { "transition-property": "opacity, background-color, transform", "transition-duration": "300ms, 2.3s" }; function _setStyleValues(styleValues, obj) { var prop; for (prop in styleValues) { obj.setProperty(prop, styleValues[prop]); } } _setStyleValues(mergedStyles, highlight.style); _setStyleValues( doAnimation ? animateStartValues : animateEndValues, highlight.style ); if (doAnimation) { _setStyleValues(transitionValues, highlight.style); window.setTimeout(function () { _setStyleValues(animateEndValues, highlight.style); }, 20); } window.document.body.appendChild(highlight); }, add: function (element, doAnimation) { if (this._elementExists(element) || element === window.document) { return; } if (this.trigger) { _trigger(element, "highlight", 1); } if ((!window.event || window.event instanceof MessageEvent) && !isInViewport(element)) { var top = getDocumentOffsetTop(element); if (top) { top -= (window.innerHeight / 2); window.scrollTo(0, top); } } this.elements.push(element); this._makeHighlightDiv(element, doAnimation); }, clear: function () { var i, highlights = window.document.querySelectorAll("." + HIGHLIGHT_CLASSNAME), body = window.document.body; for (i = 0; i < highlights.length; i++) { body.removeChild(highlights[i]); } if (this.trigger) { for (i = 0; i < this.elements.length; i++) { _trigger(this.elements[i], "highlight", 0); } } this.elements = []; }, redraw: function () { var i, highlighted; // When redrawing a selector-based highlight, run a new selector // query to ensure we have the latest set of elements to highlight. if (this.selector) { highlighted = window.document.querySelectorAll(this.selector); } else { highlighted = this.elements.slice(0); } this.clear(); for (i = 0; i < highlighted.length; i++) { this.add(highlighted[i], false); } } }; var _currentEditor; function _toggleEditor(element) { _currentEditor = new Editor(element); } var _currentMenu; function _toggleMenu(element) { if (_currentMenu) { _currentMenu.remove(); } _currentMenu = new Menu(element); } var _localHighlight; var _remoteHighlight; var _setup = false; /** Event Handlers ***********************************************************/ function onMouseOver(event) { if (_validEvent(event)) { _localHighlight.add(event.target, true); } } function onMouseOut(event) { if (_validEvent(event)) { _localHighlight.clear(); } } function onMouseMove(event) { onMouseOver(event); window.document.removeEventListener("mousemove", onMouseMove); } function onClick(event) { if (_validEvent(event)) { event.preventDefault(); event.stopPropagation(); if (event.altKey) { _toggleEditor(event.target); } else { _toggleMenu(event.target); } } } function onKeyUp(event) { if (_setup && !_validEvent(event)) { window.document.removeEventListener("keyup", onKeyUp); window.document.removeEventListener("mouseover", onMouseOver); window.document.removeEventListener("mouseout", onMouseOut); window.document.removeEventListener("mousemove", onMouseMove); window.document.removeEventListener("click", onClick); _localHighlight.clear(); _localHighlight = undefined; _setup = false; } } function onKeyDown(event) { if (!_setup && _validEvent(event)) { window.document.addEventListener("keyup", onKeyUp); window.document.addEventListener("mouseover", onMouseOver); window.document.addEventListener("mouseout", onMouseOut); window.document.addEventListener("mousemove", onMouseMove); window.document.addEventListener("click", onClick); _localHighlight = new Highlight("#ecc", true); _setup = true; } } /** Public Commands **********************************************************/ // keep alive. Called once a second when a Live Development connection is active. // If several seconds have passed without this method being called, we can assume // that the connection has been severed and we should remove all our code/hooks. function keepAlive() { lastKeepAliveTime = Date.now(); } // show goto function showGoto(targets) { if (!_currentMenu) { return; } _currentMenu.createBody(); var i; for (i in targets) { _currentMenu.addItem(targets[i]); } _currentMenu.show(); } // remove active highlights function hideHighlight() { if (_remoteHighlight) { _remoteHighlight.clear(); _remoteHighlight = null; } } // highlight a node function highlight(node, clear) { if (!_remoteHighlight) { _remoteHighlight = new Highlight("#cfc"); } if (clear) { _remoteHighlight.clear(); } _remoteHighlight.add(node, true); } // highlight a rule function highlightRule(rule) { hideHighlight(); var i, nodes = window.document.querySelectorAll(rule); for (i = 0; i < nodes.length; i++) { highlight(nodes[i]); } _remoteHighlight.selector = rule; } // redraw active highlights function redrawHighlights() { if (_remoteHighlight) { _remoteHighlight.redraw(); } } window.addEventListener("resize", redrawHighlights); // Add a capture-phase scroll listener to update highlights when // any element scrolls. function _scrollHandler(e) { // Document scrolls can be updated immediately. Any other scrolls // need to be updated on a timer to ensure the layout is correct. if (e.target === window.document) { redrawHighlights(); } else { if (_remoteHighlight || _localHighlight) { window.setTimeout(redrawHighlights, 0); } } } window.addEventListener("scroll", _scrollHandler, true); var aliveTest = window.setInterval(function () { if (Date.now() > lastKeepAliveTime + KEEP_ALIVE_TIMEOUT) { // Remove highlights hideHighlight(); // Remove listeners window.removeEventListener("resize", redrawHighlights); window.removeEventListener("scroll", _scrollHandler, true); // Clear this interval window.clearInterval(aliveTest); } }, 1000); /** * Constructor * @param {Document} htmlDocument */ function DOMEditHandler(htmlDocument) { this.htmlDocument = htmlDocument; this.rememberedNodes = null; this.entityParseParent = htmlDocument.createElement("div"); } /** * @private * Find the first matching element with the specified data-brackets-id * @param {string} id * @return {Element} */ DOMEditHandler.prototype._queryBracketsID = function (id) { if (!id) { return null; } if (this.rememberedNodes && this.rememberedNodes[id]) { return this.rememberedNodes[id]; } var results = this.htmlDocument.querySelectorAll("[data-brackets-id='" + id + "']"); return results && results[0]; }; /** * @private * Insert a new child element * @param {Element} targetElement Parent element already in the document * @param {Element} childElement New child element * @param {Object} edit */ DOMEditHandler.prototype._insertChildNode = function (targetElement, childElement, edit) { var before = this._queryBracketsID(edit.beforeID), after = this._queryBracketsID(edit.afterID); if (edit.firstChild) { before = targetElement.firstChild; } else if (edit.lastChild) { after = targetElement.lastChild; } if (before) { targetElement.insertBefore(childElement, before); } else if (after && (after !== targetElement.lastChild)) { targetElement.insertBefore(childElement, after.nextSibling); } else { targetElement.appendChild(childElement); } }; /** * @private * Given a string containing encoded entity references, returns the string with the entities decoded. * @param {string} text The text to parse. * @return {string} The decoded text. */ DOMEditHandler.prototype._parseEntities = function (text) { // Kind of a hack: just set the innerHTML of a div to the text, which will parse the entities, then // read the content out. var result; this.entityParseParent.innerHTML = text; result = this.entityParseParent.textContent; this.entityParseParent.textContent = ""; return result; }; /** * @private * @param {Node} node * @return {boolean} true if node expects its content to be raw text (not parsed for entities) according to the HTML5 spec. */ function _isRawTextNode(node) { return (node.nodeType === Node.ELEMENT_NODE && /script|style|noscript|noframes|noembed|iframe|xmp/i.test(node.tagName)); } /** * @private * Replace a range of text and comment nodes with an optional new text node * @param {Element} targetElement * @param {Object} edit */ DOMEditHandler.prototype._textReplace = function (targetElement, edit) { function prevIgnoringHighlights(node) { do { node = node.previousSibling; } while (node && node.className === HIGHLIGHT_CLASSNAME); return node; } function nextIgnoringHighlights(node) { do { node = node.nextSibling; } while (node && node.className === HIGHLIGHT_CLASSNAME); return node; } function lastChildIgnoringHighlights(node) { node = (node.childNodes.length ? node.childNodes.item(node.childNodes.length - 1) : null); if (node && node.className === HIGHLIGHT_CLASSNAME) { node = prevIgnoringHighlights(node); } return node; } var start = (edit.afterID) ? this._queryBracketsID(edit.afterID) : null, startMissing = edit.afterID && !start, end = (edit.beforeID) ? this._queryBracketsID(edit.beforeID) : null, endMissing = edit.beforeID && !end, moveNext = start && nextIgnoringHighlights(start), current = moveNext || (end && prevIgnoringHighlights(end)) || lastChildIgnoringHighlights(targetElement), next, textNode = (edit.content !== undefined) ? this.htmlDocument.createTextNode(_isRawTextNode(targetElement) ? edit.content : this._parseEntities(edit.content)) : null, lastRemovedWasText, isText; // remove all nodes inside the range while (current && (current !== end)) { isText = current.nodeType === Node.TEXT_NODE; // if start is defined, delete following text nodes // if start is not defined, delete preceding text nodes next = (moveNext) ? nextIgnoringHighlights(current) : prevIgnoringHighlights(current); // only delete up to the nearest element. // if the start/end tag was deleted in a prior edit, stop removing // nodes when we hit adjacent text nodes if ((current.nodeType === Node.ELEMENT_NODE) || ((startMissing || endMissing) && (isText && lastRemovedWasText))) { break; } else { lastRemovedWasText = isText; if (current.remove) { current.remove(); } else if (current.parentNode && current.parentNode.removeChild) { current.parentNode.removeChild(current); } current = next; } } if (textNode) { // OK to use nextSibling here (not nextIgnoringHighlights) because we do literally // want to insert immediately after the start tag. if (start && start.nextSibling) { targetElement.insertBefore(textNode, start.nextSibling); } else if (end) { targetElement.insertBefore(textNode, end); } else { targetElement.appendChild(textNode); } } }; /** * @private * Apply an array of DOM edits to the document * @param {Array.<Object>} edits */ DOMEditHandler.prototype.apply = function (edits) { var targetID, targetElement, childElement, self = this; this.rememberedNodes = {}; edits.forEach(function (edit) { var editIsSpecialTag = edit.type === "elementInsert" && (edit.tag === "html" || edit.tag === "head" || edit.tag === "body"); if (edit.type === "rememberNodes") { edit.tagIDs.forEach(function (tagID) { var node = self._queryBracketsID(tagID); self.rememberedNodes[tagID] = node; if (node.remove) { node.remove(); } else if (node.parentNode && node.parentNode.removeChild) { node.parentNode.removeChild(node); } }); return; } targetID = edit.type.match(/textReplace|textDelete|textInsert|elementInsert|elementMove/) ? edit.parentID : edit.tagID; targetElement = self._queryBracketsID(targetID); if (!targetElement && !editIsSpecialTag) { console.error("data-brackets-id=" + targetID + " not found"); return; } switch (edit.type) { case "attrChange": case "attrAdd": targetElement.setAttribute(edit.attribute, self._parseEntities(edit.value)); break; case "attrDelete": targetElement.removeAttribute(edit.attribute); break; case "elementDelete": if (targetElement.remove) { targetElement.remove(); } else if (targetElement.parentNode && targetElement.parentNode.removeChild) { targetElement.parentNode.removeChild(targetElement); } break; case "elementInsert": childElement = null; if (editIsSpecialTag) { // If we already have one of these elements (which we should), then // just copy the attributes and set the ID. childElement = self.htmlDocument[edit.tag === "html" ? "documentElement" : edit.tag]; if (!childElement) { // Treat this as a normal insertion. editIsSpecialTag = false; } } if (!editIsSpecialTag) { childElement = self.htmlDocument.createElement(edit.tag); } Object.keys(edit.attributes).forEach(function (attr) { childElement.setAttribute(attr, self._parseEntities(edit.attributes[attr])); }); childElement.setAttribute("data-brackets-id", edit.tagID); if (!editIsSpecialTag) { self._insertChildNode(targetElement, childElement, edit); } break; case "elementMove": childElement = self._queryBracketsID(edit.tagID); self._insertChildNode(targetElement, childElement, edit); break; case "textInsert": var textElement = self.htmlDocument.createTextNode(_isRawTextNode(targetElement) ? edit.content : self._parseEntities(edit.content)); self._insertChildNode(targetElement, textElement, edit); break; case "textReplace": case "textDelete": self._textReplace(targetElement, edit); break; } }); this.rememberedNodes = {}; // update highlight after applying diffs redrawHighlights(); }; function applyDOMEdits(edits) { _editHandler.apply(edits); } /** * * @param {Element} elem */ function _domElementToJSON(elem) { var json = { tag: elem.tagName.toLowerCase(), attributes: {}, children: [] }, i, len, node, value; len = elem.attributes.length; for (i = 0; i < len; i++) { node = elem.attributes.item(i); value = (node.name === "data-brackets-id") ? parseInt(node.value, 10) : node.value; json.attributes[node.name] = value; } len = elem.childNodes.length; for (i = 0; i < len; i++) { node = elem.childNodes.item(i); // ignores comment nodes and visuals generated by live preview if (node.nodeType === Node.ELEMENT_NODE && node.className !== HIGHLIGHT_CLASSNAME) { json.children.push(_domElementToJSON(node)); } else if (node.nodeType === Node.TEXT_NODE) { json.children.push({ content: node.nodeValue }); } } return json; } function getSimpleDOM() { return JSON.stringify(_domElementToJSON(window.document.documentElement)); } function updateConfig(newConfig) { config = JSON.parse(newConfig); return JSON.stringify(config); } // init _editHandler = new DOMEditHandler(window.document); if (experimental) { window.document.addEventListener("keydown", onKeyDown); } var _ws = null; function onDocumentClick(event) { var element = event.target, currentDataId, newDataId; if (_ws && element && element.hasAttribute('data-brackets-id')) { _ws.send(JSON.stringify({ type: "message", message: element.getAttribute('data-brackets-id') })); } } function createWebSocket() { _ws = new WebSocket("ws://localhost:" + remoteWSPort); _ws.onopen = function () { window.document.addEventListener("click", onDocumentClick); }; _ws.onmessage = function (evt) { }; _ws.onclose = function () { // websocket is closed window.document.removeEventListener("click", onDocumentClick); }; } if (remoteWSPort) { createWebSocket(); } return { "DOMEditHandler" : DOMEditHandler, "keepAlive" : keepAlive, "showGoto" : showGoto, "hideHighlight" : hideHighlight, "highlight" : highlight, "highlightRule" : highlightRule, "redrawHighlights" : redrawHighlights, "applyDOMEdits" : applyDOMEdits, "getSimpleDOM" : getSimpleDOM, "updateConfig" : updateConfig }; }