forked from mirrors/gecko-dev
Bug 1202458 - part1: inline text nodes in markupview only if they are short enough;r=pbro
The markup view will now inline a textnode in its container if and only if: - the text node is the only child (pseudo elements included) - the text node length is smaller than a predefined limit If a container is expanded, its text nodes will now always be rendered in full, no longer as a short version with an ellipsis. When selecting or navigating on a textnode, the layout will no longer be modified on the fly. MozReview-Commit-ID: HcDMqjbOesN --HG-- extra : rebase_source : 908ba5c1cab86018116271402c90992c1fca6d62 extra : histedit_source : f0083b1e4c54cde0fdce12e1d139baa41e7d6cda
This commit is contained in:
parent
b33a96b184
commit
fc46d5012b
10 changed files with 259 additions and 156 deletions
|
|
@ -162,9 +162,9 @@ Selection.prototype = {
|
||||||
setNodeFront: function (value, reason = "unknown") {
|
setNodeFront: function (value, reason = "unknown") {
|
||||||
this.reason = reason;
|
this.reason = reason;
|
||||||
|
|
||||||
// If a singleTextChild text node is being set, then set it's parent instead.
|
// If an inlineTextChild text node is being set, then set it's parent instead.
|
||||||
let parentNode = value && value.parentNode();
|
let parentNode = value && value.parentNode();
|
||||||
if (value && parentNode && parentNode.singleTextChild === value) {
|
if (value && parentNode && parentNode.inlineTextChild === value) {
|
||||||
value = parentNode;
|
value = parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,6 @@ const EventEmitter = require("devtools/shared/event-emitter");
|
||||||
const Heritage = require("sdk/core/heritage");
|
const Heritage = require("sdk/core/heritage");
|
||||||
const {parseAttribute} =
|
const {parseAttribute} =
|
||||||
require("devtools/client/shared/node-attribute-parser");
|
require("devtools/client/shared/node-attribute-parser");
|
||||||
const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis",
|
|
||||||
Ci.nsIPrefLocalizedString).data;
|
|
||||||
const {Task} = require("devtools/shared/task");
|
const {Task} = require("devtools/shared/task");
|
||||||
const {scrollIntoViewIfNeeded} = require("devtools/shared/layout/utils");
|
const {scrollIntoViewIfNeeded} = require("devtools/shared/layout/utils");
|
||||||
const {PrefObserver} = require("devtools/client/styleeditor/utils");
|
const {PrefObserver} = require("devtools/client/styleeditor/utils");
|
||||||
|
|
@ -989,6 +987,10 @@ MarkupView.prototype = {
|
||||||
// accessibility where necessary.
|
// accessibility where necessary.
|
||||||
this._updateChildren(container, {flash: true}).then(() =>
|
this._updateChildren(container, {flash: true}).then(() =>
|
||||||
container.updateLevel());
|
container.updateLevel());
|
||||||
|
} else if (type === "inlineTextChild") {
|
||||||
|
container.childrenDirty = true;
|
||||||
|
this._updateChildren(container, {flash: true});
|
||||||
|
container.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1549,19 +1551,19 @@ MarkupView.prototype = {
|
||||||
return promise.resolve(container);
|
return promise.resolve(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.singleTextChild
|
if (container.inlineTextChild
|
||||||
&& container.singleTextChild != container.node.singleTextChild) {
|
&& container.inlineTextChild != container.node.inlineTextChild) {
|
||||||
// This container was doing double duty as a container for a single
|
// This container was doing double duty as a container for a single
|
||||||
// text child, back that out.
|
// text child, back that out.
|
||||||
this._containers.delete(container.singleTextChild);
|
this._containers.delete(container.inlineTextChild);
|
||||||
container.clearSingleTextChild();
|
container.clearInlineTextChild();
|
||||||
|
|
||||||
if (container.hasChildren && container.selected) {
|
if (container.hasChildren && container.selected) {
|
||||||
container.setExpanded(true);
|
container.setExpanded(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.node.singleTextChild) {
|
if (container.node.inlineTextChild) {
|
||||||
container.setExpanded(false);
|
container.setExpanded(false);
|
||||||
// this container will do double duty as the container for the single
|
// this container will do double duty as the container for the single
|
||||||
// text child.
|
// text child.
|
||||||
|
|
@ -1569,9 +1571,9 @@ MarkupView.prototype = {
|
||||||
container.children.removeChild(container.children.firstChild);
|
container.children.removeChild(container.children.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.setSingleTextChild(container.node.singleTextChild);
|
container.setInlineTextChild(container.node.inlineTextChild);
|
||||||
|
|
||||||
this._containers.set(container.node.singleTextChild, container);
|
this._containers.set(container.node.inlineTextChild, container);
|
||||||
container.childrenDirty = false;
|
container.childrenDirty = false;
|
||||||
return promise.resolve(container);
|
return promise.resolve(container);
|
||||||
}
|
}
|
||||||
|
|
@ -2008,7 +2010,7 @@ MarkupContainer.prototype = {
|
||||||
* True if the current node can be expanded.
|
* True if the current node can be expanded.
|
||||||
*/
|
*/
|
||||||
get canExpand() {
|
get canExpand() {
|
||||||
return this._hasChildren && !this.node.singleTextChild;
|
return this._hasChildren && !this.node.inlineTextChild;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -2717,13 +2719,13 @@ MarkupElementContainer.prototype = Heritage.extend(MarkupContainer.prototype, {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setSingleTextChild: function (singleTextChild) {
|
setInlineTextChild: function (inlineTextChild) {
|
||||||
this.singleTextChild = singleTextChild;
|
this.inlineTextChild = inlineTextChild;
|
||||||
this.editor.updateTextEditor();
|
this.editor.updateTextEditor();
|
||||||
},
|
},
|
||||||
|
|
||||||
clearSingleTextChild: function () {
|
clearInlineTextChild: function () {
|
||||||
this.singleTextChild = undefined;
|
this.inlineTextChild = undefined;
|
||||||
this.editor.updateTextEditor();
|
this.editor.updateTextEditor();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -2905,25 +2907,14 @@ TextEditor.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function () {
|
update: function () {
|
||||||
if (!this.selected || !this.node.incompleteValue) {
|
let longstr = null;
|
||||||
let text = this.node.shortValue;
|
this.node.getNodeValue().then(ret => {
|
||||||
if (this.node.incompleteValue) {
|
longstr = ret;
|
||||||
text += ELLIPSIS;
|
return longstr.string();
|
||||||
}
|
}).then(str => {
|
||||||
this.value.textContent = text;
|
longstr.release().then(null, console.error);
|
||||||
} else {
|
this.value.textContent = str;
|
||||||
let longstr = null;
|
}).then(null, console.error);
|
||||||
this.node.getNodeValue().then(ret => {
|
|
||||||
longstr = ret;
|
|
||||||
return longstr.string();
|
|
||||||
}).then(str => {
|
|
||||||
longstr.release().then(null, console.error);
|
|
||||||
if (this.selected) {
|
|
||||||
this.value.textContent = str;
|
|
||||||
this.markup.emit("text-expand");
|
|
||||||
}
|
|
||||||
}).then(null, console.error);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy: function () {},
|
destroy: function () {},
|
||||||
|
|
@ -3114,7 +3105,7 @@ ElementEditor.prototype = {
|
||||||
* Update the inline text editor in case of a single text child node.
|
* Update the inline text editor in case of a single text child node.
|
||||||
*/
|
*/
|
||||||
updateTextEditor: function () {
|
updateTextEditor: function () {
|
||||||
let node = this.node.singleTextChild;
|
let node = this.node.inlineTextChild;
|
||||||
|
|
||||||
if (this.textEditor && this.textEditor.node != node) {
|
if (this.textEditor && this.textEditor.node != node) {
|
||||||
this.elt.removeChild(this.textEditor.elt);
|
this.elt.removeChild(this.textEditor.elt);
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
|
||||||
[browser_markup_tag_edit_11.js]
|
[browser_markup_tag_edit_11.js]
|
||||||
[browser_markup_tag_edit_12.js]
|
[browser_markup_tag_edit_12.js]
|
||||||
[browser_markup_tag_edit_13-other.js]
|
[browser_markup_tag_edit_13-other.js]
|
||||||
|
[browser_markup_textcontent_display.js]
|
||||||
[browser_markup_textcontent_edit_01.js]
|
[browser_markup_textcontent_edit_01.js]
|
||||||
[browser_markup_textcontent_edit_02.js]
|
[browser_markup_textcontent_edit_02.js]
|
||||||
[browser_markup_toggle_01.js]
|
[browser_markup_toggle_01.js]
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ const TEST_DATA = [
|
||||||
},
|
},
|
||||||
check: function* (inspector) {
|
check: function* (inspector) {
|
||||||
let container = yield getContainerForSelector("#node1", inspector);
|
let container = yield getContainerForSelector("#node1", inspector);
|
||||||
ok(container.singleTextChild, "Has single text child.");
|
ok(container.inlineTextChild, "Has single text child.");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -99,9 +99,9 @@ const TEST_DATA = [
|
||||||
},
|
},
|
||||||
check: function* (inspector) {
|
check: function* (inspector) {
|
||||||
let container = yield getContainerForSelector("#node1", inspector);
|
let container = yield getContainerForSelector("#node1", inspector);
|
||||||
ok(container.singleTextChild, "Has single text child.");
|
ok(container.inlineTextChild, "Has single text child.");
|
||||||
ok(!container.canExpand, "Can't expand container with singleTextChild.");
|
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
|
||||||
ok(!container.singleTextChild.canExpand, "Can't expand singleTextChild.");
|
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
|
||||||
is(container.editor.elt.querySelector(".text").textContent.trim(),
|
is(container.editor.elt.querySelector(".text").textContent.trim(),
|
||||||
"newtext", "Single text child editor updated.");
|
"newtext", "Single text child editor updated.");
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ const TEST_DATA = [
|
||||||
},
|
},
|
||||||
check: function* (inspector) {
|
check: function* (inspector) {
|
||||||
let container = yield getContainerForSelector("#node1", inspector);
|
let container = yield getContainerForSelector("#node1", inspector);
|
||||||
ok(!container.singleTextChild, "Does not have single text child.");
|
ok(!container.inlineTextChild, "Does not have single text child.");
|
||||||
ok(container.canExpand, "Can expand container with child nodes.");
|
ok(container.canExpand, "Can expand container with child nodes.");
|
||||||
ok(container.editor.elt.querySelector(".text") == null,
|
ok(container.editor.elt.querySelector(".text") == null,
|
||||||
"Single text child editor removed.");
|
"Single text child editor removed.");
|
||||||
|
|
@ -130,9 +130,9 @@ const TEST_DATA = [
|
||||||
},
|
},
|
||||||
check: function* (inspector) {
|
check: function* (inspector) {
|
||||||
let container = yield getContainerForSelector("#node1", inspector);
|
let container = yield getContainerForSelector("#node1", inspector);
|
||||||
ok(container.singleTextChild, "Has single text child.");
|
ok(container.inlineTextChild, "Has single text child.");
|
||||||
ok(!container.canExpand, "Can't expand container with singleTextChild.");
|
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
|
||||||
ok(!container.singleTextChild.canExpand, "Can't expand singleTextChild.");
|
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
|
||||||
ok(container.editor.elt.querySelector(".text").textContent.trim(),
|
ok(container.editor.elt.querySelector(".text").textContent.trim(),
|
||||||
"newtext", "Single text child editor updated.");
|
"newtext", "Single text child editor updated.");
|
||||||
},
|
},
|
||||||
|
|
@ -144,7 +144,7 @@ const TEST_DATA = [
|
||||||
},
|
},
|
||||||
check: function* (inspector) {
|
check: function* (inspector) {
|
||||||
let container = yield getContainerForSelector("#node1", inspector);
|
let container = yield getContainerForSelector("#node1", inspector);
|
||||||
ok(!container.singleTextChild, "Does not have single text child.");
|
ok(!container.inlineTextChild, "Does not have single text child.");
|
||||||
ok(!container.canExpand, "Can't expand empty container.");
|
ok(!container.canExpand, "Can't expand empty container.");
|
||||||
ok(container.editor.elt.querySelector(".text") == null,
|
ok(container.editor.elt.querySelector(".text") == null,
|
||||||
"Single text child editor removed.");
|
"Single text child editor removed.");
|
||||||
|
|
@ -157,9 +157,9 @@ const TEST_DATA = [
|
||||||
},
|
},
|
||||||
check: function* (inspector) {
|
check: function* (inspector) {
|
||||||
let container = yield getContainerForSelector("#node1", inspector);
|
let container = yield getContainerForSelector("#node1", inspector);
|
||||||
ok(container.singleTextChild, "Has single text child.");
|
ok(container.inlineTextChild, "Has single text child.");
|
||||||
ok(!container.canExpand, "Can't expand container with singleTextChild.");
|
ok(!container.canExpand, "Can't expand container with inlineTextChild.");
|
||||||
ok(!container.singleTextChild.canExpand, "Can't expand singleTextChild.");
|
ok(!container.inlineTextChild.canExpand, "Can't expand inlineTextChild.");
|
||||||
ok(container.editor.elt.querySelector(".text").textContent.trim(),
|
ok(container.editor.elt.querySelector(".text").textContent.trim(),
|
||||||
"newtext", "Single text child editor updated.");
|
"newtext", "Single text child editor updated.");
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Test the rendering of text nodes in the markup view.
|
||||||
|
|
||||||
|
const LONG_VALUE = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do " +
|
||||||
|
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.";
|
||||||
|
const SCHEMA = "data:text/html;charset=UTF-8,";
|
||||||
|
const TEST_URL = `${SCHEMA}<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="shorttext">Short text</div>
|
||||||
|
<div id="longtext">${LONG_VALUE}</div>
|
||||||
|
<div id="shortcomment"><!--Short comment--></div>
|
||||||
|
<div id="longcomment"><!--${LONG_VALUE}--></div>
|
||||||
|
<div id="shorttext-and-node">Short text<span>Other element</span></div>
|
||||||
|
<div id="longtext-and-node">${LONG_VALUE}<span>Other element</span></div>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
const TEST_DATA = [{
|
||||||
|
desc: "Test node containing a short text, short text nodes can be inlined.",
|
||||||
|
selector: "#shorttext",
|
||||||
|
inline: true,
|
||||||
|
value: "Short text",
|
||||||
|
}, {
|
||||||
|
desc: "Test node containing a long text, long text nodes are not inlined.",
|
||||||
|
selector: "#longtext",
|
||||||
|
inline: false,
|
||||||
|
value: LONG_VALUE,
|
||||||
|
}, {
|
||||||
|
desc: "Test node containing a short comment, comments are not inlined.",
|
||||||
|
selector: "#shortcomment",
|
||||||
|
inline: false,
|
||||||
|
value: "Short comment",
|
||||||
|
}, {
|
||||||
|
desc: "Test node containing a long comment, comments are not inlined.",
|
||||||
|
selector: "#longcomment",
|
||||||
|
inline: false,
|
||||||
|
value: LONG_VALUE,
|
||||||
|
}, {
|
||||||
|
desc: "Test node containing a short text and a span.",
|
||||||
|
selector: "#shorttext-and-node",
|
||||||
|
inline: false,
|
||||||
|
value: "Short text",
|
||||||
|
}, {
|
||||||
|
desc: "Test node containing a long text and a span.",
|
||||||
|
selector: "#longtext-and-node",
|
||||||
|
inline: false,
|
||||||
|
value: LONG_VALUE,
|
||||||
|
}, ];
|
||||||
|
|
||||||
|
add_task(function* () {
|
||||||
|
let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
|
||||||
|
|
||||||
|
for (let data of TEST_DATA) {
|
||||||
|
yield checkNode(inspector, testActor, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function* checkNode(inspector, testActor, {desc, selector, inline, value}) {
|
||||||
|
info(desc);
|
||||||
|
|
||||||
|
let container = yield getContainerForSelector(selector, inspector);
|
||||||
|
let nodeValue = yield getFirstChildNodeValue(selector, testActor);
|
||||||
|
is(nodeValue, value, "The test node's text content is correct");
|
||||||
|
|
||||||
|
is(!!container.inlineTextChild, inline, "Container inlineTextChild is as expected");
|
||||||
|
is(!container.canExpand, inline, "Container canExpand property is as expected");
|
||||||
|
|
||||||
|
let textContainer;
|
||||||
|
if (inline) {
|
||||||
|
textContainer = container.elt.querySelector("pre");
|
||||||
|
ok(!!textContainer, "Text container is already rendered for inline text elements");
|
||||||
|
} else {
|
||||||
|
textContainer = container.elt.querySelector("pre");
|
||||||
|
ok(!textContainer, "Text container is not rendered for collapsed text nodes");
|
||||||
|
yield inspector.markup.expandNode(container.node);
|
||||||
|
yield waitForMultipleChildrenUpdates(inspector);
|
||||||
|
|
||||||
|
textContainer = container.elt.querySelector("pre");
|
||||||
|
ok(!!textContainer, "Text container is rendered after expanding the container");
|
||||||
|
}
|
||||||
|
|
||||||
|
is(textContainer.textContent, value, "The complete text node is rendered.");
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
// Test editing a node's text content
|
// Test editing a node's text content
|
||||||
|
|
||||||
const TEST_URL = URL_ROOT + "doc_markup_edit.html";
|
const TEST_URL = URL_ROOT + "doc_markup_edit.html";
|
||||||
|
const {DEFAULT_VALUE_SUMMARY_LENGTH} = require("devtools/server/actors/inspector");
|
||||||
|
|
||||||
add_task(function* () {
|
add_task(function* () {
|
||||||
let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
|
let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
|
||||||
|
|
@ -26,55 +27,38 @@ add_task(function* () {
|
||||||
newValue: "LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. " +
|
newValue: "LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. " +
|
||||||
"DONEC POSUERE PLACERAT MAGNA ET IMPERDIET.",
|
"DONEC POSUERE PLACERAT MAGNA ET IMPERDIET.",
|
||||||
oldValue: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
|
oldValue: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
|
||||||
"Donec posuere placerat magna et imperdiet.",
|
"Donec posuere placerat magna et imperdiet."
|
||||||
shortValue: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
yield editContainer(inspector, testActor, {
|
yield editContainer(inspector, testActor, {
|
||||||
selector: "#node17",
|
selector: "#node17",
|
||||||
newValue: "New value",
|
newValue: "New value",
|
||||||
oldValue: "LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. " +
|
oldValue: "LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. " +
|
||||||
|
"DONEC POSUERE PLACERAT MAGNA ET IMPERDIET."
|
||||||
|
});
|
||||||
|
|
||||||
|
yield editContainer(inspector, testActor, {
|
||||||
|
selector: "#node17",
|
||||||
|
newValue: "LOREM IPSUM DOLOR SIT AMET, CONSECTETUR ADIPISCING ELIT. " +
|
||||||
"DONEC POSUERE PLACERAT MAGNA ET IMPERDIET.",
|
"DONEC POSUERE PLACERAT MAGNA ET IMPERDIET.",
|
||||||
shortValue: true
|
oldValue: "New value"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function* getNodeValue(selector, testActor) {
|
|
||||||
let nodeValue = yield testActor.eval(`
|
|
||||||
content.document.querySelector("${selector}").firstChild.nodeValue;
|
|
||||||
`);
|
|
||||||
return nodeValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function* editContainer(inspector, testActor,
|
function* editContainer(inspector, testActor,
|
||||||
{selector, newValue, oldValue, shortValue}) {
|
{selector, newValue, oldValue}) {
|
||||||
let nodeValue = yield getNodeValue(selector, testActor);
|
let nodeValue = yield getFirstChildNodeValue(selector, testActor);
|
||||||
is(nodeValue, oldValue, "The test node's text content is correct");
|
is(nodeValue, oldValue, "The test node's text content is correct");
|
||||||
|
|
||||||
info("Changing the text content");
|
info("Changing the text content");
|
||||||
let onMutated = inspector.once("markupmutation");
|
let onMutated = inspector.once("markupmutation");
|
||||||
let container = yield focusNode(selector, inspector);
|
let container = yield focusNode(selector, inspector);
|
||||||
|
|
||||||
|
let isOldValueInline = oldValue.length <= DEFAULT_VALUE_SUMMARY_LENGTH;
|
||||||
|
is(!!container.inlineTextChild, isOldValueInline, "inlineTextChild is as expected");
|
||||||
|
is(!container.canExpand, isOldValueInline, "canExpand property is as expected");
|
||||||
|
|
||||||
let field = container.elt.querySelector("pre");
|
let field = container.elt.querySelector("pre");
|
||||||
|
|
||||||
if (shortValue) {
|
|
||||||
is(oldValue.indexOf(
|
|
||||||
field.textContent.substring(0, field.textContent.length - 1)),
|
|
||||||
0,
|
|
||||||
"The shortened value starts with the full value " + field.textContent);
|
|
||||||
ok(oldValue.length > field.textContent.length,
|
|
||||||
"The shortened value is short");
|
|
||||||
} else {
|
|
||||||
is(field.textContent, oldValue,
|
|
||||||
"The text node has the correct original value");
|
|
||||||
}
|
|
||||||
|
|
||||||
inspector.markup.markNodeAsSelected(container.node);
|
|
||||||
|
|
||||||
if (shortValue) {
|
|
||||||
info("Waiting for the text to be updated");
|
|
||||||
yield inspector.markup.once("text-expand");
|
|
||||||
}
|
|
||||||
|
|
||||||
is(field.textContent, oldValue,
|
is(field.textContent, oldValue,
|
||||||
"The text node has the correct original value after selecting");
|
"The text node has the correct original value after selecting");
|
||||||
setEditableFieldValue(field, newValue, inspector);
|
setEditableFieldValue(field, newValue, inspector);
|
||||||
|
|
@ -82,9 +66,18 @@ function* editContainer(inspector, testActor,
|
||||||
info("Listening to the markupmutation event");
|
info("Listening to the markupmutation event");
|
||||||
yield onMutated;
|
yield onMutated;
|
||||||
|
|
||||||
nodeValue = yield getNodeValue(selector, testActor);
|
nodeValue = yield getFirstChildNodeValue(selector, testActor);
|
||||||
is(nodeValue, newValue, "The test node's text content has changed");
|
is(nodeValue, newValue, "The test node's text content has changed");
|
||||||
|
|
||||||
|
let isNewValueInline = newValue.length <= DEFAULT_VALUE_SUMMARY_LENGTH;
|
||||||
|
is(!!container.inlineTextChild, isNewValueInline, "inlineTextChild is as expected");
|
||||||
|
is(!container.canExpand, isNewValueInline, "canExpand property is as expected");
|
||||||
|
|
||||||
|
if (isOldValueInline != isNewValueInline) {
|
||||||
|
is(container.expanded, !isNewValueInline,
|
||||||
|
"Container was automatically expanded/collapsed");
|
||||||
|
}
|
||||||
|
|
||||||
info("Selecting the <body> to reset the selection");
|
info("Selecting the <body> to reset the selection");
|
||||||
let bodyContainer = yield getContainerForSelector("body", inspector);
|
let bodyContainer = yield getContainerForSelector("body", inspector);
|
||||||
inspector.markup.markNodeAsSelected(bodyContainer.node);
|
inspector.markup.markNodeAsSelected(bodyContainer.node);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ add_task(function* () {
|
||||||
yield inspector.markup.expandAll();
|
yield inspector.markup.expandAll();
|
||||||
yield waitForMultipleChildrenUpdates(inspector);
|
yield waitForMultipleChildrenUpdates(inspector);
|
||||||
|
|
||||||
let nodeValue = yield getNodeValue(SELECTOR, testActor);
|
let nodeValue = yield getFirstChildNodeValue(SELECTOR, testActor);
|
||||||
let expectedValue = "line6";
|
let expectedValue = "line6";
|
||||||
is(nodeValue, expectedValue, "The test node's text content is correct");
|
is(nodeValue, expectedValue, "The test node's text content is correct");
|
||||||
|
|
||||||
|
|
@ -84,17 +84,10 @@ add_task(function* () {
|
||||||
yield sendKey("VK_RETURN", {}, editor, inspector.panelWin);
|
yield sendKey("VK_RETURN", {}, editor, inspector.panelWin);
|
||||||
yield onMutated;
|
yield onMutated;
|
||||||
|
|
||||||
nodeValue = yield getNodeValue(SELECTOR, testActor);
|
nodeValue = yield getFirstChildNodeValue(SELECTOR, testActor);
|
||||||
is(nodeValue, expectedValue, "The test node's text content is correct");
|
is(nodeValue, expectedValue, "The test node's text content is correct");
|
||||||
});
|
});
|
||||||
|
|
||||||
function* getNodeValue(selector, testActor) {
|
|
||||||
let nodeValue = yield testActor.eval(`
|
|
||||||
content.document.querySelector("${selector}").firstChild.nodeValue;
|
|
||||||
`);
|
|
||||||
return nodeValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that the editor selection is at the expected positions.
|
* Check that the editor selection is at the expected positions.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,20 @@ var getContainerForSelector = Task.async(function* (selector, inspector) {
|
||||||
return container;
|
return container;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the nodeValue for the firstChild of a provided selector on the content page.
|
||||||
|
*
|
||||||
|
* @param {String} selector
|
||||||
|
* @param {TestActorFront} testActor The current TestActorFront instance.
|
||||||
|
* @return {String} the nodeValue of the first
|
||||||
|
*/
|
||||||
|
function* getFirstChildNodeValue(selector, testActor) {
|
||||||
|
let nodeValue = yield testActor.eval(`
|
||||||
|
content.document.querySelector("${selector}").firstChild.nodeValue;
|
||||||
|
`);
|
||||||
|
return nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using the markupview's _waitForChildren function, wait for all queued
|
* Using the markupview's _waitForChildren function, wait for all queued
|
||||||
* children updates to be handled.
|
* children updates to be handled.
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ var NodeActor = exports.NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
||||||
}
|
}
|
||||||
|
|
||||||
let parentNode = this.walker.parentNode(this);
|
let parentNode = this.walker.parentNode(this);
|
||||||
let singleTextChild = this.walker.singleTextChild(this);
|
let inlineTextChild = this.walker.inlineTextChild(this);
|
||||||
|
|
||||||
let form = {
|
let form = {
|
||||||
actor: this.actorID,
|
actor: this.actorID,
|
||||||
|
|
@ -257,9 +257,10 @@ var NodeActor = exports.NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
||||||
nodeType: this.rawNode.nodeType,
|
nodeType: this.rawNode.nodeType,
|
||||||
namespaceURI: this.rawNode.namespaceURI,
|
namespaceURI: this.rawNode.namespaceURI,
|
||||||
nodeName: this.rawNode.nodeName,
|
nodeName: this.rawNode.nodeName,
|
||||||
|
nodeValue: this.rawNode.nodeValue,
|
||||||
displayName: getNodeDisplayName(this.rawNode),
|
displayName: getNodeDisplayName(this.rawNode),
|
||||||
numChildren: this.numChildren,
|
numChildren: this.numChildren,
|
||||||
singleTextChild: singleTextChild ? singleTextChild.form() : undefined,
|
inlineTextChild: inlineTextChild ? inlineTextChild.form() : undefined,
|
||||||
|
|
||||||
// doctype attributes
|
// doctype attributes
|
||||||
name: this.rawNode.name,
|
name: this.rawNode.name,
|
||||||
|
|
@ -285,18 +286,6 @@ var NodeActor = exports.NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
||||||
form.isDocumentElement = true;
|
form.isDocumentElement = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.rawNode.nodeValue) {
|
|
||||||
// We only include a short version of the value if it's longer than
|
|
||||||
// gValueSummaryLength
|
|
||||||
if (this.rawNode.nodeValue.length > gValueSummaryLength) {
|
|
||||||
form.shortValue = this.rawNode.nodeValue
|
|
||||||
.substring(0, gValueSummaryLength);
|
|
||||||
form.incompleteValue = true;
|
|
||||||
} else {
|
|
||||||
form.shortValue = this.rawNode.nodeValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an extra API for custom properties added by other
|
// Add an extra API for custom properties added by other
|
||||||
// modules/extensions.
|
// modules/extensions.
|
||||||
form.setFormProperty = (name, value) => {
|
form.setFormProperty = (name, value) => {
|
||||||
|
|
@ -330,6 +319,7 @@ var NodeActor = exports.NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
|
||||||
nativeAnonymousChildList: true,
|
nativeAnonymousChildList: true,
|
||||||
attributes: true,
|
attributes: true,
|
||||||
characterData: true,
|
characterData: true,
|
||||||
|
characterDataOldValue: true,
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true
|
subtree: true
|
||||||
});
|
});
|
||||||
|
|
@ -1147,12 +1137,12 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the given NodeActor only has a single text node as a child,
|
* If the given NodeActor only has a single text node as a child with a text
|
||||||
* return that child's NodeActor.
|
* content small enough to be inlined, return that child's NodeActor.
|
||||||
*
|
*
|
||||||
* @param NodeActor node
|
* @param NodeActor node
|
||||||
*/
|
*/
|
||||||
singleTextChild: function (node) {
|
inlineTextChild: function (node) {
|
||||||
// Quick checks to prevent creating a new walker if possible.
|
// Quick checks to prevent creating a new walker if possible.
|
||||||
if (node.isBeforePseudoElement ||
|
if (node.isBeforePseudoElement ||
|
||||||
node.isAfterPseudoElement ||
|
node.isAfterPseudoElement ||
|
||||||
|
|
@ -1164,11 +1154,15 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
||||||
let docWalker = this.getDocumentWalker(node.rawNode);
|
let docWalker = this.getDocumentWalker(node.rawNode);
|
||||||
let firstChild = docWalker.firstChild();
|
let firstChild = docWalker.firstChild();
|
||||||
|
|
||||||
// If the first child isn't a text node, or there are multiple children
|
// Bail out if:
|
||||||
// then bail out
|
// - more than one child
|
||||||
|
// - unique child is not a text node
|
||||||
|
// - unique child is a text node, but is too long to be inlined
|
||||||
if (!firstChild ||
|
if (!firstChild ||
|
||||||
|
docWalker.nextSibling() ||
|
||||||
firstChild.nodeType !== Ci.nsIDOMNode.TEXT_NODE ||
|
firstChild.nodeType !== Ci.nsIDOMNode.TEXT_NODE ||
|
||||||
docWalker.nextSibling()) {
|
firstChild.nodeValue.length > gValueSummaryLength
|
||||||
|
) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2200,8 +2194,7 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
||||||
* newValue: <string> - The new value of the attribute, if any.
|
* newValue: <string> - The new value of the attribute, if any.
|
||||||
*
|
*
|
||||||
* `characterData` type:
|
* `characterData` type:
|
||||||
* newValue: <string> - the new shortValue for the node
|
* newValue: <string> - the new nodeValue for the node
|
||||||
* [incompleteValue: true] - True if the shortValue was truncated.
|
|
||||||
*
|
*
|
||||||
* `childList` type is returned when the set of children for a node
|
* `childList` type is returned when the set of children for a node
|
||||||
* has changed. Includes extra data, which can be used by the client to
|
* has changed. Includes extra data, which can be used by the client to
|
||||||
|
|
@ -2211,7 +2204,7 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
||||||
* seen by the client* that were added to the target node.
|
* seen by the client* that were added to the target node.
|
||||||
* removed: array of <domnode actor ID> The list of actors *previously
|
* removed: array of <domnode actor ID> The list of actors *previously
|
||||||
* seen by the client* that were removed from the target node.
|
* seen by the client* that were removed from the target node.
|
||||||
* singleTextChild: If the node now has a single text child, it will
|
* inlineTextChild: If the node now has a single text child, it will
|
||||||
* be sent here.
|
* be sent here.
|
||||||
*
|
*
|
||||||
* Actors that are included in a MutationRecord's `removed` but
|
* Actors that are included in a MutationRecord's `removed` but
|
||||||
|
|
@ -2288,13 +2281,8 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
||||||
targetNode.getAttribute(mutation.attributeName)
|
targetNode.getAttribute(mutation.attributeName)
|
||||||
: null;
|
: null;
|
||||||
} else if (type === "characterData") {
|
} else if (type === "characterData") {
|
||||||
if (targetNode.nodeValue.length > gValueSummaryLength) {
|
mutation.newValue = targetNode.nodeValue;
|
||||||
mutation.newValue = targetNode.nodeValue
|
this._maybeQueueInlineTextChildMutation(change, targetNode);
|
||||||
.substring(0, gValueSummaryLength);
|
|
||||||
mutation.incompleteValue = true;
|
|
||||||
} else {
|
|
||||||
mutation.newValue = targetNode.nodeValue;
|
|
||||||
}
|
|
||||||
} else if (type === "childList" || type === "nativeAnonymousChildList") {
|
} else if (type === "childList" || type === "nativeAnonymousChildList") {
|
||||||
// Get the list of removed and added actors that the client has seen
|
// Get the list of removed and added actors that the client has seen
|
||||||
// so that it can keep its ownership tree up to date.
|
// so that it can keep its ownership tree up to date.
|
||||||
|
|
@ -2330,15 +2318,50 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, {
|
||||||
mutation.removed = removedActors;
|
mutation.removed = removedActors;
|
||||||
mutation.added = addedActors;
|
mutation.added = addedActors;
|
||||||
|
|
||||||
let singleTextChild = this.singleTextChild(targetActor);
|
let inlineTextChild = this.inlineTextChild(targetActor);
|
||||||
if (singleTextChild) {
|
if (inlineTextChild) {
|
||||||
mutation.singleTextChild = singleTextChild.form();
|
mutation.inlineTextChild = inlineTextChild.form();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.queueMutation(mutation);
|
this.queueMutation(mutation);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the provided mutation could change the way the target element is
|
||||||
|
* inlined with its parent node. If it might, a custom mutation of type
|
||||||
|
* "inlineTextChild" will be queued.
|
||||||
|
*
|
||||||
|
* @param {MutationRecord} mutation
|
||||||
|
* A characterData type mutation
|
||||||
|
*/
|
||||||
|
_maybeQueueInlineTextChildMutation: function (mutation) {
|
||||||
|
let {oldValue, target} = mutation;
|
||||||
|
let newValue = target.nodeValue;
|
||||||
|
let limit = gValueSummaryLength;
|
||||||
|
|
||||||
|
if ((oldValue.length <= limit && newValue.length <= limit) ||
|
||||||
|
(oldValue.length > limit && newValue.length > limit)) {
|
||||||
|
// Bail out if the new & old values are both below/above the size limit.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parentActor = this.getNode(target.parentNode);
|
||||||
|
if (!parentActor || parentActor.rawNode.children.length > 0) {
|
||||||
|
// If the parent node has other children, a character data mutation will
|
||||||
|
// not change anything regarding inlining text nodes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inlineTextChild = this.inlineTextChild(parentActor);
|
||||||
|
this.queueMutation({
|
||||||
|
type: "inlineTextChild",
|
||||||
|
target: parentActor.actorID,
|
||||||
|
inlineTextChild:
|
||||||
|
inlineTextChild ? inlineTextChild.form() : undefined
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onFrameLoad: function ({ window, isTopLevel }) {
|
onFrameLoad: function ({ window, isTopLevel }) {
|
||||||
if (!this.rootDoc && isTopLevel) {
|
if (!this.rootDoc && isTopLevel) {
|
||||||
this.rootDoc = window.document;
|
this.rootDoc = window.document;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Services = require("Services");
|
|
||||||
const { Ci } = require("chrome");
|
const { Ci } = require("chrome");
|
||||||
require("devtools/shared/fronts/styles");
|
require("devtools/shared/fronts/styles");
|
||||||
require("devtools/shared/fronts/highlighters");
|
require("devtools/shared/fronts/highlighters");
|
||||||
|
|
@ -15,7 +14,6 @@ const {
|
||||||
preEvent,
|
preEvent,
|
||||||
types
|
types
|
||||||
} = require("devtools/shared/protocol.js");
|
} = require("devtools/shared/protocol.js");
|
||||||
const { makeInfallible } = require("devtools/shared/DevToolsUtils");
|
|
||||||
const {
|
const {
|
||||||
inspectorSpec,
|
inspectorSpec,
|
||||||
nodeSpec,
|
nodeSpec,
|
||||||
|
|
@ -71,15 +69,6 @@ const AttributeModificationList = Class({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// A resolve that hits the main loop first.
|
|
||||||
function delayedResolve(value) {
|
|
||||||
let deferred = promise.defer();
|
|
||||||
Services.tm.mainThread.dispatch(makeInfallible(() => {
|
|
||||||
deferred.resolve(value);
|
|
||||||
}), 0);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client side of the node actor.
|
* Client side of the node actor.
|
||||||
*
|
*
|
||||||
|
|
@ -122,6 +111,14 @@ const NodeFront = FrontClassWithSpec(nodeSpec, {
|
||||||
this.actorID = form;
|
this.actorID = form;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// backward-compatibility: shortValue indicates we are connected to old server
|
||||||
|
if (form.shortValue) {
|
||||||
|
// If the value is not complete, set nodeValue to null, it will be fetched
|
||||||
|
// when calling getNodeValue()
|
||||||
|
form.nodeValue = form.incompleteValue ? null : form.shortValue;
|
||||||
|
}
|
||||||
|
|
||||||
// Shallow copy of the form. We could just store a reference, but
|
// Shallow copy of the form. We could just store a reference, but
|
||||||
// eventually we'll want to update some of the data.
|
// eventually we'll want to update some of the data.
|
||||||
this._form = object.merge(form);
|
this._form = object.merge(form);
|
||||||
|
|
@ -135,11 +132,11 @@ const NodeFront = FrontClassWithSpec(nodeSpec, {
|
||||||
this.reparent(parentNodeFront);
|
this.reparent(parentNodeFront);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (form.singleTextChild) {
|
if (form.inlineTextChild) {
|
||||||
this.singleTextChild =
|
this.inlineTextChild =
|
||||||
types.getType("domnode").read(form.singleTextChild, ctx);
|
types.getType("domnode").read(form.inlineTextChild, ctx);
|
||||||
} else {
|
} else {
|
||||||
this.singleTextChild = undefined;
|
this.inlineTextChild = undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -186,8 +183,7 @@ const NodeFront = FrontClassWithSpec(nodeSpec, {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (change.type === "characterData") {
|
} else if (change.type === "characterData") {
|
||||||
this._form.shortValue = change.newValue;
|
this._form.nodeValue = change.newValue;
|
||||||
this._form.incompleteValue = change.incompleteValue;
|
|
||||||
} else if (change.type === "pseudoClassLock") {
|
} else if (change.type === "pseudoClassLock") {
|
||||||
this._form.pseudoClassLocks = change.pseudoClassLocks;
|
this._form.pseudoClassLocks = change.pseudoClassLocks;
|
||||||
} else if (change.type === "events") {
|
} else if (change.type === "events") {
|
||||||
|
|
@ -259,12 +255,6 @@ const NodeFront = FrontClassWithSpec(nodeSpec, {
|
||||||
get tagName() {
|
get tagName() {
|
||||||
return this.nodeType === Ci.nsIDOMNode.ELEMENT_NODE ? this.nodeName : null;
|
return this.nodeType === Ci.nsIDOMNode.ELEMENT_NODE ? this.nodeName : null;
|
||||||
},
|
},
|
||||||
get shortValue() {
|
|
||||||
return this._form.shortValue;
|
|
||||||
},
|
|
||||||
get incompleteValue() {
|
|
||||||
return !!this._form.incompleteValue;
|
|
||||||
},
|
|
||||||
|
|
||||||
get isDocumentElement() {
|
get isDocumentElement() {
|
||||||
return !!this._form.isDocumentElement;
|
return !!this._form.isDocumentElement;
|
||||||
|
|
@ -324,11 +314,14 @@ const NodeFront = FrontClassWithSpec(nodeSpec, {
|
||||||
},
|
},
|
||||||
|
|
||||||
getNodeValue: custom(function () {
|
getNodeValue: custom(function () {
|
||||||
if (!this.incompleteValue) {
|
// backward-compatibility: if nodevalue is null and shortValue is defined, the actual
|
||||||
return delayedResolve(new ShortLongString(this.shortValue));
|
// value of the node needs to be fetched on the server.
|
||||||
|
if (this._form.nodeValue === null && this._form.shortValue) {
|
||||||
|
return this._getNodeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._getNodeValue();
|
let str = this._form.nodeValue || "";
|
||||||
|
return promise.resolve(new ShortLongString(str));
|
||||||
}, {
|
}, {
|
||||||
impl: "_getNodeValue"
|
impl: "_getNodeValue"
|
||||||
}),
|
}),
|
||||||
|
|
@ -806,13 +799,6 @@ const WalkerFront = FrontClassWithSpec(walkerSpec, {
|
||||||
addedFronts.push(addedFront);
|
addedFronts.push(addedFront);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change.singleTextChild) {
|
|
||||||
targetFront.singleTextChild =
|
|
||||||
types.getType("domnode").read(change.singleTextChild, this);
|
|
||||||
} else {
|
|
||||||
targetFront.singleTextChild = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before passing to users, replace the added and removed actor
|
// Before passing to users, replace the added and removed actor
|
||||||
// ids with front in the mutation record.
|
// ids with front in the mutation record.
|
||||||
emittedMutation.added = addedFronts;
|
emittedMutation.added = addedFronts;
|
||||||
|
|
@ -858,6 +844,19 @@ const WalkerFront = FrontClassWithSpec(walkerSpec, {
|
||||||
targetFront.updateMutation(change);
|
targetFront.updateMutation(change);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the inlineTextChild property of the target for a selected list of
|
||||||
|
// mutation types.
|
||||||
|
if (change.type === "inlineTextChild" ||
|
||||||
|
change.type === "childList" ||
|
||||||
|
change.type === "nativeAnonymousChildList") {
|
||||||
|
if (change.inlineTextChild) {
|
||||||
|
targetFront.inlineTextChild =
|
||||||
|
types.getType("domnode").read(change.inlineTextChild, this);
|
||||||
|
} else {
|
||||||
|
targetFront.inlineTextChild = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emitMutations.push(emittedMutation);
|
emitMutations.push(emittedMutation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue