/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Joe Hewitt (original author) * Jason Barnabe * * 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 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /*************************************************************** * StyleRulesViewer -------------------------------------------- * The viewer for CSS style rules that apply to a DOM element. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - * REQUIRED IMPORTS: * chrome://inspector/content/util.js * chrome://inspector/content/jsutil/xpcom/XPCU.js * chrome://inspector/content/jsutil/rdf/RDFU.js ****************************************************************/ //////////// global variables ///////////////////// var viewer; var gPromptService; //////////// global constants //////////////////// var kCSSRuleDataSourceIID = "@mozilla.org/rdf/datasource;1?name=Inspector_CSSRules"; var kCSSDecDataSourceIID = "@mozilla.org/rdf/datasource;1?name=Inspector_CSSDec"; var kPromptServiceCID = "@mozilla.org/embedcomp/prompt-service;1"; var nsEventStateUnspecificed = 0; var nsEventStateActive = 1; var nsEventStateFocus = 2; var nsEventStateHover = 4; var nsEventStateDragOver = 8; ///////////////////////////////////////////////// window.addEventListener("load", StyleRulesViewer_initialize, false); function StyleRulesViewer_initialize() { viewer = new StyleRulesViewer(); viewer.initialize(parent.FrameExchange.receiveData(window)); gPromptService = XPCU.getService(kPromptServiceCID, "nsIPromptService"); } //////////////////////////////////////////////////////////////////////////// //// class StyleRulesViewer function StyleRulesViewer() // implements inIViewer { this.mObsMan = new ObserverManager(); this.mURL = window.location; this.mRuleTree = document.getElementById("olStyleRules"); this.mRuleBoxObject = this.mRuleTree.treeBoxObject; this.mPropsTree = document.getElementById("olStyleProps"); this.mPropsBoxObject = this.mPropsTree.treeBoxObject; } //XXX Don't use anonymous functions StyleRulesViewer.prototype = { //////////////////////////////////////////////////////////////////////////// //// Initialization mRuleDS: null, mDecDS: null, mSubject: null, mPanel: null, //////////////////////////////////////////////////////////////////////////// //// interface inIViewer get uid() { return "styleRules" }, get pane() { return this.mPanel }, get selection() { return null }, get subject() { return this.mSubject }, set subject(aObject) { this.mSubject = aObject; // update the rule tree this.mRuleView = new StyleRuleView(aObject); this.mRuleBoxObject.view = this.mRuleView; // clear the props tree this.mPropsView = null; this.mPropsBoxObject.view = null; this.mObsMan.dispatchEvent("subjectChange", { subject: aObject }); }, initialize: function(aPane) { this.mPanel = aPane; aPane.notifyViewerReady(this); }, destroy: function() { // We need to remove the views at this time or else they will attempt to // re-paint while the document is being deconstructed, resulting in // some nasty XPConnect assertions this.mRuleBoxObject.view = null; this.mPropsBoxObject.view = null; }, isCommandEnabled: function isCommandEnabled(aCommand) { var declaration = this.getSelectedDec(); // if no declaration is selected, nothing is enabled if (!declaration) return false; //XXX can't edit resource: stylesheets because of bug 343508 var rule = declaration.parentRule; var isEditable = !(rule && rule.parentStyleSheet && /^resource:/.test(rule.parentStyleSheet.href)); switch (aCommand) { case "cmdEditCopy": return this.mPropsTree.view.selection.count > 0; case "cmdEditDelete": case "cmdTogglePriority": return isEditable && this.mPropsTree.view.selection.count > 0; case "cmdEditInsert": return isEditable && this.mRuleTree.view.selection.count == 1; case "cmdEditEdit": return isEditable && this.mPropsTree.view.selection.count == 1; } return false; }, getCommand: function getCommand(aCommand) { switch (aCommand) { case "cmdEditCopy": return new cmdEditCopy(this.mPropsView.getSelectedRowObjects()); case "cmdEditDelete": return new cmdEditDelete(this.getSelectedDec(), this.mPropsView.getSelectedRowObjects()); case "cmdEditInsert": var bundle = this.mPanel.panelset.stringBundle; var msg = bundle.getString("styleRulePropertyName.message"); var title = bundle.getString("styleRuleNewProperty.title"); var property = { value: "" }; var value = { value: "" }; var dummy = { value: false }; if (!gPromptService.prompt(window, title, msg, property, null, dummy)) { return null; } msg = bundle.getString("styleRulePropertyValue.message"); if (!gPromptService.prompt(window, title, msg, value, null, dummy)) { return null; } return new cmdEditInsert(this.getSelectedDec(), property.value, value.value, ""); case "cmdEditEdit": var rule = this.getSelectedDec(); var property = this.getSelectedProp(); var priority = rule.getPropertyPriority(property); var bundle = this.mPanel.panelset.stringBundle; var msg = bundle.getString("styleRulePropertyValue.message"); var title = bundle.getString("styleRuleEditProperty.title"); var value = { value: rule.getPropertyValue(property) }; var dummy = { value: false }; if (!gPromptService.prompt(window, title, msg, value, null, dummy)) { return null; } return new cmdEditEdit(rule, property, value.value, priority); case "cmdTogglePriority": return new cmdTogglePriority(this.getSelectedDec(), this.mPropsView.getSelectedRowObjects()); } return null; }, //////////////////////////////////////////////////////////////////////////// //// event dispatching addObserver: function(aEvent, aObserver) { this.mObsMan.addObserver(aEvent, aObserver); }, removeObserver: function(aEvent, aObserver) { this.mObsMan.removeObserver(aEvent, aObserver); }, //////////////////////////////////////////////////////////////////////////// //// UI Commands //////// rule contextual commands cmdNewRule: function() { }, cmdToggleSelectedRule: function() { }, cmdDeleteSelectedRule: function() { }, cmdOpenSelectedFileInEditor: function() { var item = this.mRuleTree.selectedItems[0]; if (item) { var path = null; var url = InsUtil.getDSProperty(this.mRuleDS, item.id, "FileURL"); if (url.substr(0, 6) == "chrome") { // This is a tricky situation. Finding the source of a chrome file means knowing where // your build tree is located, and where in the tree the file came from. Since files // from the build tree get copied into chrome:// from unpredictable places, the best way // to find them is manually. // // Solution to implement: Have the user pick the file location the first time it is opened. // After that, keep the result in some sort of registry to look up next time it is opened. // Allow this registry to be edited via preferences. } else if (url.substr(0, 4) == "file") { path = url; } if (path) { try { var exe = XPCU.createInstance("@mozilla.org/file/local;1", "nsILocalFile"); exe.initWithPath("c:\\windows\\notepad.exe"); exe = exe.nsIFile; var C = Components; var proc = C.classes['@mozilla.org/process/util;1'].createInstance (C.interfaces.nsIProcess); proc.init(exe); proc.run(false, [url], 1); } catch (ex) { alert("Unable to open editor."); } } } }, //////////////////////////////////////////////////////////////////////////// //// Uncategorized getSelectedDec: function() { var idx = this.mRuleTree.currentIndex; return this.mRuleView.getDecAt(idx); }, getSelectedProp: function() { var dec = this.getSelectedDec(); var idx = this.mPropsTree.currentIndex; return dec.item(idx); }, onRuleSelect: function() { var dec = this.getSelectedDec(); this.mPropsView = new StylePropsView(dec); this.mPropsBoxObject.view = this.mPropsView; viewer.pane.panelset.updateAllCommands(); }, onPropSelect: function() { viewer.pane.panelset.updateAllCommands(); }, onCreateRulePopup: function() { }, propOnPopupShowing: function propOnPopupShowing() { var commandset = document.getElementById("cmdsProps"); for (var i = 0; i < commandset.childNodes.length; i++) { var command = commandset.childNodes[i]; command.setAttribute("disabled", !viewer.isCommandEnabled(command.id)); } } }; //////////////////////////////////////////////////////////////////////////// //// StyleRuleView function StyleRuleView(aObject) { this.mDOMUtils = XPCU.getService("@mozilla.org/inspector/dom-utils;1", "inIDOMUtils"); if (aObject instanceof Components.interfaces.nsIDOMCSSStyleSheet) { this.mSheetRules = aObject.cssRules; } else { this.mRules = this.mDOMUtils.getCSSStyleRules(aObject); if (aObject.hasAttribute("style")) { try { this.mStyleAttribute = new XPCNativeWrapper(aObject, "style").style; } catch (ex) {} } } } StyleRuleView.prototype = new inBaseTreeView(); StyleRuleView.prototype.mSheetRules = null; StyleRuleView.prototype.mRules = null; StyleRuleView.prototype.mStyleAttribute = null; StyleRuleView.prototype.__defineGetter__("rowCount", function() { return this.mRules ? this.mRules.Count() + (this.mStyleAttribute ? 1 : 0) : this.mSheetRules ? this.mSheetRules.length : 0; }); StyleRuleView.prototype.getRuleAt = function(aRow) { if (this.mRules) { var rule = this.mRules.GetElementAt(aRow); try { return XPCU.QI(rule, "nsIDOMCSSStyleRule"); } catch (ex) { return null; } } return this.mSheetRules[aRow]; } StyleRuleView.prototype.getDecAt = function(aRow) { if (this.mRules) { if (this.mStyleAttribute && aRow + 1 == this.rowCount) { return this.mStyleAttribute; } var rule = this.mRules.GetElementAt(aRow); try { return XPCU.QI(rule, "nsIDOMCSSStyleRule").style; } catch (ex) { return null; } } return this.mSheetRules[aRow].style; } StyleRuleView.prototype.getCellText = function(aRow, aCol) { if (aRow > this.rowCount) return ""; // special case for the style attribute if (this.mStyleAttribute && aRow + 1 == this.rowCount) { if (aCol.id == "olcRule") { return 'style=""'; } if (aCol.id == "olcFileURL") { // we ought to be able to get to the URL... return ""; } if (aCol.id == "olcLine") { return ""; } return ""; } var rule = this.getRuleAt(aRow); if (!rule) return ""; if (aCol.id == "olcRule") { switch (rule.type) { case CSSRule.STYLE_RULE: return rule.selectorText; case CSSRule.IMPORT_RULE: // XXX should use .cssText here, but that asserts about some media crap return "@import url(" + rule.href + ");"; default: return rule.cssText; } } if (aCol.id == "olcFileURL") { return rule.parentStyleSheet ? rule.parentStyleSheet.href : ""; } if (aCol.id == "olcLine") { return rule.type == CSSRule.STYLE_RULE ? this.mDOMUtils.getRuleLine(rule) : ""; } return ""; } //////////////////////////////////////////////////////////////////////////// //// StylePropsView function StylePropsView(aDec) { this.mDec = aDec; } StylePropsView.prototype = new inBaseTreeView(); StylePropsView.prototype.__defineGetter__("rowCount", function() { return this.mDec ? this.mDec.length : 0; }); StylePropsView.prototype.getCellProperties = function(aRow, aCol, aProperties) { if (aCol.id == "olcPropPriority") { var prop = this.mDec.item(aRow); if (this.mDec.getPropertyPriority(prop) == "important") { aProperties.AppendElement(this.createAtom("important")); } } } StylePropsView.prototype.getCellText = function (aRow, aCol) { var prop = this.mDec.item(aRow); if (aCol.id == "olcPropName") { return prop; } else if (aCol.id == "olcPropValue") { return this.mDec.getPropertyValue(prop) } return null; } /** * Returns a CSSDeclaration for the row in the tree corresponding to the * passed index. * @param aIndex index of the row in the tree * @return a CSSDeclaration */ StylePropsView.prototype.getRowObjectFromIndex = function getRowObjectFromIndex(aIndex) { var prop = this.mDec.item(aIndex); return new CSSDeclaration(prop, this.mDec.getPropertyValue(prop), this.mDec.getPropertyPriority(prop)); } /** * Handles inserting a CSS declaration * @param aRule the rule that will contain the new declaration * @param aProperty the property of the new declaration * @param aValue the value of the new declaration * @param aPriority the priority of the new declaration ("important" or "") */ function cmdEditInsert(aRule, aProperty, aValue, aPriority) { this.rule = aRule; this.property = aProperty; this.value = aValue; this.priority = aPriority; } cmdEditInsert.prototype = { // remove this line for bug 179621, Phase Three txnType: "standard", // required for nsITransaction QueryInterface: txnQueryInterface, merge: txnMerge, isTransient: false, doTransaction: function doTransaction() { viewer.mPropsBoxObject.beginUpdateBatch(); try { this.rule.setProperty(this.property, this.value, this.priority); } finally { viewer.mPropsBoxObject.endUpdateBatch(); } }, undoTransaction: function undoTransaction() { this.rule.removeProperty(this.property); viewer.mPropsBoxObject.invalidate(); }, redoTransaction: function redoTransaction() { this.doTransaction(); } } /** * Handles deleting CSS declarations * @param aRule the rule containing the declarations * @param aDeclarations an array of CSSDeclarations to delete */ function cmdEditDelete(aRule, aDeclarations) { this.rule = aRule; this.declarations = aDeclarations; } cmdEditDelete.prototype = { // remove this line for bug 179621, Phase Three txnType: "standard", // required for nsITransaction QueryInterface: txnQueryInterface, merge: txnMerge, isTransient: false, doTransaction: function doTransaction() { viewer.mPropsBoxObject.beginUpdateBatch(); for (var i = 0; i < this.declarations.length; i++) this.rule.removeProperty(this.declarations[i].property); viewer.mPropsBoxObject.endUpdateBatch(); }, undoTransaction: function undoTransaction() { viewer.mPropsBoxObject.beginUpdateBatch(); for (var i = 0; i < this.declarations.length; i++) this.rule.setProperty(this.declarations[i].property, this.declarations[i].value, this.declarations[i].important ? "important" : ""); }, redoTransaction: function redoTransaction() { this.doTransaction(); } } /** * Handles editing CSS declarations * @param aRule the rule containing the declaration * @param aProperty the property to change * @param aNewValue the new value for the property * @param aNewValue the new priority for the property ("important" or "") */ function cmdEditEdit(aRule, aProperty, aNewValue, aNewPriority) { this.rule = aRule; this.property = aProperty; this.oldValue = aRule.getPropertyValue(aProperty); this.newValue = aNewValue; this.oldPriority = aRule.getPropertyPriority(aProperty); this.newPriority = aNewPriority; } cmdEditEdit.prototype = { // remove this line for bug 179621, Phase Three txnType: "standard", // required for nsITransaction QueryInterface: txnQueryInterface, merge: txnMerge, isTransient: false, doTransaction: function doTransaction() { this.rule.setProperty(this.property, this.newValue, this.newPriority); viewer.mPropsBoxObject.invalidate(); }, undoTransaction: function undoTransaction() { this.rule.setProperty(this.property, this.oldValue, this.oldPriority); viewer.mPropsBoxObject.invalidate(); }, redoTransaction: function redoTransaction() { this.doTransaction(); } } /** * Handles toggling CSS !important. * @param aRule the rule containing the declarations * @param aDeclarations an array of CSSDeclarations to toggle */ function cmdTogglePriority(aRule, aDeclarations) { this.rule = aRule; this.declarations = aDeclarations; } cmdTogglePriority.prototype = { // remove this line for bug 179621, Phase Three txnType: "standard", // required for nsITransaction QueryInterface: txnQueryInterface, merge: txnMerge, isTransient: false, doTransaction: function doTransaction() { for (var i = 0; i < this.declarations.length; i++) { //XXX bug 305761 means we can't make something not important, so instead // we'll delete this property and make a new one at the proper priority. // this method also sucks because the property gets moved to the bottom. var property = this.declarations[i].property; var value = this.declarations[i].value; var newPriority = this.rule.getPropertyPriority(property) == "" ? "important" : ""; this.rule.removeProperty(property); this.rule.setProperty(property, value, newPriority); } viewer.mPropsBoxObject.invalidate(); }, undoTransaction: function undoTransaction() { this.doTransaction(); }, redoTransaction: function redoTransaction() { this.doTransaction(); } }