Bug 968241 - Copy outerHTML using keyboard shortcut. r=pbrosset
Copy outerHTML of the currently selected node of the inspector. Works for ELEMENT, DOCUMENT_TYPE and COMMENT node types. - bound "copy" event in markup-view to copy outerHTML - added doctypeString property to NodeFront in actors/inspector.js - markup-view.js is also using this property now - added mochitest with dedicated html
This commit is contained in:
@@ -1047,7 +1047,7 @@ InspectorPanel.prototype = {
|
||||
if (!this.selection.isNode()) {
|
||||
return;
|
||||
}
|
||||
this._copyLongStr(this.walker.innerHTML(this.selection.nodeFront));
|
||||
this._copyLongString(this.walker.innerHTML(this.selection.nodeFront));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1057,8 +1057,21 @@ InspectorPanel.prototype = {
|
||||
if (!this.selection.isNode()) {
|
||||
return;
|
||||
}
|
||||
let node = this.selection.nodeFront;
|
||||
|
||||
this._copyLongStr(this.walker.outerHTML(this.selection.nodeFront));
|
||||
switch (node.nodeType) {
|
||||
case Ci.nsIDOMNode.ELEMENT_NODE :
|
||||
this._copyLongString(this.walker.outerHTML(node));
|
||||
break;
|
||||
case Ci.nsIDOMNode.COMMENT_NODE :
|
||||
this._getLongString(node.getNodeValue()).then(comment => {
|
||||
clipboardHelper.copyString("<!--" + comment + "-->");
|
||||
});
|
||||
break;
|
||||
case Ci.nsIDOMNode.DOCUMENT_TYPE_NODE :
|
||||
clipboardHelper.copyString(node.doctypeString);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1071,13 +1084,29 @@ InspectorPanel.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_copyLongStr: function(promise) {
|
||||
return promise.then(longstr => {
|
||||
return longstr.string().then(toCopy => {
|
||||
longstr.release().then(null, console.error);
|
||||
clipboardHelper.copyString(toCopy);
|
||||
/**
|
||||
* Copy the content of a longString (via a promise resolving a LongStringActor) to the clipboard
|
||||
* @param {Promise} longStringActorPromise promise expected to resolve a LongStringActor instance
|
||||
* @return {Promise} promise resolving (with no argument) when the string is sent to the clipboard
|
||||
*/
|
||||
_copyLongString: function(longStringActorPromise) {
|
||||
return this._getLongString(longStringActorPromise).then(string => {
|
||||
clipboardHelper.copyString(string);
|
||||
}).catch(Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the content of a longString (via a promise resolving a LongStringActor)
|
||||
* @param {Promise} longStringActorPromise promise expected to resolve a LongStringActor instance
|
||||
* @return {Promise} promise resolving with the retrieved string as argument
|
||||
*/
|
||||
_getLongString: function(longStringActorPromise) {
|
||||
return longStringActorPromise.then(longStringActor => {
|
||||
return longStringActor.string().then(string => {
|
||||
longStringActor.release().catch(Cu.reportError);
|
||||
return string;
|
||||
});
|
||||
}).then(null, console.error);
|
||||
}).catch(Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ support-files =
|
||||
doc_inspector_infobar_01.html
|
||||
doc_inspector_infobar_02.html
|
||||
doc_inspector_menu.html
|
||||
doc_inspector_outerhtml.html
|
||||
doc_inspector_remove-iframe-during-load.html
|
||||
doc_inspector_search.html
|
||||
doc_inspector_search-reserved.html
|
||||
@@ -76,6 +77,7 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
|
||||
[browser_inspector_initialization.js]
|
||||
[browser_inspector_inspect-object-element.js]
|
||||
[browser_inspector_invalidate.js]
|
||||
[browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
|
||||
[browser_inspector_keyboard-shortcuts.js]
|
||||
[browser_inspector_menu-01-sensitivity.js]
|
||||
[browser_inspector_menu-02-copy-items.js]
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/* 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 copy outer HTML from the keyboard/copy event
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_inspector_outerhtml.html";
|
||||
|
||||
add_task(function *() {
|
||||
let { inspector } = yield openInspectorForURL(TEST_URL);
|
||||
let root = inspector.markup._elt;
|
||||
|
||||
info("Test copy outerHTML for COMMENT node");
|
||||
let comment = getElementByType(inspector, Ci.nsIDOMNode.COMMENT_NODE);
|
||||
yield setSelectionNodeFront(comment, inspector);
|
||||
yield checkClipboard("<!-- Comment -->", root);
|
||||
|
||||
info("Test copy outerHTML for DOCTYPE node");
|
||||
let doctype = getElementByType(inspector, Ci.nsIDOMNode.DOCUMENT_TYPE_NODE);
|
||||
yield setSelectionNodeFront(doctype, inspector);
|
||||
yield checkClipboard("<!DOCTYPE html>", root);
|
||||
|
||||
info("Test copy outerHTML for ELEMENT node");
|
||||
yield selectAndHighlightNode("div", inspector);
|
||||
yield checkClipboard("<div><p>Test copy OuterHTML</p></div>", root);
|
||||
});
|
||||
|
||||
function* setSelectionNodeFront(node, inspector) {
|
||||
let updated = inspector.once("inspector-updated");
|
||||
inspector.selection.setNodeFront(node);
|
||||
yield updated;
|
||||
}
|
||||
|
||||
function* checkClipboard(expectedText, node) {
|
||||
let deferred = promise.defer();
|
||||
waitForClipboard(
|
||||
expectedText,
|
||||
() => fireCopyEvent(node),
|
||||
deferred.resolve,
|
||||
deferred.reject
|
||||
);
|
||||
|
||||
try {
|
||||
yield deferred.promise;
|
||||
ok(true, "Clipboard successfully filled with : " + expectedText);
|
||||
} catch (e) {
|
||||
ok(false, "Clipboard could not be filled with the expected text : " + expectedText);
|
||||
}
|
||||
}
|
||||
|
||||
function getElementByType(inspector, type) {
|
||||
for (let [node] of inspector.markup._containers) {
|
||||
if (node.nodeType === type) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
browser/devtools/inspector/test/doc_inspector_outerhtml.html
Normal file
11
browser/devtools/inspector/test/doc_inspector_outerhtml.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Inspector Copy OuterHTML Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Comment -->
|
||||
<div><p>Test copy OuterHTML</p></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -723,6 +723,15 @@ function wait(ms) {
|
||||
return def.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the copy event on the given element
|
||||
*/
|
||||
function fireCopyEvent(element) {
|
||||
let evt = element.ownerDocument.createEvent("Event");
|
||||
evt.initEvent("copy", true, true);
|
||||
element.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an async message to the frame script (chrome -> content) and wait for a
|
||||
* response message with the same name (content -> chrome).
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
-moz-control-character-visibility: visible;
|
||||
}
|
||||
|
||||
body {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
/* Force height and width (possibly overflowing) from inline elements.
|
||||
* This allows long overflows of text or input fields to still be styled with
|
||||
* the container, rather than the background disappearing when scrolling */
|
||||
@@ -16,7 +20,6 @@
|
||||
|
||||
body.dragging .tag-line {
|
||||
cursor: grabbing;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
#root-wrapper:after {
|
||||
|
||||
@@ -111,6 +111,9 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
|
||||
this._boundKeyDown = this._onKeyDown.bind(this);
|
||||
this._frame.contentWindow.addEventListener("keydown", this._boundKeyDown, false);
|
||||
|
||||
this._onCopy = this._onCopy.bind(this);
|
||||
this._frame.contentWindow.addEventListener("copy", this._onCopy);
|
||||
|
||||
this._boundFocus = this._onFocus.bind(this);
|
||||
this._frame.addEventListener("focus", this._boundFocus, false);
|
||||
|
||||
@@ -507,6 +510,20 @@ MarkupView.prototype = {
|
||||
return walker;
|
||||
},
|
||||
|
||||
_onCopy: function (evt) {
|
||||
// Ignore copy events from editors
|
||||
if (this._isInputOrTextarea(evt.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let selection = this._inspector.selection;
|
||||
if (selection.isNode()) {
|
||||
this._inspector.copyOuterHTML();
|
||||
}
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
},
|
||||
|
||||
/**
|
||||
* Key handling.
|
||||
*/
|
||||
@@ -514,8 +531,7 @@ MarkupView.prototype = {
|
||||
let handled = true;
|
||||
|
||||
// Ignore keystrokes that originated in editors.
|
||||
if (aEvent.target.tagName.toLowerCase() === "input" ||
|
||||
aEvent.target.tagName.toLowerCase() === "textarea") {
|
||||
if (this._isInputOrTextarea(aEvent.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -614,6 +630,14 @@ MarkupView.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a node is an input or textarea
|
||||
*/
|
||||
_isInputOrTextarea : function (element) {
|
||||
let name = element.tagName.toLowerCase();
|
||||
return name === "input" || name === "textarea";
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a node from the DOM.
|
||||
* This is an undoable action.
|
||||
@@ -1485,6 +1509,9 @@ MarkupView.prototype = {
|
||||
this._boundKeyDown, false);
|
||||
this._boundKeyDown = null;
|
||||
|
||||
this._frame.contentWindow.removeEventListener("copy", this._onCopy);
|
||||
this._onCopy = null;
|
||||
|
||||
this._inspector.selection.off("new-node-front", this._boundOnNewSelection);
|
||||
this._boundOnNewSelection = null;
|
||||
|
||||
@@ -2314,10 +2341,7 @@ function GenericEditor(aContainer, aNode) {
|
||||
this.tag.textContent = aNode.isBeforePseudoElement ? "::before" : "::after";
|
||||
} else if (aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
|
||||
this.elt.classList.add("comment");
|
||||
this.tag.textContent = '<!DOCTYPE ' + aNode.name +
|
||||
(aNode.publicId ? ' PUBLIC "' + aNode.publicId + '"': '') +
|
||||
(aNode.systemId ? ' "' + aNode.systemId + '"' : '') +
|
||||
'>';
|
||||
this.tag.textContent = aNode.doctypeString;
|
||||
} else {
|
||||
this.tag.textContent = aNode.nodeName;
|
||||
}
|
||||
|
||||
@@ -849,6 +849,12 @@ let NodeFront = protocol.FrontClass(NodeActor, {
|
||||
get nodeName() {
|
||||
return this._form.nodeName;
|
||||
},
|
||||
get doctypeString() {
|
||||
return '<!DOCTYPE ' + this._form.name +
|
||||
(this._form.publicId ? ' PUBLIC "' + this._form.publicId + '"': '') +
|
||||
(this._form.systemId ? ' "' + this._form.systemId + '"' : '') +
|
||||
'>';
|
||||
},
|
||||
|
||||
get baseURI() {
|
||||
return this._form.baseURI;
|
||||
@@ -2399,11 +2405,11 @@ var WalkerActor = protocol.ActorClass({
|
||||
* @param {NodeActor} node The node.
|
||||
*/
|
||||
outerHTML: method(function(node) {
|
||||
let html = "";
|
||||
let outerHTML = "";
|
||||
if (!isNodeDead(node)) {
|
||||
html = node.rawNode.outerHTML;
|
||||
outerHTML = node.rawNode.outerHTML;
|
||||
}
|
||||
return LongStringActor(this.conn, html);
|
||||
return LongStringActor(this.conn, outerHTML);
|
||||
}, {
|
||||
request: {
|
||||
node: Arg(0, "domnode")
|
||||
|
||||
Reference in New Issue
Block a user