toolkit/devtools/server/actors/styles.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const {Cc, Ci, Cu} = require("chrome");
     8 const Services = require("Services");
     9 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
    10 const protocol = require("devtools/server/protocol");
    11 const {Arg, Option, method, RetVal, types} = protocol;
    12 const events = require("sdk/event/core");
    13 const object = require("sdk/util/object");
    14 const { Class } = require("sdk/core/heritage");
    15 const { StyleSheetActor } = require("devtools/server/actors/stylesheets");
    17 loader.lazyGetter(this, "CssLogic", () => require("devtools/styleinspector/css-logic").CssLogic);
    18 loader.lazyGetter(this, "DOMUtils", () => Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils));
    20 // The PageStyle actor flattens the DOM CSS objects a little bit, merging
    21 // Rules and their Styles into one actor.  For elements (which have a style
    22 // but no associated rule) we fake a rule with the following style id.
    23 const ELEMENT_STYLE = 100;
    24 exports.ELEMENT_STYLE = ELEMENT_STYLE;
    26 const PSEUDO_ELEMENTS = [":first-line", ":first-letter", ":before", ":after", ":-moz-selection"];
    27 exports.PSEUDO_ELEMENTS = PSEUDO_ELEMENTS;
    29 // Predeclare the domnode actor type for use in requests.
    30 types.addActorType("domnode");
    32 /**
    33  * DOM Nodes returned by the style actor will be owned by the DOM walker
    34  * for the connection.
    35   */
    36 types.addLifetime("walker", "walker");
    38 /**
    39  * When asking for the styles applied to a node, we return a list of
    40  * appliedstyle json objects that lists the rules that apply to the node
    41  * and which element they were inherited from (if any).
    42  */
    43 types.addDictType("appliedstyle", {
    44   rule: "domstylerule#actorid",
    45   inherited: "nullable:domnode#actorid"
    46 });
    48 types.addDictType("matchedselector", {
    49   rule: "domstylerule#actorid",
    50   selector: "string",
    51   value: "string",
    52   status: "number"
    53 });
    55 /**
    56  * The PageStyle actor lets the client look at the styles on a page, as
    57  * they are applied to a given node.
    58  */
    59 var PageStyleActor = protocol.ActorClass({
    60   typeName: "pagestyle",
    62   /**
    63    * Create a PageStyleActor.
    64    *
    65    * @param inspector
    66    *    The InspectorActor that owns this PageStyleActor.
    67    *
    68    * @constructor
    69    */
    70   initialize: function(inspector) {
    71     protocol.Actor.prototype.initialize.call(this, null);
    72     this.inspector = inspector;
    73     if (!this.inspector.walker) {
    74       throw Error("The inspector's WalkerActor must be created before " +
    75                    "creating a PageStyleActor.");
    76     }
    77     this.walker = inspector.walker;
    78     this.cssLogic = new CssLogic;
    80     // Stores the association of DOM objects -> actors
    81     this.refMap = new Map;
    82   },
    84   get conn() this.inspector.conn,
    86   /**
    87    * Return or create a StyleRuleActor for the given item.
    88    * @param item Either a CSSStyleRule or a DOM element.
    89    */
    90   _styleRef: function(item) {
    91     if (this.refMap.has(item)) {
    92       return this.refMap.get(item);
    93     }
    94     let actor = StyleRuleActor(this, item);
    95     this.manage(actor);
    96     this.refMap.set(item, actor);
    98     return actor;
    99   },
   101   /**
   102    * Return or create a StyleSheetActor for the given
   103    * nsIDOMCSSStyleSheet
   104    */
   105   _sheetRef: function(sheet) {
   106     if (this.refMap.has(sheet)) {
   107       return this.refMap.get(sheet);
   108     }
   109     let actor = new StyleSheetActor(sheet, this, this.walker.rootWin);
   110     this.manage(actor);
   111     this.refMap.set(sheet, actor);
   113     return actor;
   114   },
   116   /**
   117    * Get the computed style for a node.
   118    *
   119    * @param NodeActor node
   120    * @param object options
   121    *   `filter`: A string filter that affects the "matched" handling.
   122    *     'user': Include properties from user style sheets.
   123    *     'ua': Include properties from user and user-agent sheets.
   124    *     Default value is 'ua'
   125    *   `markMatched`: true if you want the 'matched' property to be added
   126    *     when a computed property has been modified by a style included
   127    *     by `filter`.
   128    *   `onlyMatched`: true if unmatched properties shouldn't be included.
   129    *
   130    * @returns a JSON blob with the following form:
   131    *   {
   132    *     "property-name": {
   133    *       value: "property-value",
   134    *       priority: "!important" <optional>
   135    *       matched: <true if there are matched selectors for this value>
   136    *     },
   137    *     ...
   138    *   }
   139    */
   140   getComputed: method(function(node, options) {
   141     let ret = Object.create(null);
   143     this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
   144     this.cssLogic.highlight(node.rawNode);
   145     let computed = this.cssLogic._computedStyle || [];
   147     Array.prototype.forEach.call(computed, name => {
   148       let matched = undefined;
   149       ret[name] = {
   150         value: computed.getPropertyValue(name),
   151         priority: computed.getPropertyPriority(name) || undefined
   152       };
   153     });
   155     if (options.markMatched || options.onlyMatched) {
   156       let matched = this.cssLogic.hasMatchedSelectors(Object.keys(ret));
   157       for (let key in ret) {
   158         if (matched[key]) {
   159           ret[key].matched = options.markMatched ? true : undefined
   160         } else if (options.onlyMatched) {
   161           delete ret[key];
   162         }
   163       }
   164     }
   166     return ret;
   167   }, {
   168     request: {
   169       node: Arg(0, "domnode"),
   170       markMatched: Option(1, "boolean"),
   171       onlyMatched: Option(1, "boolean"),
   172       filter: Option(1, "string"),
   173     },
   174     response: {
   175       computed: RetVal("json")
   176     }
   177   }),
   179   /**
   180    * Get a list of selectors that match a given property for a node.
   181    *
   182    * @param NodeActor node
   183    * @param string property
   184    * @param object options
   185    *   `filter`: A string filter that affects the "matched" handling.
   186    *     'user': Include properties from user style sheets.
   187    *     'ua': Include properties from user and user-agent sheets.
   188    *     Default value is 'ua'
   189    *
   190    * @returns a JSON object with the following form:
   191    *   {
   192    *     // An ordered list of rules that apply
   193    *     matched: [{
   194    *       rule: <rule actorid>,
   195    *       sourceText: <string>, // The source of the selector, relative
   196    *                             // to the node in question.
   197    *       selector: <string>, // the selector ID that matched
   198    *       value: <string>, // the value of the property
   199    *       status: <int>,
   200    *         // The status of the match - high numbers are better placed
   201    *         // to provide styling information:
   202    *         // 3: Best match, was used.
   203    *         // 2: Matched, but was overridden.
   204    *         // 1: Rule from a parent matched.
   205    *         // 0: Unmatched (never returned in this API)
   206    *     }, ...],
   207    *
   208    *     // The full form of any domrule referenced.
   209    *     rules: [ <domrule>, ... ], // The full form of any domrule referenced
   210    *
   211    *     // The full form of any sheets referenced.
   212    *     sheets: [ <domsheet>, ... ]
   213    *  }
   214    */
   215   getMatchedSelectors: method(function(node, property, options) {
   216     this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
   217     this.cssLogic.highlight(node.rawNode);
   219     let walker = node.parent();
   221     let rules = new Set;
   222     let sheets = new Set;
   224     let matched = [];
   225     let propInfo = this.cssLogic.getPropertyInfo(property);
   226     for (let selectorInfo of propInfo.matchedSelectors) {
   227       let cssRule = selectorInfo.selector.cssRule;
   228       let domRule = cssRule.sourceElement || cssRule.domRule;
   230       let rule = this._styleRef(domRule);
   231       rules.add(rule);
   233       matched.push({
   234         rule: rule,
   235         sourceText: this.getSelectorSource(selectorInfo, node.rawNode),
   236         selector: selectorInfo.selector.text,
   237         name: selectorInfo.property,
   238         value: selectorInfo.value,
   239         status: selectorInfo.status
   240       });
   241     }
   243     this.expandSets(rules, sheets);
   245     return {
   246       matched: matched,
   247       rules: [...rules],
   248       sheets: [...sheets],
   249     };
   250   }, {
   251     request: {
   252       node: Arg(0, "domnode"),
   253       property: Arg(1, "string"),
   254       filter: Option(2, "string")
   255     },
   256     response: RetVal(types.addDictType("matchedselectorresponse", {
   257       rules: "array:domstylerule",
   258       sheets: "array:stylesheet",
   259       matched: "array:matchedselector"
   260     }))
   261   }),
   263   // Get a selector source for a CssSelectorInfo relative to a given
   264   // node.
   265   getSelectorSource: function(selectorInfo, relativeTo) {
   266     let result = selectorInfo.selector.text;
   267     if (selectorInfo.elementStyle) {
   268       let source = selectorInfo.sourceElement;
   269       if (source === relativeTo) {
   270         result = "this";
   271       } else {
   272         result = CssLogic.getShortName(source);
   273       }
   274       result += ".style"
   275     }
   276     return result;
   277   },
   279   /**
   280    * Get the set of styles that apply to a given node.
   281    * @param NodeActor node
   282    * @param string property
   283    * @param object options
   284    *   `filter`: A string filter that affects the "matched" handling.
   285    *     'user': Include properties from user style sheets.
   286    *     'ua': Include properties from user and user-agent sheets.
   287    *     Default value is 'ua'
   288    *   `inherited`: Include styles inherited from parent nodes.
   289    *   `matchedSeletors`: Include an array of specific selectors that
   290    *     caused this rule to match its node.
   291    */
   292   getApplied: method(function(node, options) {
   293     let entries = [];
   295     this.addElementRules(node.rawNode, undefined, options, entries);
   297     if (options.inherited) {
   298       let parent = this.walker.parentNode(node);
   299       while (parent && parent.rawNode.nodeType != Ci.nsIDOMNode.DOCUMENT_NODE) {
   300         this.addElementRules(parent.rawNode, parent, options, entries);
   301         parent = this.walker.parentNode(parent);
   302       }
   303     }
   305     if (options.matchedSelectors) {
   306       for (let entry of entries) {
   307         if (entry.rule.type === ELEMENT_STYLE) {
   308           continue;
   309         }
   311         let domRule = entry.rule.rawRule;
   312         let selectors = CssLogic.getSelectors(domRule);
   313         let element = entry.inherited ? entry.inherited.rawNode : node.rawNode;
   314         entry.matchedSelectors = [];
   315         for (let i = 0; i < selectors.length; i++) {
   316           if (DOMUtils.selectorMatchesElement(element, domRule, i)) {
   317             entry.matchedSelectors.push(selectors[i]);
   318           }
   319         }
   321       }
   322     }
   324     let rules = new Set;
   325     let sheets = new Set;
   326     entries.forEach(entry => rules.add(entry.rule));
   327     this.expandSets(rules, sheets);
   329     return {
   330       entries: entries,
   331       rules: [...rules],
   332       sheets: [...sheets]
   333     }
   334   }, {
   335     request: {
   336       node: Arg(0, "domnode"),
   337       inherited: Option(1, "boolean"),
   338       matchedSelectors: Option(1, "boolean"),
   339       filter: Option(1, "string")
   340     },
   341     response: RetVal(types.addDictType("appliedStylesReturn", {
   342       entries: "array:appliedstyle",
   343       rules: "array:domstylerule",
   344       sheets: "array:stylesheet"
   345     }))
   346   }),
   348   _hasInheritedProps: function(style) {
   349     return Array.prototype.some.call(style, prop => {
   350       return DOMUtils.isInheritedProperty(prop);
   351     });
   352   },
   354   /**
   355    * Helper function for getApplied, adds all the rules from a given
   356    * element.
   357    */
   358   addElementRules: function(element, inherited, options, rules)
   359   {
   360     if (!element.style) {
   361       return;
   362     }
   364     let elementStyle = this._styleRef(element);
   366     if (!inherited || this._hasInheritedProps(element.style)) {
   367       rules.push({
   368         rule: elementStyle,
   369         inherited: inherited,
   370       });
   371     }
   373     let pseudoElements = inherited ? [null] : [null, ...PSEUDO_ELEMENTS];
   374     for (let pseudo of pseudoElements) {
   376       // Get the styles that apply to the element.
   377       let domRules = DOMUtils.getCSSStyleRules(element, pseudo);
   379       if (!domRules) {
   380         continue;
   381       }
   383       // getCSSStyleRules returns ordered from least-specific to
   384       // most-specific.
   385       for (let i = domRules.Count() - 1; i >= 0; i--) {
   386         let domRule = domRules.GetElementAt(i);
   388         let isSystem = !CssLogic.isContentStylesheet(domRule.parentStyleSheet);
   390         if (isSystem && options.filter != CssLogic.FILTER.UA) {
   391           continue;
   392         }
   394         if (inherited) {
   395           // Don't include inherited rules if none of its properties
   396           // are inheritable.
   397           let hasInherited = Array.prototype.some.call(domRule.style, prop => {
   398             return DOMUtils.isInheritedProperty(prop);
   399           });
   400           if (!hasInherited) {
   401             continue;
   402           }
   403         }
   405         let ruleActor = this._styleRef(domRule);
   406         rules.push({
   407           rule: ruleActor,
   408           inherited: inherited,
   409           pseudoElement: pseudo
   410         });
   411       }
   413     }
   414   },
   416   /**
   417    * Expand Sets of rules and sheets to include all parent rules and sheets.
   418    */
   419   expandSets: function(ruleSet, sheetSet) {
   420     // Sets include new items in their iteration
   421     for (let rule of ruleSet) {
   422       if (rule.rawRule.parentRule) {
   423         let parent = this._styleRef(rule.rawRule.parentRule);
   424         if (!ruleSet.has(parent)) {
   425           ruleSet.add(parent);
   426         }
   427       }
   428       if (rule.rawRule.parentStyleSheet) {
   429         let parent = this._sheetRef(rule.rawRule.parentStyleSheet);
   430         if (!sheetSet.has(parent)) {
   431           sheetSet.add(parent);
   432         }
   433       }
   434     }
   436     for (let sheet of sheetSet) {
   437       if (sheet.rawSheet.parentStyleSheet) {
   438         let parent = this._sheetRef(sheet.rawSheet.parentStyleSheet);
   439         if (!sheetSet.has(parent)) {
   440           sheetSet.add(parent);
   441         }
   442       }
   443     }
   444   },
   446   getLayout: method(function(node, options) {
   447     this.cssLogic.highlight(node.rawNode);
   449     let layout = {};
   451     // First, we update the first part of the layout view, with
   452     // the size of the element.
   454     let clientRect = node.rawNode.getBoundingClientRect();
   455     layout.width = Math.round(clientRect.width);
   456     layout.height = Math.round(clientRect.height);
   458     // We compute and update the values of margins & co.
   459     let style = node.rawNode.ownerDocument.defaultView.getComputedStyle(node.rawNode);
   460     for (let prop of [
   461       "position",
   462       "margin-top",
   463       "margin-right",
   464       "margin-bottom",
   465       "margin-left",
   466       "padding-top",
   467       "padding-right",
   468       "padding-bottom",
   469       "padding-left",
   470       "border-top-width",
   471       "border-right-width",
   472       "border-bottom-width",
   473       "border-left-width"
   474     ]) {
   475       layout[prop] = style.getPropertyValue(prop);
   476     }
   478     if (options.autoMargins) {
   479       layout.autoMargins = this.processMargins(this.cssLogic);
   480     }
   482     for (let i in this.map) {
   483       let property = this.map[i].property;
   484       this.map[i].value = parseInt(style.getPropertyValue(property));
   485     }
   488     if (options.margins) {
   489       layout.margins = this.processMargins(cssLogic);
   490     }
   492     return layout;
   493   }, {
   494     request: {
   495       node: Arg(0, "domnode"),
   496       autoMargins: Option(1, "boolean")
   497     },
   498     response: RetVal("json")
   499   }),
   501   /**
   502    * Find 'auto' margin properties.
   503    */
   504   processMargins: function(cssLogic) {
   505     let margins = {};
   507     for (let prop of ["top", "bottom", "left", "right"]) {
   508       let info = cssLogic.getPropertyInfo("margin-" + prop);
   509       let selectors = info.matchedSelectors;
   510       if (selectors && selectors.length > 0 && selectors[0].value == "auto") {
   511         margins[prop] = "auto";
   512       }
   513     }
   515     return margins;
   516   },
   518 });
   519 exports.PageStyleActor = PageStyleActor;
   521 /**
   522  * Front object for the PageStyleActor
   523  */
   524 var PageStyleFront = protocol.FrontClass(PageStyleActor, {
   525   initialize: function(conn, form, ctx, detail) {
   526     protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
   527     this.inspector = this.parent();
   528   },
   530   destroy: function() {
   531     protocol.Front.prototype.destroy.call(this);
   532   },
   534   get walker() {
   535     return this.inspector.walker;
   536   },
   538   getMatchedSelectors: protocol.custom(function(node, property, options) {
   539     return this._getMatchedSelectors(node, property, options).then(ret => {
   540       return ret.matched;
   541     });
   542   }, {
   543     impl: "_getMatchedSelectors"
   544   }),
   546   getApplied: protocol.custom(function(node, options={}) {
   547     return this._getApplied(node, options).then(ret => {
   548       return ret.entries;
   549     });
   550   }, {
   551     impl: "_getApplied"
   552   })
   553 });
   555 // Predeclare the domstylerule actor type
   556 types.addActorType("domstylerule");
   558 /**
   559  * An actor that represents a CSS style object on the protocol.
   560  *
   561  * We slightly flatten the CSSOM for this actor, it represents
   562  * both the CSSRule and CSSStyle objects in one actor.  For nodes
   563  * (which have a CSSStyle but no CSSRule) we create a StyleRuleActor
   564  * with a special rule type (100).
   565  */
   566 var StyleRuleActor = protocol.ActorClass({
   567   typeName: "domstylerule",
   568   initialize: function(pageStyle, item) {
   569     protocol.Actor.prototype.initialize.call(this, null);
   570     this.pageStyle = pageStyle;
   571     this.rawStyle = item.style;
   573     if (item instanceof (Ci.nsIDOMCSSRule)) {
   574       this.type = item.type;
   575       this.rawRule = item;
   576       if (this.rawRule instanceof Ci.nsIDOMCSSStyleRule && this.rawRule.parentStyleSheet) {
   577         this.line = DOMUtils.getRuleLine(this.rawRule);
   578         this.column = DOMUtils.getRuleColumn(this.rawRule);
   579       }
   580     } else {
   581       // Fake a rule
   582       this.type = ELEMENT_STYLE;
   583       this.rawNode = item;
   584       this.rawRule = {
   585         style: item.style,
   586         toString: function() "[element rule " + this.style + "]"
   587       }
   588     }
   589   },
   591   get conn() this.pageStyle.conn,
   593   // Objects returned by this actor are owned by the PageStyleActor
   594   // to which this rule belongs.
   595   get marshallPool() this.pageStyle,
   597   toString: function() "[StyleRuleActor for " + this.rawRule + "]",
   599   form: function(detail) {
   600     if (detail === "actorid") {
   601       return this.actorID;
   602     }
   604     let form = {
   605       actor: this.actorID,
   606       type: this.type,
   607       line: this.line || undefined,
   608       column: this.column
   609     };
   611     if (this.rawRule.parentRule) {
   612       form.parentRule = this.pageStyle._styleRef(this.rawRule.parentRule).actorID;
   613     }
   614     if (this.rawRule.parentStyleSheet) {
   615       form.parentStyleSheet = this.pageStyle._sheetRef(this.rawRule.parentStyleSheet).actorID;
   616     }
   618     switch (this.type) {
   619       case Ci.nsIDOMCSSRule.STYLE_RULE:
   620         form.selectors = CssLogic.getSelectors(this.rawRule);
   621         form.cssText = this.rawStyle.cssText || "";
   622         break;
   623       case ELEMENT_STYLE:
   624         // Elements don't have a parent stylesheet, and therefore
   625         // don't have an associated URI.  Provide a URI for
   626         // those.
   627         form.href = this.rawNode.ownerDocument.location.href;
   628         form.cssText = this.rawStyle.cssText || "";
   629         break;
   630       case Ci.nsIDOMCSSRule.CHARSET_RULE:
   631         form.encoding = this.rawRule.encoding;
   632         break;
   633       case Ci.nsIDOMCSSRule.IMPORT_RULE:
   634         form.href = this.rawRule.href;
   635         break;
   636       case Ci.nsIDOMCSSRule.MEDIA_RULE:
   637         form.media = [];
   638         for (let i = 0, n = this.rawRule.media.length; i < n; i++) {
   639           form.media.push(this.rawRule.media.item(i));
   640         }
   641         break;
   642     }
   644     return form;
   645   },
   647   /**
   648    * Modify a rule's properties.  Passed an array of modifications:
   649    * {
   650    *   type: "set",
   651    *   name: <string>,
   652    *   value: <string>,
   653    *   priority: <optional string>
   654    * }
   655    *  or
   656    * {
   657    *   type: "remove",
   658    *   name: <string>,
   659    * }
   660    *
   661    * @returns the rule with updated properties
   662    */
   663   modifyProperties: method(function(modifications) {
   664     let validProps = new Map();
   666     // Use a fresh element for each call to this function to prevent side effects
   667     // that pop up based on property values that were already set on the element.
   669     let document;
   670     if (this.rawNode) {
   671       document = this.rawNode.ownerDocument;
   672     } else {
   673       let parentStyleSheet = this.rawRule.parentStyleSheet;
   674       while (parentStyleSheet.ownerRule &&
   675           parentStyleSheet.ownerRule instanceof Ci.nsIDOMCSSImportRule) {
   676         parentStyleSheet = parentStyleSheet.ownerRule.parentStyleSheet;
   677       }
   679       if (parentStyleSheet.ownerNode instanceof Ci.nsIDOMHTMLDocument) {
   680         document = parentStyleSheet.ownerNode;
   681       } else {
   682         document = parentStyleSheet.ownerNode.ownerDocument;
   683       }
   684     }
   686     let tempElement = document.createElement("div");
   688     for (let mod of modifications) {
   689       if (mod.type === "set") {
   690         tempElement.style.setProperty(mod.name, mod.value, mod.priority || "");
   691         this.rawStyle.setProperty(mod.name,
   692           tempElement.style.getPropertyValue(mod.name), mod.priority || "");
   693       } else if (mod.type === "remove") {
   694         this.rawStyle.removeProperty(mod.name);
   695       }
   696     }
   698     return this;
   699   }, {
   700     request: { modifications: Arg(0, "array:json") },
   701     response: { rule: RetVal("domstylerule") }
   702   })
   703 });
   705 /**
   706  * Front for the StyleRule actor.
   707  */
   708 var StyleRuleFront = protocol.FrontClass(StyleRuleActor, {
   709   initialize: function(client, form, ctx, detail) {
   710     protocol.Front.prototype.initialize.call(this, client, form, ctx, detail);
   711   },
   713   destroy: function() {
   714     protocol.Front.prototype.destroy.call(this);
   715   },
   717   form: function(form, detail) {
   718     if (detail === "actorid") {
   719       this.actorID = form;
   720       return;
   721     }
   722     this.actorID = form.actor;
   723     this._form = form;
   724     if (this._mediaText) {
   725       this._mediaText = null;
   726     }
   727   },
   729   /**
   730    * Return a new RuleModificationList for this node.
   731    */
   732   startModifyingProperties: function() {
   733   return new RuleModificationList(this);
   734   },
   736   get type() this._form.type,
   737   get line() this._form.line || -1,
   738   get column() this._form.column || -1,
   739   get cssText() {
   740     return this._form.cssText;
   741   },
   742   get selectors() {
   743     return this._form.selectors;
   744   },
   745   get media() {
   746     return this._form.media;
   747   },
   748   get mediaText() {
   749     if (!this._form.media) {
   750       return null;
   751     }
   752     if (this._mediaText) {
   753       return this._mediaText;
   754     }
   755     this._mediaText = this.media.join(", ");
   756     return this._mediaText;
   757   },
   759   get parentRule() {
   760     return this.conn.getActor(this._form.parentRule);
   761   },
   763   get parentStyleSheet() {
   764     return this.conn.getActor(this._form.parentStyleSheet);
   765   },
   767   get element() {
   768     return this.conn.getActor(this._form.element);
   769   },
   771   get href() {
   772     if (this._form.href) {
   773       return this._form.href;
   774     }
   775     let sheet = this.parentStyleSheet;
   776     return sheet.href;
   777   },
   779   get nodeHref() {
   780     let sheet = this.parentStyleSheet;
   781     return sheet ? sheet.nodeHref : "";
   782   },
   784   get location()
   785   {
   786     return {
   787       href: this.href,
   788       line: this.line,
   789       column: this.column
   790     };
   791   },
   793   getOriginalLocation: function()
   794   {
   795     if (this._originalLocation) {
   796       return promise.resolve(this._originalLocation);
   797     }
   799     let parentSheet = this.parentStyleSheet;
   800     if (!parentSheet) {
   801       return promise.resolve(this.location);
   802     }
   803     return parentSheet.getOriginalLocation(this.line, this.column)
   804       .then(({ source, line, column }) => {
   805         let location = {
   806           href: source,
   807           line: line,
   808           column: column
   809         }
   810         if (!source) {
   811           location.href = this.href;
   812         }
   813         this._originalLocation = location;
   814         return location;
   815       })
   816   },
   818   // Only used for testing, please keep it that way.
   819   _rawStyle: function() {
   820     if (!this.conn._transport._serverConnection) {
   821       console.warn("Tried to use rawNode on a remote connection.");
   822       return null;
   823     }
   824     let actor = this.conn._transport._serverConnection.getActor(this.actorID);
   825     if (!actor) {
   826       return null;
   827     }
   828     return actor.rawStyle;
   829   }
   830 });
   832 /**
   833  * Convenience API for building a list of attribute modifications
   834  * for the `modifyAttributes` request.
   835  */
   836 var RuleModificationList = Class({
   837   initialize: function(rule) {
   838     this.rule = rule;
   839     this.modifications = [];
   840   },
   842   apply: function() {
   843     return this.rule.modifyProperties(this.modifications);
   844   },
   845   setProperty: function(name, value, priority) {
   846     this.modifications.push({
   847       type: "set",
   848       name: name,
   849       value: value,
   850       priority: priority
   851     });
   852   },
   853   removeProperty: function(name) {
   854     this.modifications.push({
   855       type: "remove",
   856       name: name
   857     });
   858   }
   859 });

mercurial