Bug 1268466 - ruleview: create new rule <style> el in frame document;r=tromey draft
authorJulian Descottes <jdescottes@mozilla.com>
Sat, 30 Apr 2016 16:41:54 +0200
changeset 358375 48e989e229b7972101c2761c881127244530273d
parent 357831 3e2e4184eeffe605002d06eca912050e88ec3e04
child 519840 b389e9abe91c240342011e3233ef2ac187f373d4
push id16993
push userjdescottes@mozilla.com
push dateMon, 02 May 2016 17:53:56 +0000
reviewerstromey
bugs1268466
milestone49.0a1
Bug 1268466 - ruleview: create new rule <style> el in frame document;r=tromey To create new rules, style elements are added to the content document. For nodes located in an iframe, the style element should be added to the ownerDocument of this particular node. MozReview-Commit-ID: 7ynd7YU3WeL
devtools/client/inspector/rules/test/browser.ini
devtools/client/inspector/rules/test/browser_rules_add-rule_iframes.js
devtools/server/actors/styles.js
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -48,16 +48,17 @@ support-files =
 [browser_rules_add-property_02.js]
 [browser_rules_add-property-svg.js]
 [browser_rules_add-rule_01.js]
 [browser_rules_add-rule_02.js]
 [browser_rules_add-rule_03.js]
 [browser_rules_add-rule_04.js]
 [browser_rules_add-rule_05.js]
 [browser_rules_add-rule_pseudo_class.js]
+[browser_rules_add-rule_iframes.js]
 [browser_rules_authored.js]
 [browser_rules_authored_color.js]
 [browser_rules_authored_override.js]
 [browser_rules_blob_stylesheet.js]
 [browser_rules_colorpicker-and-image-tooltip_01.js]
 [browser_rules_colorpicker-and-image-tooltip_02.js]
 [browser_rules_colorpicker-appears-on-swatch-click.js]
 [browser_rules_colorpicker-commit-on-ENTER.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule_iframes.js
@@ -0,0 +1,85 @@
+/* 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/ */
+
+"use strict";
+
+// Tests adding a rule on elements nested in iframes.
+
+const TEST_URI =
+ `<div>outer</div>
+  <iframe id="frame1" src="data:text/html;charset=utf-8,<div>inner1</div>">
+  </iframe>
+  <iframe id="frame2" src="data:text/html;charset=utf-8,<div>inner2</div>">
+  </iframe>`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  yield selectNode("div", inspector);
+  yield addNewRule(inspector, view);
+  yield testNewRule(view, "div", 1);
+  yield addNewProperty(view, 1, "color", "red");
+
+  let innerFrameDiv1 = yield getNodeFrontInFrame("div", "#frame1", inspector);
+  yield selectNode(innerFrameDiv1, inspector);
+  yield addNewRule(inspector, view);
+  yield testNewRule(view, "div", 1);
+  yield addNewProperty(view, 1, "color", "blue");
+
+  let innerFrameDiv2 = yield getNodeFrontInFrame("div", "#frame2", inspector);
+  yield selectNode(innerFrameDiv2, inspector);
+  yield addNewRule(inspector, view);
+  yield testNewRule(view, "div", 1);
+  yield addNewProperty(view, 1, "color", "green");
+});
+
+function* addNewRule(inspector, view) {
+  info("Adding the new rule using the button");
+  view.addRuleButton.click();
+  info("Waiting for rule view to change");
+  let onRuleViewChanged = once(view, "ruleview-changed");
+  yield onRuleViewChanged;
+}
+
+/**
+ * Check the newly created rule has the expected selector and submit the
+ * selector editor.
+ */
+function* testNewRule(view, expected, index) {
+  let idRuleEditor = getRuleViewRuleEditor(view, index);
+  let editor = idRuleEditor.selectorText.ownerDocument.activeElement;
+  is(editor.value, expected,
+      "Selector editor value is as expected: " + expected);
+
+  info("Entering the escape key");
+  EventUtils.synthesizeKey("VK_ESCAPE", {});
+
+  is(idRuleEditor.selectorText.textContent, expected,
+      "Selector text value is as expected: " + expected);
+}
+
+/**
+ * Add a new property in the rule at the provided index in the rule view.
+ *
+ * @param {RuleView} view
+ * @param {Number} index
+ *        The index of the rule in which we should add a new property.
+ * @param {String} name
+ *        The name of the new property.
+ * @param {String} value
+ *        The value of the new property.
+ */
+function* addNewProperty(view, index, name, value) {
+  let idRuleEditor = getRuleViewRuleEditor(view, index);
+  info(`Adding new property "${name}: ${value};"`);
+
+  let onRuleViewChanged = view.once("ruleview-changed");
+  idRuleEditor.addProperty(name, value, "");
+  yield onRuleViewChanged;
+
+  let textProps = idRuleEditor.rule.textProps;
+  let lastProperty = textProps[textProps.length - 1];
+  is(lastProperty.name, name, "Last property has the expected name");
+  is(lastProperty.value, value, "Last property has the expected value");
+}
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -147,16 +147,19 @@ var PageStyleActor = protocol.ActorClass
                    "creating a PageStyleActor.");
     }
     this.walker = inspector.walker;
     this.cssLogic = new CssLogic();
 
     // Stores the association of DOM objects -> actors
     this.refMap = new Map();
 
+    // Maps document elements to style elements, used to add new rules.
+    this.styleElements = new WeakMap();
+
     this.onFrameUnload = this.onFrameUnload.bind(this);
     events.on(this.inspector.tabActor, "will-navigate", this.onFrameUnload);
 
     this._styleApplied = this._styleApplied.bind(this);
     this._watchedSheets = new Set();
   },
 
   destroy: function() {
@@ -164,17 +167,17 @@ var PageStyleActor = protocol.ActorClass
       return;
     }
     protocol.Actor.prototype.destroy.call(this);
     events.off(this.inspector.tabActor, "will-navigate", this.onFrameUnload);
     this.inspector = null;
     this.walker = null;
     this.refMap = null;
     this.cssLogic = null;
-    this._styleElement = null;
+    this.styleElements = null;
 
     for (let sheet of this._watchedSheets) {
       sheet.off("style-applied", this._styleApplied);
     }
     this._watchedSheets.clear();
   },
 
   get conn() {
@@ -966,33 +969,36 @@ var PageStyleActor = protocol.ActorClass
 
     return margins;
   },
 
   /**
    * On page navigation, tidy up remaining objects.
    */
   onFrameUnload: function() {
-    this._styleElement = null;
+    this.styleElements = new WeakMap();
   },
 
   /**
-   * Helper function to addNewRule to construct a new style tag in the document.
+   * Helper function to addNewRule to get or create a style tag in the provided
+   * document.
+   *
+   * @param {Document} document
+   *        The document in which the style element should be appended.
    * @returns DOMElement of the style tag
    */
-  get styleElement() {
-    if (!this._styleElement) {
-      let document = this.inspector.window.document;
+  getStyleElement: function(document) {
+    if (!this.styleElements.has(document)) {
       let style = document.createElementNS(XHTML_NS, "style");
       style.setAttribute("type", "text/css");
       document.documentElement.appendChild(style);
-      this._styleElement = style;
+      this.styleElements.set(document, style);
     }
 
-    return this._styleElement;
+    return this.styleElements.get(document);
   },
 
   /**
    * Helper function for adding a new rule and getting its applied style
    * properties
    * @param NodeActor node
    * @param CSSStyleRule rule
    * @returns Object containing its applied style properties
@@ -1011,17 +1017,17 @@ var PageStyleActor = protocol.ActorClass
    * @param {Boolean} editAuthored
    *        True if the selector should be updated by editing the
    *        authored text; false if the selector should be updated via
    *        CSSOM.
    * @returns {StyleRuleActor} the new rule
    */
   addNewRule: method(Task.async(function* (node, pseudoClasses,
                                           editAuthored = false) {
-    let style = this.styleElement;
+    let style = this.getStyleElement(node.rawNode.ownerDocument);
     let sheet = style.sheet;
     let cssRules = sheet.cssRules;
     let rawNode = node.rawNode;
 
     let selector;
     if (rawNode.id) {
       selector = "#" + CSS.escape(rawNode.id);
     } else if (rawNode.className) {