Bug 703643 - Be able to copy from the rules view; r=prouget

This commit is contained in:
Michael Ratcliffe 2012-03-11 14:01:38 +00:00
parent 30e2c80691
commit db9875d847
14 changed files with 1137 additions and 57 deletions

View File

@ -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;
});

View File

@ -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).

View File

@ -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;

View File

@ -55,7 +55,7 @@ var EXPORTED_SYMBOLS = ["StyleInspector"];
function StyleInspector(aContext, aIUI)
{
this._init(aContext, aIUI);
};
}
StyleInspector.prototype = {

View File

@ -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>

View File

@ -37,6 +37,7 @@
.ruleview {
overflow: auto;
-moz-user-select: text;
}
.ruleview-computedlist:not(.styleinspector-open) {

View File

@ -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)

View File

@ -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);
});

View File

@ -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";
}

View File

@ -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>";
}

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;