mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
Bug 703643 - Be able to copy from the rules view; r=prouget
This commit is contained in:
parent
30e2c80691
commit
db9875d847
@ -27,6 +27,7 @@
|
||||
* Paul Rouget <paul@mozilla.com>
|
||||
* Kyle Simpson <ksimpson@mozilla.com>
|
||||
* Johan Charlez <johan.charlez@gmail.com>
|
||||
* Mike Ratcliffe <mratcliffe@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
@ -854,9 +855,7 @@ InspectorUI.prototype = {
|
||||
*/
|
||||
copyInnerHTML: function IUI_copyInnerHTML()
|
||||
{
|
||||
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
|
||||
getService(Ci.nsIClipboardHelper);
|
||||
clipboard.copyString(this.selection.innerHTML);
|
||||
clipboardHelper.copyString(this.selection.innerHTML);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -865,9 +864,7 @@ InspectorUI.prototype = {
|
||||
*/
|
||||
copyOuterHTML: function IUI_copyOuterHTML()
|
||||
{
|
||||
let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
|
||||
getService(Ci.nsIClipboardHelper);
|
||||
clipboard.copyString(this.selection.outerHTML);
|
||||
clipboardHelper.copyString(this.selection.outerHTML);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -935,12 +932,34 @@ InspectorUI.prototype = {
|
||||
|
||||
this.ruleView = new CssRuleView(doc, ruleViewStore);
|
||||
|
||||
// Add event handlers bound to this.
|
||||
this.boundRuleViewChanged = this.ruleViewChanged.bind(this);
|
||||
this.ruleView.element.addEventListener("CssRuleViewChanged",
|
||||
this.boundRuleViewChanged);
|
||||
this.cssRuleViewBoundCSSLinkClicked = this.ruleViewCSSLinkClicked.bind(this);
|
||||
this.ruleView.element.addEventListener("CssRuleViewCSSLinkClicked",
|
||||
this.cssRuleViewBoundCSSLinkClicked);
|
||||
this.cssRuleViewBoundMouseDown = this.ruleViewMouseDown.bind(this);
|
||||
this.ruleView.element.addEventListener("mousedown",
|
||||
this.cssRuleViewBoundMouseDown);
|
||||
this.cssRuleViewBoundMouseUp = this.ruleViewMouseUp.bind(this);
|
||||
this.ruleView.element.addEventListener("mouseup",
|
||||
this.cssRuleViewBoundMouseUp);
|
||||
this.cssRuleViewBoundMouseMove = this.ruleViewMouseMove.bind(this);
|
||||
this.cssRuleViewBoundMenuUpdate = this.ruleViewMenuUpdate.bind(this);
|
||||
|
||||
this.cssRuleViewBoundCopy = this.ruleViewCopy.bind(this);
|
||||
iframe.addEventListener("copy", this.cssRuleViewBoundCopy);
|
||||
|
||||
this.cssRuleViewBoundCopyRule = this.ruleViewCopyRule.bind(this);
|
||||
this.cssRuleViewBoundCopyDeclaration =
|
||||
this.ruleViewCopyDeclaration.bind(this);
|
||||
this.cssRuleViewBoundCopyProperty = this.ruleViewCopyProperty.bind(this);
|
||||
this.cssRuleViewBoundCopyPropertyValue =
|
||||
this.ruleViewCopyPropertyValue.bind(this);
|
||||
|
||||
// Add the rule view's context menu.
|
||||
this.ruleViewAddContextMenu();
|
||||
|
||||
doc.documentElement.appendChild(this.ruleView.element);
|
||||
this.ruleView.highlight(this.selection);
|
||||
@ -1011,19 +1030,356 @@ InspectorUI.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This is the mousedown handler for the rule view. We use it to track whether
|
||||
* text is currently getting selected.
|
||||
* .
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewMouseDown: function IUI_ruleViewMouseDown(aEvent)
|
||||
{
|
||||
this.ruleView.element.addEventListener("mousemove",
|
||||
this.cssRuleViewBoundMouseMove);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is the mouseup handler for the rule view. We use it to track whether
|
||||
* text is currently getting selected.
|
||||
* .
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewMouseUp: function IUI_ruleViewMouseUp(aEvent)
|
||||
{
|
||||
this.ruleView.element.removeEventListener("mousemove",
|
||||
this.cssRuleViewBoundMouseMove);
|
||||
this.ruleView._selectionMode = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is the mousemove handler for the rule view. We use it to track whether
|
||||
* text is currently getting selected.
|
||||
* .
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewMouseMove: function IUI_ruleViewMouseMove(aEvent)
|
||||
{
|
||||
this.ruleView._selectionMode = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a context menu to the rule view.
|
||||
*/
|
||||
ruleViewAddContextMenu: function IUI_ruleViewAddContextMenu()
|
||||
{
|
||||
let iframe = this.getToolIframe(this.ruleViewObject);
|
||||
let popupSet = this.chromeDoc.getElementById("mainPopupSet");
|
||||
let menu = this.chromeDoc.createElement("menupopup");
|
||||
menu.addEventListener("popupshowing", this.cssRuleViewBoundMenuUpdate);
|
||||
menu.id = "rule-view-context-menu";
|
||||
|
||||
// Copy selection
|
||||
let label = styleInspectorStrings
|
||||
.GetStringFromName("rule.contextmenu.copyselection");
|
||||
let accessKey = styleInspectorStrings
|
||||
.GetStringFromName("rule.contextmenu.copyselection.accesskey");
|
||||
let item = this.chromeDoc.createElement("menuitem");
|
||||
item.id = "rule-view-copy";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.cssRuleViewBoundCopy);
|
||||
menu.appendChild(item);
|
||||
|
||||
// Copy rule
|
||||
label = styleInspectorStrings.
|
||||
GetStringFromName("rule.contextmenu.copyrule");
|
||||
accessKey = styleInspectorStrings.
|
||||
GetStringFromName("rule.contextmenu.copyrule.accesskey");
|
||||
item = this.chromeDoc.createElement("menuitem");
|
||||
item.id = "rule-view-copy-rule";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.cssRuleViewBoundCopyRule);
|
||||
menu.appendChild(item);
|
||||
|
||||
// Copy declaration
|
||||
label = styleInspectorStrings.
|
||||
GetStringFromName("rule.contextmenu.copydeclaration");
|
||||
accessKey = styleInspectorStrings.
|
||||
GetStringFromName("rule.contextmenu.copydeclaration.accesskey");
|
||||
item = this.chromeDoc.createElement("menuitem");
|
||||
item.id = "rule-view-copy-declaration";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.cssRuleViewBoundCopyDeclaration);
|
||||
menu.appendChild(item);
|
||||
|
||||
// Copy property name
|
||||
label = styleInspectorStrings.
|
||||
GetStringFromName("rule.contextmenu.copyproperty");
|
||||
accessKey = styleInspectorStrings.
|
||||
GetStringFromName("rule.contextmenu.copyproperty.accesskey");
|
||||
item = this.chromeDoc.createElement("menuitem");
|
||||
item.id = "rule-view-copy-property";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.cssRuleViewBoundCopyProperty);
|
||||
menu.appendChild(item);
|
||||
|
||||
// Copy property value
|
||||
label = styleInspectorStrings.
|
||||
GetStringFromName("rule.contextmenu.copypropertyvalue");
|
||||
accessKey = styleInspectorStrings.
|
||||
GetStringFromName("rule.contextmenu.copypropertyvalue.accesskey");
|
||||
item = this.chromeDoc.createElement("menuitem");
|
||||
item.id = "rule-view-copy-property-value";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.cssRuleViewBoundCopyPropertyValue);
|
||||
menu.appendChild(item);
|
||||
|
||||
popupSet.appendChild(menu);
|
||||
|
||||
iframe.setAttribute("context", menu.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the rule view's context menu by disabling irrelevant menuitems and
|
||||
* enabling relevant ones.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewMenuUpdate: function IUI_ruleViewMenuUpdate(aEvent)
|
||||
{
|
||||
let iframe = this.getToolIframe(this.ruleViewObject);
|
||||
let win = iframe.contentWindow;
|
||||
|
||||
// Copy selection.
|
||||
let disable = win.getSelection().isCollapsed;
|
||||
let menuitem = this.chromeDoc.getElementById("rule-view-copy");
|
||||
menuitem.disabled = disable;
|
||||
|
||||
// Copy property, copy property name & copy property value.
|
||||
let node = this.chromeDoc.popupNode;
|
||||
if (!node.classList.contains("ruleview-property") &&
|
||||
!node.classList.contains("ruleview-computed")) {
|
||||
while (node = node.parentElement) {
|
||||
if (node.classList.contains("ruleview-property") ||
|
||||
node.classList.contains("ruleview-computed")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let disablePropertyItems = !node || (node &&
|
||||
!node.classList.contains("ruleview-property") &&
|
||||
!node.classList.contains("ruleview-computed"));
|
||||
|
||||
menuitem = this.chromeDoc.querySelector("#rule-view-copy-declaration");
|
||||
menuitem.disabled = disablePropertyItems;
|
||||
menuitem = this.chromeDoc.querySelector("#rule-view-copy-property");
|
||||
menuitem.disabled = disablePropertyItems;
|
||||
menuitem = this.chromeDoc.querySelector("#rule-view-copy-property-value");
|
||||
menuitem.disabled = disablePropertyItems;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy selected text from the rule view.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewCopy: function IUI_ruleViewCopy(aEvent)
|
||||
{
|
||||
let iframe = this.getToolIframe(this.ruleViewObject);
|
||||
let win = iframe.contentWindow;
|
||||
let text = win.getSelection().toString();
|
||||
|
||||
// Remove any double newlines.
|
||||
text = text.replace(/(\r?\n)\r?\n/g, "$1");
|
||||
|
||||
// Remove "inline"
|
||||
let inline = styleInspectorStrings.GetStringFromName("rule.sourceInline");
|
||||
let rx = new RegExp("^" + inline + "\\r?\\n?", "g");
|
||||
text = text.replace(rx, "");
|
||||
|
||||
// Remove file:line
|
||||
text = text.replace(/[\w\.]+:\d+(\r?\n)/g, "$1");
|
||||
|
||||
// Remove inherited from: line
|
||||
let inheritedFrom = styleInspectorStrings
|
||||
.GetStringFromName("rule.inheritedSource");
|
||||
inheritedFrom = inheritedFrom.replace(/\s%S\s\(%S\)/g, "");
|
||||
rx = new RegExp("(\r?\n)" + inheritedFrom + ".*", "g");
|
||||
text = text.replace(rx, "$1");
|
||||
|
||||
clipboardHelper.copyString(text);
|
||||
|
||||
if (aEvent) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy a rule from the rule view.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewCopyRule: function IUI_ruleViewCopyRule(aEvent)
|
||||
{
|
||||
let node = this.chromeDoc.popupNode;
|
||||
if (node.className != "ruleview-code") {
|
||||
if (node.className == "ruleview-rule-source") {
|
||||
node = node.nextElementSibling;
|
||||
} else {
|
||||
while (node = node.parentElement) {
|
||||
if (node.className == "ruleview-code") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node.className == "ruleview-code") {
|
||||
// We need to strip expanded properties from the node because we use
|
||||
// node.textContent below, which also gets text from hidden nodes. The
|
||||
// simplest way to do this is to clone the node and remove them from the
|
||||
// clone.
|
||||
node = node.cloneNode();
|
||||
let computed = node.querySelector(".ruleview-computedlist");
|
||||
if (computed) {
|
||||
computed.parentNode.removeChild(computed);
|
||||
}
|
||||
}
|
||||
|
||||
let text = node.textContent;
|
||||
|
||||
// Format the rule
|
||||
if (osString == "WINNT") {
|
||||
text = text.replace(/{/g, "{\r\n ");
|
||||
text = text.replace(/;/g, ";\r\n ");
|
||||
text = text.replace(/\s*}/g, "\r\n}");
|
||||
} else {
|
||||
text = text.replace(/{/g, "{\n ");
|
||||
text = text.replace(/;/g, ";\n ");
|
||||
text = text.replace(/\s*}/g, "\n}");
|
||||
}
|
||||
|
||||
clipboardHelper.copyString(text);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy a declaration from the rule view.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewCopyDeclaration: function IUI_ruleViewCopyDeclaration(aEvent)
|
||||
{
|
||||
let node = this.chromeDoc.popupNode;
|
||||
if (!node.classList.contains("ruleview-property") &&
|
||||
!node.classList.contains("ruleview-computed")) {
|
||||
while (node = node.parentElement) {
|
||||
if (node.classList.contains("ruleview-property") ||
|
||||
node.classList.contains("ruleview-computed")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to strip expanded properties from the node because we use
|
||||
// node.textContent below, which also gets text from hidden nodes. The
|
||||
// simplest way to do this is to clone the node and remove them from the
|
||||
// clone.
|
||||
node = node.cloneNode();
|
||||
let computed = node.querySelector(".ruleview-computedlist");
|
||||
if (computed) {
|
||||
computed.parentNode.removeChild(computed);
|
||||
}
|
||||
clipboardHelper.copyString(node.textContent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy a property name from the rule view.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewCopyProperty: function IUI_ruleViewCopyProperty(aEvent)
|
||||
{
|
||||
let node = this.chromeDoc.popupNode;
|
||||
|
||||
if (!node.classList.contains("ruleview-propertyname")) {
|
||||
node = node.querySelector(".ruleview-propertyname");
|
||||
}
|
||||
|
||||
if (node) {
|
||||
clipboardHelper.copyString(node.textContent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy a property value from the rule view.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
ruleViewCopyPropertyValue: function IUI_ruleViewCopyPropertyValue(aEvent)
|
||||
{
|
||||
let node = this.chromeDoc.popupNode;
|
||||
|
||||
if (!node.classList.contains("ruleview-propertyvalue")) {
|
||||
node = node.querySelector(".ruleview-propertyvalue");
|
||||
}
|
||||
|
||||
if (node) {
|
||||
clipboardHelper.copyString(node.textContent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the rule view.
|
||||
*/
|
||||
destroyRuleView: function IUI_destroyRuleView()
|
||||
{
|
||||
let iframe = this.getToolIframe(this.ruleViewObject);
|
||||
iframe.removeEventListener("copy", this.cssRuleViewBoundCopy);
|
||||
iframe.parentNode.removeChild(iframe);
|
||||
|
||||
if (this.ruleView) {
|
||||
let menu = this.chromeDoc.querySelector("#rule-view-context-menu");
|
||||
if (menu) {
|
||||
// Copy
|
||||
let menuitem = this.chromeDoc.querySelector("#rule-view-copy");
|
||||
menuitem.removeEventListener("command", this.cssRuleViewBoundCopy);
|
||||
|
||||
// Copy rule
|
||||
menuitem = this.chromeDoc.querySelector("#rule-view-copy-rule");
|
||||
menuitem.removeEventListener("command", this.cssRuleViewBoundCopyRule);
|
||||
|
||||
// Copy property
|
||||
menuitem = this.chromeDoc.querySelector("#rule-view-copy-declaration");
|
||||
menuitem.removeEventListener("command",
|
||||
this.cssRuleViewBoundCopyDeclaration);
|
||||
|
||||
// Copy property name
|
||||
menuitem = this.chromeDoc.querySelector("#rule-view-copy-property");
|
||||
menuitem.removeEventListener("command",
|
||||
this.cssRuleViewBoundCopyProperty);
|
||||
|
||||
// Copy property value
|
||||
menuitem = this.chromeDoc.querySelector("#rule-view-copy-property-value");
|
||||
menuitem.removeEventListener("command",
|
||||
this.cssRuleViewBoundCopyPropertyValue);
|
||||
|
||||
menu.removeEventListener("popupshowing", this.cssRuleViewBoundMenuUpdate);
|
||||
menu.parentNode.removeChild(menu);
|
||||
}
|
||||
|
||||
this.ruleView.element.removeEventListener("CssRuleViewChanged",
|
||||
this.boundRuleViewChanged);
|
||||
this.ruleView.element.removeEventListener("CssRuleViewCSSLinkClicked",
|
||||
this.cssRuleViewBoundCSSLinkClicked);
|
||||
this.ruleView.element.removeEventListener("mousedown",
|
||||
this.cssRuleViewBoundMouseDown);
|
||||
this.ruleView.element.removeEventListener("mouseup",
|
||||
this.cssRuleViewBoundMouseUp);
|
||||
this.ruleView.element.removeEventListener("mousemove",
|
||||
this.cssRuleViewBoundMouseMove);
|
||||
delete boundRuleViewChanged;
|
||||
this.ruleView.clear();
|
||||
delete this.ruleView;
|
||||
@ -1245,6 +1601,7 @@ InspectorUI.prototype = {
|
||||
iframe.id = "devtools-sidebar-iframe-" + aRegObj.id;
|
||||
iframe.setAttribute("flex", "1");
|
||||
iframe.setAttribute("tooltip", "aHTMLTooltip");
|
||||
iframe.addEventListener("mousedown", iframe.focus);
|
||||
this.sidebarDeck.appendChild(iframe);
|
||||
|
||||
// wire up button to show the iframe
|
||||
@ -1356,6 +1713,10 @@ InspectorUI.prototype = {
|
||||
let btn = this.chromeDoc.getElementById(buttonId);
|
||||
this.unbindToolEvent(btn, "click");
|
||||
|
||||
// Remove focus listener
|
||||
let iframe = this.getToolIframe(aRegObj);
|
||||
iframe.removeEventListener("mousedown", iframe.focus);
|
||||
|
||||
// remove sidebar buttons and tools
|
||||
this.sidebarToolbar.removeChild(btn);
|
||||
|
||||
@ -2241,3 +2602,17 @@ XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
|
||||
XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
|
||||
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
|
||||
return Cc["@mozilla.org/widget/clipboardhelper;1"].
|
||||
getService(Ci.nsIClipboardHelper);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "styleInspectorStrings", function() {
|
||||
return Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/styleinspector.properties");
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "osString", function() {
|
||||
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
||||
});
|
||||
|
@ -41,6 +41,7 @@
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const Cu = Components.utils;
|
||||
const FILTER_CHANGED_TIMEOUT = 300;
|
||||
|
||||
@ -161,9 +162,18 @@ function CssHtmlTree(aStyleInspector)
|
||||
this.getRTLAttr = this.win.getComputedStyle(this.win.gBrowser).direction;
|
||||
this.propertyViews = [];
|
||||
|
||||
// Create bound methods.
|
||||
this.siBoundMenuUpdate = this.computedViewMenuUpdate.bind(this);
|
||||
this.siBoundCopy = this.computedViewCopy.bind(this);
|
||||
this.siBoundCopyDeclaration = this.computedViewCopyDeclaration.bind(this);
|
||||
this.siBoundCopyProperty = this.computedViewCopyProperty.bind(this);
|
||||
this.siBoundCopyPropertyValue = this.computedViewCopyPropertyValue.bind(this);
|
||||
|
||||
// The document in which we display the results (csshtmltree.xul).
|
||||
this.styleDocument = this.styleWin.contentWindow.document;
|
||||
|
||||
this.styleDocument.addEventListener("copy", this.siBoundCopy);
|
||||
|
||||
// Nodes used in templating
|
||||
this.root = this.styleDocument.getElementById("root");
|
||||
this.templateRoot = this.styleDocument.getElementById("templateRoot");
|
||||
@ -176,6 +186,7 @@ function CssHtmlTree(aStyleInspector)
|
||||
// The element that we're inspecting, and the document that it comes from.
|
||||
this.viewedElement = null;
|
||||
this.createStyleViews();
|
||||
this.createContextMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,6 +242,11 @@ XPCOMUtils.defineLazyGetter(CssHtmlTree, "HELP_LINK_TITLE", function() {
|
||||
return CssHtmlTree.HELP_LINK_TITLE = CssHtmlTree.l10n("helpLinkTitle");
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
|
||||
return Cc["@mozilla.org/widget/clipboardhelper;1"].
|
||||
getService(Ci.nsIClipboardHelper);
|
||||
});
|
||||
|
||||
CssHtmlTree.prototype = {
|
||||
// Cache the list of properties that have matched and unmatched properties.
|
||||
_matchedProperties: null,
|
||||
@ -469,6 +485,177 @@ CssHtmlTree.prototype = {
|
||||
return this._unmatchedProperties[aProperty];
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a context menu.
|
||||
*/
|
||||
createContextMenu: function SI_createContextMenu()
|
||||
{
|
||||
let popupSet = this.doc.getElementById("mainPopupSet");
|
||||
|
||||
let menu = this.doc.createElement("menupopup");
|
||||
menu.addEventListener("popupshowing", this.siBoundMenuUpdate);
|
||||
menu.id = "computed-view-context-menu";
|
||||
popupSet.appendChild(menu);
|
||||
|
||||
// Copy selection
|
||||
let label = CssHtmlTree.l10n("style.contextmenu.copyselection");
|
||||
let accessKey = CssHtmlTree.l10n("style.contextmenu.copyselection.accesskey");
|
||||
let item = this.doc.createElement("menuitem");
|
||||
item.id = "computed-view-copy";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.siBoundCopy);
|
||||
menu.appendChild(item);
|
||||
|
||||
// Copy declaration
|
||||
label = CssHtmlTree.l10n("style.contextmenu.copydeclaration");
|
||||
accessKey = CssHtmlTree.l10n("style.contextmenu.copydeclaration.accesskey");
|
||||
item = this.doc.createElement("menuitem");
|
||||
item.id = "computed-view-copy-declaration";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.siBoundCopyDeclaration);
|
||||
menu.appendChild(item);
|
||||
|
||||
// Copy property name
|
||||
label = CssHtmlTree.l10n("style.contextmenu.copyproperty");
|
||||
accessKey = CssHtmlTree.l10n("style.contextmenu.copyproperty.accesskey");
|
||||
item = this.doc.createElement("menuitem");
|
||||
item.id = "computed-view-copy-property";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.siBoundCopyProperty);
|
||||
menu.appendChild(item);
|
||||
|
||||
// Copy property value
|
||||
label = CssHtmlTree.l10n("style.contextmenu.copypropertyvalue");
|
||||
accessKey = CssHtmlTree.l10n("style.contextmenu.copypropertyvalue.accesskey");
|
||||
item = this.doc.createElement("menuitem");
|
||||
item.id = "computed-view-copy-property-value";
|
||||
item.setAttribute("label", label);
|
||||
item.setAttribute("accesskey", accessKey);
|
||||
item.addEventListener("command", this.siBoundCopyPropertyValue);
|
||||
menu.appendChild(item);
|
||||
|
||||
this.styleWin.setAttribute("context", menu.id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the context menu by disabling irrelevant menuitems and enabling
|
||||
* relevant ones.
|
||||
*/
|
||||
computedViewMenuUpdate: function si_computedViewMenuUpdate()
|
||||
{
|
||||
let win = this.styleDocument.defaultView;
|
||||
let disable = win.getSelection().isCollapsed;
|
||||
let menuitem = this.doc.querySelector("#computed-view-copy");
|
||||
menuitem.disabled = disable;
|
||||
|
||||
let node = this.doc.popupNode;
|
||||
if (!node.classList.contains("property-view")) {
|
||||
while (node = node.parentElement) {
|
||||
if (node.classList.contains("property-view")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let disablePropertyItems = !node;
|
||||
menuitem = this.doc.querySelector("#computed-view-copy-declaration");
|
||||
menuitem.disabled = disablePropertyItems;
|
||||
menuitem = this.doc.querySelector("#computed-view-copy-property");
|
||||
menuitem.disabled = disablePropertyItems;
|
||||
menuitem = this.doc.querySelector("#computed-view-copy-property-value");
|
||||
menuitem.disabled = disablePropertyItems;
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy selected text.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
computedViewCopy: function si_computedViewCopy(aEvent)
|
||||
{
|
||||
let win = this.styleDocument.defaultView;
|
||||
let text = win.getSelection().toString();
|
||||
|
||||
// Tidy up block headings by moving CSS property names and their values onto
|
||||
// the same line and inserting a colon between them.
|
||||
text = text.replace(/(.+)\r?\n\s+/g, "$1: ");
|
||||
|
||||
// Remove any MDN link titles
|
||||
text = text.replace(CssHtmlTree.HELP_LINK_TITLE, "");
|
||||
clipboardHelper.copyString(text);
|
||||
|
||||
if (aEvent) {
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy declaration.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
computedViewCopyDeclaration: function si_computedViewCopyDeclaration(aEvent)
|
||||
{
|
||||
let node = this.doc.popupNode;
|
||||
if (!node.classList.contains("property-view")) {
|
||||
while (node = node.parentElement) {
|
||||
if (node.classList.contains("property-view")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node) {
|
||||
let name = node.querySelector(".property-name").textContent;
|
||||
let value = node.querySelector(".property-value").textContent;
|
||||
|
||||
clipboardHelper.copyString(name + ": " + value + ";");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy property name.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
computedViewCopyProperty: function si_computedViewCopyProperty(aEvent)
|
||||
{
|
||||
let node = this.doc.popupNode;
|
||||
if (!node.classList.contains("property-view")) {
|
||||
while (node = node.parentElement) {
|
||||
if (node.classList.contains("property-view")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node) {
|
||||
node = node.querySelector(".property-name");
|
||||
clipboardHelper.copyString(node.textContent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy property value.
|
||||
*
|
||||
* @param aEvent The event object
|
||||
*/
|
||||
computedViewCopyPropertyValue: function si_computedViewCopyPropertyValue(aEvent)
|
||||
{
|
||||
let node = this.doc.popupNode;
|
||||
if (!node.classList.contains("property-view")) {
|
||||
while (node = node.parentElement) {
|
||||
if (node.classList.contains("property-view")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node) {
|
||||
node = node.querySelector(".property-value");
|
||||
clipboardHelper.copyString(node.textContent);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Destructor for CssHtmlTree.
|
||||
*/
|
||||
@ -486,6 +673,32 @@ CssHtmlTree.prototype = {
|
||||
this._refreshProcess.cancel();
|
||||
}
|
||||
|
||||
// Remove context menu
|
||||
let menu = this.doc.querySelector("#computed-view-context-menu");
|
||||
if (menu) {
|
||||
// Copy selected
|
||||
let menuitem = this.doc.querySelector("#computed-view-copy");
|
||||
menuitem.removeEventListener("command", this.siBoundCopy);
|
||||
|
||||
// Copy property
|
||||
menuitem = this.doc.querySelector("#computed-view-copy-declaration");
|
||||
menuitem.removeEventListener("command", this.siBoundCopyDeclaration);
|
||||
|
||||
// Copy property name
|
||||
menuitem = this.doc.querySelector("#computed-view-copy-property");
|
||||
menuitem.removeEventListener("command", this.siBoundCopyProperty);
|
||||
|
||||
// Copy property value
|
||||
menuitem = this.doc.querySelector("#computed-view-copy-property-value");
|
||||
menuitem.removeEventListener("command", this.siBoundCopyPropertyValue);
|
||||
|
||||
menu.removeEventListener("popupshowing", this.siBoundMenuUpdate);
|
||||
menu.parentNode.removeChild(menu);
|
||||
}
|
||||
|
||||
// Remove bound listeners
|
||||
this.styleDocument.removeEventListener("copy", this.siBoundCopy);
|
||||
|
||||
// Nodes used in templating
|
||||
delete this.root;
|
||||
delete this.propertyContainer;
|
||||
@ -658,32 +871,35 @@ PropertyView.prototype = {
|
||||
let doc = this.tree.doc;
|
||||
this.element = doc.createElementNS(HTML_NS, "tr");
|
||||
this.element.setAttribute("class", this.propertyHeaderClassName);
|
||||
this.element.addEventListener("click", this.propertyRowClick.bind(this), false);
|
||||
|
||||
this.propertyHeader = doc.createElementNS(HTML_NS, "td");
|
||||
this.element.appendChild(this.propertyHeader);
|
||||
this.propertyHeader.setAttribute("class", "property-header");
|
||||
|
||||
this.matchedExpander = doc.createElementNS(HTML_NS, "div");
|
||||
this.propertyHeader.appendChild(this.matchedExpander);
|
||||
this.matchedExpander.setAttribute("class", "match expander");
|
||||
|
||||
this.nameNode = doc.createElementNS(HTML_NS, "div");
|
||||
this.propertyHeader.appendChild(this.nameNode);
|
||||
this.nameNode.setAttribute("tabindex", "0");
|
||||
this.nameNode.addEventListener("keydown", function(aEvent) {
|
||||
this.matchedExpander.setAttribute("tabindex", "0");
|
||||
this.matchedExpander.addEventListener("click",
|
||||
this.matchedExpanderClick.bind(this), false);
|
||||
this.matchedExpander.addEventListener("keydown", function(aEvent) {
|
||||
let keyEvent = Ci.nsIDOMKeyEvent;
|
||||
if (aEvent.keyCode == keyEvent.DOM_VK_F1) {
|
||||
this.mdnLinkClick();
|
||||
}
|
||||
if (aEvent.keyCode == keyEvent.DOM_VK_RETURN ||
|
||||
aEvent.keyCode == keyEvent.DOM_VK_SPACE) {
|
||||
this.propertyRowClick(aEvent);
|
||||
aEvent.keyCode == keyEvent.DOM_VK_SPACE) {
|
||||
this.matchedExpanderClick(aEvent);
|
||||
}
|
||||
}.bind(this), false);
|
||||
this.propertyHeader.appendChild(this.matchedExpander);
|
||||
|
||||
this.nameNode = doc.createElementNS(HTML_NS, "div");
|
||||
this.propertyHeader.appendChild(this.nameNode);
|
||||
this.nameNode.setAttribute("class", "property-name");
|
||||
this.nameNode.textContent = this.name;
|
||||
this.nameNode.addEventListener("click", function(aEvent) {
|
||||
this.matchedExpander.focus();
|
||||
}.bind(this), false);
|
||||
|
||||
let helpcontainer = doc.createElementNS(HTML_NS, "td");
|
||||
this.element.appendChild(helpcontainer);
|
||||
@ -754,9 +970,9 @@ PropertyView.prototype = {
|
||||
this.matchedSelectorsContainer.parentNode.hidden = !hasMatchedSelectors;
|
||||
|
||||
if (hasMatchedSelectors) {
|
||||
this.propertyHeader.parentNode.classList.add("expandable");
|
||||
this.matchedExpander.classList.add("expandable");
|
||||
} else {
|
||||
this.propertyHeader.parentNode.classList.remove("expandable");
|
||||
this.matchedExpander.classList.remove("expandable");
|
||||
}
|
||||
|
||||
if (this.matchedExpanded && hasMatchedSelectors) {
|
||||
@ -852,17 +1068,13 @@ PropertyView.prototype = {
|
||||
* The action when a user expands matched selectors.
|
||||
*
|
||||
* @param {Event} aEvent Used to determine the class name of the targets click
|
||||
* event. If the class name is "helplink" then the event is allowed to bubble
|
||||
* to the mdn link icon.
|
||||
* event.
|
||||
*/
|
||||
propertyRowClick: function PropertyView_propertyRowClick(aEvent)
|
||||
matchedExpanderClick: function PropertyView_matchedExpanderClick(aEvent)
|
||||
{
|
||||
if (aEvent.target.className != "helplink") {
|
||||
this.matchedExpanded = !this.matchedExpanded;
|
||||
this.refreshAllSelectors();
|
||||
this.nameNode.focus();
|
||||
aEvent.preventDefault();
|
||||
}
|
||||
this.matchedExpanded = !this.matchedExpanded;
|
||||
this.refreshAllSelectors();
|
||||
aEvent.preventDefault();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -986,6 +1198,14 @@ SelectorView.prototype = {
|
||||
return result;
|
||||
},
|
||||
|
||||
maybeOpenStyleEditor: function(aEvent)
|
||||
{
|
||||
let keyEvent = Ci.nsIDOMKeyEvent;
|
||||
if (aEvent.keyCode == keyEvent.DOM_VK_RETURN) {
|
||||
this.openStyleEditor();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When a css link is clicked this method is called in order to either:
|
||||
* 1. Open the link in view source (for element style attributes).
|
||||
|
@ -23,6 +23,7 @@
|
||||
* Contributor(s):
|
||||
* Dave Camp <dcamp@mozilla.com> (Original Author)
|
||||
* Rob Campbell <rcampbell@mozilla.com>
|
||||
* Mike Ratcliffe <mratcliffe@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
@ -807,7 +808,7 @@ CssRuleView.prototype = {
|
||||
for each (let rule in this._elementStyle.rules) {
|
||||
// Don't hold a reference to this editor beyond the one held
|
||||
// by the node.
|
||||
let editor = new RuleEditor(this.doc, rule);
|
||||
let editor = new RuleEditor(this, rule);
|
||||
this.element.appendChild(editor.element);
|
||||
}
|
||||
},
|
||||
@ -816,15 +817,17 @@ CssRuleView.prototype = {
|
||||
/**
|
||||
* Create a RuleEditor.
|
||||
*
|
||||
* @param object aDoc
|
||||
* The document holding this rule editor.
|
||||
* @param CssRuleView aRuleView
|
||||
* The CssRuleView containg the document holding this rule editor and the
|
||||
* _selectionMode flag.
|
||||
* @param Rule aRule
|
||||
* The Rule object we're editing.
|
||||
* @constructor
|
||||
*/
|
||||
function RuleEditor(aDoc, aRule)
|
||||
function RuleEditor(aRuleView, aRule)
|
||||
{
|
||||
this.doc = aDoc;
|
||||
this.ruleView = aRuleView;
|
||||
this.doc = this.ruleView.doc;
|
||||
this.rule = aRule;
|
||||
|
||||
this._onNewProperty = this._onNewProperty.bind(this);
|
||||
@ -893,8 +896,16 @@ RuleEditor.prototype = {
|
||||
|
||||
// We made the close brace focusable, tabbing to it
|
||||
// or clicking on it should start the new property editor.
|
||||
this.closeBrace.addEventListener("focus", function() {
|
||||
this.newProperty();
|
||||
this.closeBrace.addEventListener("focus", function(aEvent) {
|
||||
if (!this.ruleView._selectionMode) {
|
||||
this.newProperty();
|
||||
}
|
||||
}.bind(this), true);
|
||||
this.closeBrace.addEventListener("mousedown", function(aEvent) {
|
||||
aEvent.preventDefault();
|
||||
}.bind(this), true);
|
||||
this.closeBrace.addEventListener("click", function(aEvent) {
|
||||
this.closeBrace.focus();
|
||||
}.bind(this), true);
|
||||
},
|
||||
|
||||
@ -1261,6 +1272,21 @@ function editableField(aOptions)
|
||||
aOptions.element.addEventListener("focus", function() {
|
||||
new InplaceEditor(aOptions);
|
||||
}, false);
|
||||
|
||||
// In order to allow selection on the element, prevent focus on
|
||||
// mousedown. Focus on click instead.
|
||||
aOptions.element.addEventListener("mousedown", function(evt) {
|
||||
evt.preventDefault();
|
||||
}, false);
|
||||
aOptions.element.addEventListener("click", function(evt) {
|
||||
let win = this.ownerDocument.defaultView;
|
||||
let selection = win.getSelection();
|
||||
if (selection.isCollapsed) {
|
||||
aOptions.element.focus();
|
||||
} else {
|
||||
selection.removeAllRanges();
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
var _editableField = editableField;
|
||||
|
||||
|
@ -55,7 +55,7 @@ var EXPORTED_SYMBOLS = ["StyleInspector"];
|
||||
function StyleInspector(aContext, aIUI)
|
||||
{
|
||||
this._init(aContext, aIUI);
|
||||
};
|
||||
}
|
||||
|
||||
StyleInspector.prototype = {
|
||||
|
||||
|
@ -114,8 +114,11 @@ To visually debug the templates without running firefox, alter the display:none
|
||||
${selector.humanReadableText(__element)}
|
||||
</td>
|
||||
<td class="rule-link">
|
||||
<a target="_blank" onclick="${selector.openStyleEditor}" class="link"
|
||||
title="${selector.selectorInfo.href}">${selector.selectorInfo.source}</a>
|
||||
<a target="_blank" class="link"
|
||||
onclick="${selector.openStyleEditor}"
|
||||
onkeydown="${selector.maybeOpenStyleEditor}"
|
||||
title="${selector.selectorInfo.href}"
|
||||
tabindex="0">${selector.selectorInfo.source}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</loop>
|
||||
|
@ -37,6 +37,7 @@
|
||||
|
||||
.ruleview {
|
||||
overflow: auto;
|
||||
-moz-user-select: text;
|
||||
}
|
||||
|
||||
.ruleview-computedlist:not(.styleinspector-open) {
|
||||
|
@ -65,6 +65,8 @@ _BROWSER_TEST_FILES = \
|
||||
browser_bug722196_property_view_media_queries.js \
|
||||
browser_bug722196_rule_view_media_queries.js \
|
||||
browser_bug_592743_specificity.js \
|
||||
browser_ruleview_bug_703643_context_menu_copy.js \
|
||||
browser_computedview_bug_703643_context_menu_copy.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
|
@ -55,12 +55,12 @@ function SI_test()
|
||||
let searchbar = stylePanel.cssHtmlTree.searchField;
|
||||
let propView = getFirstVisiblePropertyView();
|
||||
let rulesTable = propView.matchedSelectorsContainer;
|
||||
let nameNode = propView.nameNode;
|
||||
let matchedExpander = propView.matchedExpander;
|
||||
|
||||
info("Adding focus event handler to property name node");
|
||||
nameNode.addEventListener("focus", function nameFocused() {
|
||||
this.removeEventListener("focus", nameFocused);
|
||||
info("property name is focused");
|
||||
info("Adding focus event handler to property expander");
|
||||
matchedExpander.addEventListener("focus", function expanderFocused() {
|
||||
this.removeEventListener("focus", expanderFocused);
|
||||
info("property expander is focused");
|
||||
info("checking expand / collapse");
|
||||
testKey(iframe.contentWindow, "VK_SPACE", rulesTable);
|
||||
testKey(iframe.contentWindow, "VK_RETURN", rulesTable);
|
||||
@ -74,7 +74,7 @@ function SI_test()
|
||||
searchbar.addEventListener("focus", function searchbarFocused() {
|
||||
this.removeEventListener("focus", searchbarFocused);
|
||||
info("search filter is focused");
|
||||
info("tabbing to property name node");
|
||||
info("tabbing to property expander node");
|
||||
EventUtils.synthesizeKey("VK_TAB", {}, iframe.contentWindow);
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,163 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the style inspector works properly
|
||||
|
||||
let doc;
|
||||
let stylePanel;
|
||||
let cssHtmlTree;
|
||||
|
||||
function createDocument()
|
||||
{
|
||||
doc.body.innerHTML = '<style type="text/css"> ' +
|
||||
'span { font-variant: small-caps; color: #000000; } ' +
|
||||
'.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
|
||||
'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
|
||||
'<h1>Some header text</h1>\n' +
|
||||
'<p id="salutation" style="font-size: 12pt">hi.</p>\n' +
|
||||
'<p id="body" style="font-size: 12pt">I am a test-case. This text exists ' +
|
||||
'solely to provide some things to <span style="color: yellow">' +
|
||||
'highlight</span> and <span style="font-weight: bold">count</span> ' +
|
||||
'style list-items in the box at right. If you are reading this, ' +
|
||||
'you should go do something else instead. Maybe read a book. Or better ' +
|
||||
'yet, write some test-cases for another bit of code. ' +
|
||||
'<span style="font-style: italic">some text</span></p>\n' +
|
||||
'<p id="closing">more text</p>\n' +
|
||||
'<p>even more text</p>' +
|
||||
'</div>';
|
||||
doc.title = "Computed view context menu test";
|
||||
|
||||
let span = doc.querySelector("span");
|
||||
ok(span, "captain, we have the span");
|
||||
|
||||
stylePanel = new StyleInspector(window);
|
||||
Services.obs.addObserver(runStyleInspectorTests, "StyleInspector-populated", false);
|
||||
stylePanel.createPanel(false, function() {
|
||||
stylePanel.open(span);
|
||||
});
|
||||
}
|
||||
|
||||
function runStyleInspectorTests()
|
||||
{
|
||||
Services.obs.removeObserver(runStyleInspectorTests, "StyleInspector-populated", false);
|
||||
|
||||
ok(stylePanel.isOpen(), "style inspector is open");
|
||||
|
||||
cssHtmlTree = stylePanel.cssHtmlTree;
|
||||
|
||||
let contentDocument = stylePanel.iframe.contentDocument;
|
||||
let prop = contentDocument.querySelector(".property-view");
|
||||
ok(prop, "captain, we have the property-view node");
|
||||
|
||||
// We need the context menu to open in the correct place in order for
|
||||
// popupNode to be propertly set.
|
||||
EventUtils.synthesizeMouse(prop, 1, 1, { type: "contextmenu", button: 2 },
|
||||
stylePanel.iframe.contentWindow);
|
||||
|
||||
checkCopyProperty()
|
||||
}
|
||||
|
||||
function checkCopyProperty()
|
||||
{
|
||||
info("Checking that cssHtmlTree.siBoundCopyDeclaration() returns the " +
|
||||
"correct clipboard value");
|
||||
let expectedPattern = "color: rgb\\(255, 255, 0\\);";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function CS_boundCopyPropCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},
|
||||
cssHtmlTree.siBoundCopyDeclaration,
|
||||
checkCopyPropertyName, checkCopyPropertyName);
|
||||
}
|
||||
|
||||
function checkCopyPropertyName()
|
||||
{
|
||||
info("Checking that cssHtmlTree.siBoundCopyProperty() returns the " +
|
||||
"correct clipboard value");
|
||||
let expectedPattern = "color";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function CS_boundCopyPropNameCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},
|
||||
cssHtmlTree.siBoundCopyProperty,
|
||||
checkCopyPropertyValue, checkCopyPropertyValue);
|
||||
}
|
||||
|
||||
function checkCopyPropertyValue()
|
||||
{
|
||||
info("Checking that cssHtmlTree.siBoundCopyPropertyValue() returns the " +
|
||||
"correct clipboard value");
|
||||
let expectedPattern = "rgb\\(255, 255, 0\\)";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function CS_boundCopyPropValueCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},
|
||||
cssHtmlTree.siBoundCopyPropertyValue,
|
||||
checkCopySelection, checkCopySelection);
|
||||
}
|
||||
|
||||
function checkCopySelection()
|
||||
{
|
||||
let contentDocument = stylePanel.iframe.contentDocument;
|
||||
let contentWindow = stylePanel.iframe.contentWindow;
|
||||
let props = contentDocument.querySelectorAll(".property-view");
|
||||
ok(props, "captain, we have the property-view nodes");
|
||||
|
||||
let range = document.createRange();
|
||||
range.setStart(props[0], 0);
|
||||
range.setEnd(props[3], 3);
|
||||
contentWindow.getSelection().addRange(range);
|
||||
|
||||
info("Checking that cssHtmlTree.siBoundCopyPropertyValue() " +
|
||||
" returns the correct clipboard value");
|
||||
|
||||
let expectedPattern = "color: rgb\\(255, 255, 0\\)[\\r\\n]+" +
|
||||
"font-family: helvetica,sans-serif[\\r\\n]+" +
|
||||
"font-size: 16px[\\r\\n]+" +
|
||||
"font-variant: small-caps[\\r\\n]*";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function CS_boundCopyCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},
|
||||
cssHtmlTree.siBoundCopy, closeStyleInspector, closeStyleInspector);
|
||||
}
|
||||
|
||||
function checkClipboardData(aExpectedPattern)
|
||||
{
|
||||
let actual = SpecialPowers.getClipboardData("text/unicode");
|
||||
let expectedRegExp = new RegExp(aExpectedPattern, "g");
|
||||
return expectedRegExp.test(actual);
|
||||
}
|
||||
|
||||
function closeStyleInspector()
|
||||
{
|
||||
Services.obs.addObserver(finishUp, "StyleInspector-closed", false);
|
||||
stylePanel.close();
|
||||
}
|
||||
|
||||
function finishUp()
|
||||
{
|
||||
Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
|
||||
ok(!stylePanel.isOpen(), "style inspector is closed");
|
||||
doc = stylePanel = cssHtmlTree = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
|
||||
doc = content.document;
|
||||
waitForFocus(createDocument, content);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,computed view context menu test";
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let doc;
|
||||
|
||||
function createDocument()
|
||||
{
|
||||
doc.body.innerHTML = '<style type="text/css"> ' +
|
||||
'html { color: #000000; } ' +
|
||||
'span { font-variant: small-caps; color: #000000; } ' +
|
||||
'.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ' +
|
||||
'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">\n' +
|
||||
'<h1>Some header text</h1>\n' +
|
||||
'<p id="salutation" style="font-size: 12pt">hi.</p>\n' +
|
||||
'<p id="body" style="font-size: 12pt">I am a test-case. This text exists ' +
|
||||
'solely to provide some things to <span style="color: yellow">' +
|
||||
'highlight</span> and <span style="font-weight: bold">count</span> ' +
|
||||
'style list-items in the box at right. If you are reading this, ' +
|
||||
'you should go do something else instead. Maybe read a book. Or better ' +
|
||||
'yet, write some test-cases for another bit of code. ' +
|
||||
'<span style="font-style: italic">some text</span></p>\n' +
|
||||
'<p id="closing">more text</p>\n' +
|
||||
'<p>even more text</p>' +
|
||||
'</div>';
|
||||
doc.title = "Rule view context menu test";
|
||||
|
||||
openInspector();
|
||||
}
|
||||
|
||||
function openInspector()
|
||||
{
|
||||
ok(window.InspectorUI, "InspectorUI variable exists");
|
||||
ok(!InspectorUI.inspecting, "Inspector is not highlighting");
|
||||
ok(InspectorUI.store.isEmpty(), "Inspector.store is empty");
|
||||
|
||||
Services.obs.addObserver(inspectorUIOpen,
|
||||
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
|
||||
InspectorUI.openInspectorUI();
|
||||
}
|
||||
|
||||
function inspectorUIOpen()
|
||||
{
|
||||
Services.obs.removeObserver(inspectorUIOpen,
|
||||
InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
|
||||
|
||||
// Make sure the inspector is open.
|
||||
ok(InspectorUI.inspecting, "Inspector is highlighting");
|
||||
ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
|
||||
ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
|
||||
ok(!InspectorUI.store.isEmpty(), "InspectorUI.store is not empty");
|
||||
is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
|
||||
|
||||
// Highlight a node.
|
||||
let div = content.document.getElementsByTagName("div")[0];
|
||||
InspectorUI.inspectNode(div);
|
||||
InspectorUI.stopInspecting();
|
||||
is(InspectorUI.selection, div, "selection matches the div element");
|
||||
|
||||
Services.obs.addObserver(testClip,
|
||||
InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
|
||||
|
||||
InspectorUI.showSidebar();
|
||||
InspectorUI.openRuleView();
|
||||
}
|
||||
|
||||
function testClip()
|
||||
{
|
||||
Services.obs.removeObserver(testClip,
|
||||
InspectorUI.INSPECTOR_NOTIFICATIONS.RULEVIEWREADY, false);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Checking that InspectorUI.ruleViewCopyRule() returns " +
|
||||
"the correct clipboard value");
|
||||
let expectedPattern = "element {[\\r\\n]+" +
|
||||
" margin: 10em;[\\r\\n]+" +
|
||||
" font-size: 14pt;[\\r\\n]+" +
|
||||
" font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||
" color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
|
||||
"}[\\r\\n]*";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function IUI_boundCopyPropCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},
|
||||
checkCopyRule, checkCopyProperty, checkCopyProperty);
|
||||
});
|
||||
}
|
||||
|
||||
function checkCopyRule() {
|
||||
let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
|
||||
let contentDoc = ruleView.contentDocument;
|
||||
let props = contentDoc.querySelectorAll(".ruleview-property");
|
||||
|
||||
is(props.length, 5, "checking property length");
|
||||
|
||||
let prop = props[2];
|
||||
let propName = prop.querySelector(".ruleview-propertyname").textContent;
|
||||
let propValue = prop.querySelector(".ruleview-propertyvalue").textContent;
|
||||
|
||||
is(propName, "font-family", "checking property name");
|
||||
is(propValue, "helvetica,sans-serif", "checking property value");
|
||||
|
||||
// We need the context menu to open in the correct place in order for
|
||||
// popupNode to be propertly set.
|
||||
EventUtils.synthesizeMouse(prop, 1, 1, { type: "contextmenu", button: 2 },
|
||||
ruleView.contentWindow);
|
||||
|
||||
InspectorUI.ruleViewCopyRule();
|
||||
}
|
||||
|
||||
function checkCopyProperty()
|
||||
{
|
||||
info("Checking that InspectorUI.cssRuleViewBoundCopyDeclaration() returns " +
|
||||
"the correct clipboard value");
|
||||
let expectedPattern = "font-family: helvetica,sans-serif;";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function IUI_boundCopyPropCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},
|
||||
InspectorUI.cssRuleViewBoundCopyDeclaration,
|
||||
checkCopyPropertyName, checkCopyPropertyName);
|
||||
}
|
||||
|
||||
function checkCopyPropertyName()
|
||||
{
|
||||
info("Checking that InspectorUI.cssRuleViewBoundCopyProperty() returns " +
|
||||
"the correct clipboard value");
|
||||
let expectedPattern = "font-family";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function IUI_boundCopyPropNameCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},
|
||||
InspectorUI.cssRuleViewBoundCopyProperty,
|
||||
checkCopyPropertyValue, checkCopyPropertyValue);
|
||||
}
|
||||
|
||||
function checkCopyPropertyValue()
|
||||
{
|
||||
info("Checking that InspectorUI.cssRuleViewBoundCopyPropertyValue() " +
|
||||
" returns the correct clipboard value");
|
||||
let expectedPattern = "helvetica,sans-serif";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function IUI_boundCopyPropValueCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},
|
||||
InspectorUI.cssRuleViewBoundCopyPropertyValue,
|
||||
checkCopySelection, checkCopySelection);
|
||||
}
|
||||
|
||||
function checkCopySelection()
|
||||
{
|
||||
let ruleView = document.querySelector("#devtools-sidebar-iframe-ruleview");
|
||||
let contentDoc = ruleView.contentDocument;
|
||||
let props = contentDoc.querySelectorAll(".ruleview-property");
|
||||
|
||||
let range = document.createRange();
|
||||
range.setStart(props[0], 0);
|
||||
range.setEnd(props[4], 8);
|
||||
ruleView.contentWindow.getSelection().addRange(range);
|
||||
|
||||
info("Checking that InspectorUI.cssRuleViewBoundCopy() returns the correct" +
|
||||
"clipboard value");
|
||||
let expectedPattern = " margin: 10em;[\\r\\n]+" +
|
||||
" font-size: 14pt;[\\r\\n]+" +
|
||||
" font-family: helvetica,sans-serif;[\\r\\n]+" +
|
||||
" color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
|
||||
"}[\\r\\n]+" +
|
||||
"html {[\\r\\n]+" +
|
||||
" color: rgb\\(0, 0, 0\\);[\\r\\n]*";
|
||||
info("Expected pattern: " + expectedPattern);
|
||||
|
||||
SimpleTest.waitForClipboard(function IUI_boundCopyCheck() {
|
||||
return checkClipboardData(expectedPattern);
|
||||
},InspectorUI.cssRuleViewBoundCopy, finishup, finishup);
|
||||
}
|
||||
|
||||
function checkClipboardData(aExpectedPattern)
|
||||
{
|
||||
let actual = SpecialPowers.getClipboardData("text/unicode");
|
||||
let expectedRegExp = new RegExp(aExpectedPattern, "g");
|
||||
return expectedRegExp.test(actual);
|
||||
}
|
||||
|
||||
function finishup()
|
||||
{
|
||||
InspectorUI.closeInspectorUI();
|
||||
gBrowser.removeCurrentTab();
|
||||
doc = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee,
|
||||
true);
|
||||
doc = content.document;
|
||||
waitForFocus(createDocument, content);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,<p>rule view context menu test</p>";
|
||||
}
|
@ -49,3 +49,75 @@ helpLinkTitle=Read the documentation for this property
|
||||
# entered into the rule view a warning icon is displayed. This text is used for
|
||||
# the title attribute of the warning icon.
|
||||
rule.warning.title=Invalid property value
|
||||
|
||||
# LOCALIZATION NOTE (style.contextmenu.copyselection): The computed view's
|
||||
# context menu copy entry.
|
||||
style.contextmenu.copyselection=Copy selection
|
||||
|
||||
# LOCALIZATION NOTE (style.contextmenu.copyselection.accesskey): The computed
|
||||
# view's context menu copy entry access key.
|
||||
style.contextmenu.copyselection.accesskey=C
|
||||
|
||||
# LOCALIZATION NOTE (style.contextmenu.copydeclaration): The style inspector's
|
||||
# context menu copy property entry allows a complete CSS property to be copied.
|
||||
style.contextmenu.copydeclaration=Copy declaration line
|
||||
|
||||
# LOCALIZATION NOTE (style.contextmenu.copydeclaration.accesskey): The style
|
||||
# inspector's context menu copy property access key.
|
||||
style.contextmenu.copydeclaration.accesskey=D
|
||||
|
||||
# LOCALIZATION NOTE (style.contextmenu.copyproperty): The style inspector's
|
||||
# context menu copy property name entry allows a CSS property name to be copied.
|
||||
style.contextmenu.copyproperty=Copy property
|
||||
|
||||
# LOCALIZATION NOTE (style.contextmenu.copyproperty.accesskey): The style
|
||||
# inspector's context menu copy property name access key.
|
||||
style.contextmenu.copyproperty.accesskey=P
|
||||
|
||||
# LOCALIZATION NOTE (style.contextmenu.copypropertyvalue): The style inspector's
|
||||
# context menu copy property value entry allows a CSS property name to be copied.
|
||||
style.contextmenu.copypropertyvalue=Copy property value
|
||||
|
||||
# LOCALIZATION NOTE (style.contextmenu.copypropertyvalue.accesskey): The style
|
||||
# inspector's context menu copy property value access key.
|
||||
style.contextmenu.copypropertyvalue.accesskey=U
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copyselection): The rule view's context
|
||||
# menu copy entry.
|
||||
rule.contextmenu.copyselection=Copy selection
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copyselection.accesskey): The rule view's
|
||||
# context menu copy entry access key.
|
||||
rule.contextmenu.copyselection.accesskey=C
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copyrule): The rule view's context menu
|
||||
# copy rule entry allows a complete CSS rule to be copied.
|
||||
rule.contextmenu.copyrule=Copy rule
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copyrule.accesskey): The rule view's
|
||||
# context menu copy rule access key.
|
||||
rule.contextmenu.copyrule.accesskey=R
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copydeclaration): The rule view's context
|
||||
# menu copy property entry allows a complete CSS property to be copied.
|
||||
rule.contextmenu.copydeclaration=Copy declaration line
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copydeclaration.accesskey): The rule view's
|
||||
# context menu copy property access key.
|
||||
rule.contextmenu.copydeclaration.accesskey=D
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copyproperty): The rule view's context
|
||||
# menu copy property entry allows a CSS property name to be copied.
|
||||
rule.contextmenu.copyproperty=Copy property
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copyproperty.accesskey): The rule
|
||||
# view's context menu copy property name access key.
|
||||
rule.contextmenu.copyproperty.accesskey=P
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copypropertyvalue): The rule view's
|
||||
# context menu copy property entry allows a CSS property value to be copied.
|
||||
rule.contextmenu.copypropertyvalue=Copy property value
|
||||
|
||||
# LOCALIZATION NOTE (rule.contextmenu.copypropertyvalue.accesskey): The rule
|
||||
# view's context menu copy property value access key.
|
||||
rule.contextmenu.copypropertyvalue.accesskey=U
|
||||
|
@ -105,15 +105,12 @@
|
||||
-moz-appearance: treetwistyopen;
|
||||
}
|
||||
|
||||
.expandable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.match {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.expandable > .property-header > .match {
|
||||
.expandable {
|
||||
cursor: pointer;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@ -165,6 +162,7 @@
|
||||
-moz-box-flex: 1;
|
||||
overflow-y: auto;
|
||||
border-collapse: collapse;
|
||||
-moz-user-select: text;
|
||||
}
|
||||
|
||||
.darkrow {
|
||||
@ -235,6 +233,11 @@
|
||||
-moz-padding-end: 5px;
|
||||
}
|
||||
|
||||
.ruleview-ruleclose {
|
||||
width: -moz-min-content;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.ruleview-propertylist {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
@ -107,15 +107,12 @@
|
||||
-moz-appearance: treetwistyopen;
|
||||
}
|
||||
|
||||
.expandable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.match {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.expandable > .property-header > .match {
|
||||
.expandable {
|
||||
cursor: pointer;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@ -167,6 +164,7 @@
|
||||
-moz-box-flex: 1;
|
||||
overflow-y: auto;
|
||||
border-collapse: collapse;
|
||||
-moz-user-select: text;
|
||||
}
|
||||
|
||||
.darkrow {
|
||||
@ -237,6 +235,11 @@
|
||||
-moz-padding-end: 5px;
|
||||
}
|
||||
|
||||
.ruleview-ruleclose {
|
||||
width: -moz-min-content;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.ruleview-propertylist {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
@ -105,15 +105,12 @@
|
||||
background-image: url("chrome://global/skin/tree/twisty-open.png");
|
||||
}
|
||||
|
||||
.expandable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.match {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.expandable > .property-header > .match {
|
||||
.expandable {
|
||||
cursor: pointer;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@ -165,6 +162,7 @@
|
||||
-moz-box-flex: 1;
|
||||
overflow-y: auto;
|
||||
border-collapse: collapse;
|
||||
-moz-user-select: text;
|
||||
}
|
||||
|
||||
.darkrow {
|
||||
@ -235,6 +233,11 @@
|
||||
-moz-padding-end: 5px;
|
||||
}
|
||||
|
||||
.ruleview-ruleclose {
|
||||
width: -moz-min-content;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.ruleview-propertylist {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
Loading…
Reference in New Issue
Block a user