toolkit/devtools/server/actors/styles.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial