Merge m-c to fx-team
authorPanos Astithas <past@mozilla.com>
Thu, 01 Nov 2012 09:30:11 +0200
changeset 111905 5bd5bb168eb1d25d43ad61dde8fed56d2a4346a9
parent 111889 c6ccd1d30c1541f4b855655aeb7f6c193bdb74b7 (current diff)
parent 111904 ea6521dc8ee96ce2c316afa66827c7f0b59307f4 (diff)
child 111906 482d32e2b34843b4185ff77235bf5d3e8dcac89e
child 112100 aeb34ce13c59fe293080f804bd60ab7497136241
push id23781
push userpastithas@mozilla.com
push dateThu, 01 Nov 2012 07:34:43 +0000
treeherdermozilla-central@5bd5bb168eb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone19.0a1
first release with
nightly linux32
5bd5bb168eb1 / 19.0a1 / 20121101030705 / files
nightly linux64
5bd5bb168eb1 / 19.0a1 / 20121101030705 / files
nightly mac
5bd5bb168eb1 / 19.0a1 / 20121101030705 / files
nightly win32
5bd5bb168eb1 / 19.0a1 / 20121101030705 / files
nightly win64
5bd5bb168eb1 / 19.0a1 / 20121101030705 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team
b2g/chrome/content/settings.js
browser/devtools/markupview/MarkupView.jsm
browser/devtools/styleinspector/CssHtmlTree.jsm
browser/devtools/styleinspector/CssRuleView.jsm
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -148,18 +148,20 @@ Components.utils.import('resource://gre/
     cutils.close();
   } catch(e) {
     //Error
   }
   lock.set('deviceinfo.hardware', hardware_version, null, null);
 })();
 
 // =================== Debugger ====================
-SettingsListener.observe('devtools.debugger.remote-enabled', false, function(enabled) {
+SettingsListener.observe('devtools.debugger.remote-enabled', false, function(value) {
   Services.prefs.setBoolPref('devtools.debugger.remote-enabled', value);
+  // This preference is consulted during startup
+  Services.prefs.savePrefFile(null);
 });
 
 SettingsListener.observe('devtools.debugger.log', false, function(value) {
   Services.prefs.setBoolPref('devtools.debugger.log', value);
 });
 
 SettingsListener.observe('devtools.debugger.remote-port', 6000, function(value) {
   Services.prefs.setIntPref('devtools.debugger.remote-port', value);
--- a/browser/devtools/markupview/MarkupView.jsm
+++ b/browser/devtools/markupview/MarkupView.jsm
@@ -862,17 +862,21 @@ function ElementEditor(aContainer, aNode
     element: this.newAttr,
     trigger: "dblclick",
     stopOnReturn: true,
     done: function EE_onNew(aVal, aCommit) {
       if (!aCommit) {
         return;
       }
 
-      this._applyAttributes(aVal);
+      try {
+        this._applyAttributes(aVal);
+      } catch (x) {
+        return;
+      }
     }.bind(this)
   });
 
   let tagName = this.node.nodeName.toLowerCase();
   this.tag.textContent = tagName;
   this.closeTag.textContent = tagName;
 
   this.update();
@@ -957,21 +961,26 @@ ElementEditor.prototype = {
         done: function EE_editAttribute_done(aVal, aCommit) {
           if (!aCommit) {
             return;
           }
 
           this.undo.startBatch();
 
           // Remove the attribute stored in this editor and re-add any attributes
-          // parsed out of the input element.
-          this._removeAttribute(this.node, aAttr.name)
-          this._applyAttributes(aVal, attr);
-
-          this.undo.endBatch();
+          // parsed out of the input element. Restore original attribute if
+          // parsing fails.
+          this._removeAttribute(this.node, aAttr.name);
+          try {
+            this._applyAttributes(aVal, attr);
+            this.undo.endBatch();
+          } catch (e) {
+            this.undo.endBatch();
+            this.undo.undo();
+          }
         }.bind(this)
       });
 
       this.attrs[aAttr.name] = attr;
     }
 
     name.textContent = aAttr.name;
     val.textContent = aAttr.value;
@@ -982,25 +991,27 @@ ElementEditor.prototype = {
   /**
    * Parse a user-entered attribute string and apply the resulting
    * attributes to the node.  This operation is undoable.
    *
    * @param string aValue the user-entered value.
    * @param Element aAttrNode the attribute editor that created this
    *        set of attributes, used to place new attributes where the
    *        user put them.
+   * @throws SYNTAX_ERR if aValue is not well-formed.
    */
   _applyAttributes: function EE__applyAttributes(aValue, aAttrNode)
   {
     // Create a dummy node for parsing the attribute list.
     let dummyNode = this.doc.createElement("div");
 
     let parseTag = (this.node.namespaceURI.match(/svg/i) ? "svg" :
                    (this.node.namespaceURI.match(/mathml/i) ? "math" : "div"));
     let parseText = "<" + parseTag + " " + aValue + "/>";
+    // Throws exception if parseText is not well-formed.
     dummyNode.innerHTML = parseText;
     let parsedNode = dummyNode.firstChild;
 
     let attrs = parsedNode.attributes;
 
     this.undo.startBatch();
 
     for (let i = 0; i < attrs.length; i++) {
--- a/browser/devtools/markupview/test/browser_inspector_markup_edit.html
+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.html
@@ -28,13 +28,16 @@
       <div id="node19">
         <div id="node20">
           <div id="node21">
             line21
           </div>
         </div>
       </div>
     </div>
+    <div id="node22" class="unchanged"></div>
+    <div id="node23"></div>
+    <div id="node24"></div>
     <div id="retag-me">
       <div id="retag-me-2"></div>
     </div>
   </body>
 </html>
--- a/browser/devtools/markupview/test/browser_inspector_markup_edit.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.js
@@ -68,16 +68,38 @@ function test() {
       after: function() {
         assertAttributes(doc.querySelector("#node1"), {
           id: "node1",
           class: "changednode1"
         });
       }
     },
 
+    // Try change an attribute to a badly formed string
+    {
+      before: function() {
+        assertAttributes(doc.querySelector("#node22"), {
+          id: "node22",
+          class: "unchanged"
+        });
+      },
+      execute: function() {
+        let editor = markup.getContainer(doc.querySelector("#node22")).editor;
+        let attr = editor.attrs["class"].querySelector(".editable");
+        editField(attr, 'class="""');
+      },
+      after: function() {
+        assertAttributes(doc.querySelector("#node22"), {
+          id: "node22",
+          class: "unchanged"
+        });
+      }
+    },
+
+
     // Remove an attribute
     {
       before: function() {
         assertAttributes(doc.querySelector("#node4"), {
           id: "node4",
           class: "node4"
         });
       },
@@ -109,16 +131,35 @@ function test() {
         assertAttributes(doc.querySelector("#node14"), {
           id: "node14",
           class: "newclass",
           style: "color:green"
         });
       }
     },
 
+    // Try add a badly formed attribute by clicking the empty space after a node
+    {
+      before: function() {
+        assertAttributes(doc.querySelector("#node23"), {
+          id: "node23",
+        });
+      },
+      execute: function() {
+        let editor = markup.getContainer(doc.querySelector("#node23")).editor;
+        let attr = editor.newAttr;
+        editField(attr, 'class="newclass" style="""');
+      },
+      after: function() {
+        assertAttributes(doc.querySelector("#node23"), {
+          id: "node23",
+        });
+      }
+    },
+
     // Add attributes by adding to an existing attribute's entry
     {
       setup: function() {
         InspectorUI.select(doc.querySelector("#node18"), true, true, true);
       },
       before: function() {
         assertAttributes(doc.querySelector("#node18"), {
           id: "node18",
@@ -135,16 +176,35 @@ function test() {
           id: "node18",
           class: "newclass",
           style: "color:green"
         });
         is(InspectorUI.highlighter.nodeInfo.classesBox.textContent, ".newclass", "Correct classes in the infobar after edit.");
       }
     },
 
+    // Try add attributes by adding to an existing attribute's entry
+    {
+      before: function() {
+        assertAttributes(doc.querySelector("#node24"), {
+          id: "node24",
+        });
+      },
+      execute: function() {
+        let editor = markup.getContainer(doc.querySelector("#node24")).editor;
+        let attr = editor.attrs["id"].querySelector(".editable");
+        editField(attr, attr.textContent + ' class="""');
+      },
+      after: function() {
+        assertAttributes(doc.querySelector("#node24"), {
+          id: "node24",
+        });
+      }
+    },
+
     // Remove an element with the delete key
     {
       before: function() {
         ok(!!doc.querySelector("#node18"), "Node 18 should exist.");
       },
       execute: function() {
         markup.selectNode(doc.querySelector("#node18"));
         EventUtils.sendKey("delete");
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -724,23 +724,26 @@ var Scratchpad = {
   /**
    * Get recent files.
    *
    * @return Array
    *         File paths.
    */
   getRecentFiles: function SP_getRecentFiles()
   {
-    let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
-    let branch = Services.prefs.
-                 getBranch("devtools.scratchpad.");
+    let branch = Services.prefs.getBranch("devtools.scratchpad.");
+    let filePaths = [];
 
-    let filePaths = [];
+    // WARNING: Do not use getCharPref here, it doesn't play nicely with
+    // Unicode strings.
+
     if (branch.prefHasUserValue("recentFilePaths")) {
-      filePaths = JSON.parse(branch.getCharPref("recentFilePaths"));
+      let data = branch.getComplexValue("recentFilePaths",
+        Ci.nsISupportsString).data;
+      filePaths = JSON.parse(data);
     }
 
     return filePaths;
   },
 
   /**
    * Save a recent file in a JSON parsable string.
    *
@@ -776,20 +779,26 @@ var Scratchpad = {
     // If we are not storing the file and the 'recent files'-list is full,
     // remove the oldest file from the list.
     else if (filesCount === maxRecent) {
       filePaths.shift();
     }
 
     filePaths.push(aFile.path);
 
-    let branch = Services.prefs.
-                 getBranch("devtools.scratchpad.");
-    branch.setCharPref("recentFilePaths", JSON.stringify(filePaths));
-    return;
+    // WARNING: Do not use setCharPref here, it doesn't play nicely with
+    // Unicode strings.
+
+    let str = Cc["@mozilla.org/supports-string;1"]
+      .createInstance(Ci.nsISupportsString);
+    str.data = JSON.stringify(filePaths);
+
+    let branch = Services.prefs.getBranch("devtools.scratchpad.");
+    branch.setComplexValue("recentFilePaths",
+      Ci.nsISupportsString, str);
   },
 
   /**
    * Populates the 'Open Recent'-menu.
    */
   populateRecentFilesMenu: function SP_populateRecentFilesMenu()
   {
     let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
@@ -863,21 +872,29 @@ var Scratchpad = {
           this.populateRecentFilesMenu();
         }
 
         menu.removeAttribute("hidden");
       }
 
       let filePaths = this.getRecentFiles();
       if (maxRecent < filePaths.length) {
-        let branch = Services.prefs.
-                     getBranch("devtools.scratchpad.");
         let diff = filePaths.length - maxRecent;
         filePaths.splice(0, diff);
-        branch.setCharPref("recentFilePaths", JSON.stringify(filePaths));
+
+        // WARNING: Do not use setCharPref here, it doesn't play nicely with
+        // Unicode strings.
+
+        let str = Cc["@mozilla.org/supports-string;1"]
+          .createInstance(Ci.nsISupportsString);
+        str.data = JSON.stringify(filePaths);
+
+        let branch = Services.prefs.getBranch("devtools.scratchpad.");
+        branch.setComplexValue("recentFilePaths",
+          Ci.nsISupportsString, str);
       }
     }
   },
   /**
    * Save the textbox content to the currently open file.
    *
    * @param function aCallback
    *        Optional function you want to call when file is saved
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_651942_recent_files.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_651942_recent_files.js
@@ -22,17 +22,17 @@ var lists = {
   recentFiles01: null,
   recentFiles02: null,
   recentFiles03: null,
   recentFiles04: null,
 };
 
 // Temporary file names.
 let gFileName01 = "file01_ForBug651942.tmp"
-let gFileName02 = "file02_ForBug651942.tmp"
+let gFileName02 = "☕" // See bug 783858 for more information
 let gFileName03 = "file03_ForBug651942.tmp"
 let gFileName04 = "file04_ForBug651942.tmp"
 
 // Content for the temporary files.
 let gFileContent;
 let gFileContent01 = "hello.world.01('bug651942');";
 let gFileContent02 = "hello.world.02('bug651942');";
 let gFileContent03 = "hello.world.03('bug651942');";
--- a/browser/devtools/styleinspector/CssHtmlTree.jsm
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -938,25 +938,25 @@ PropertyView.prototype = {
 
     if (this.prevViewedElement != this.tree.viewedElement) {
       this._matchedSelectorViews = null;
       this._unmatchedSelectorViews = null;
       this.prevViewedElement = this.tree.viewedElement;
     }
 
     if (!this.tree.viewedElement || !this.visible) {
-      this.valueNode.innerHTML = "";
+      this.valueNode.textContent = "";
       this.matchedSelectorsContainer.parentNode.hidden = true;
-      this.matchedSelectorsContainer.innerHTML = "";
+      this.matchedSelectorsContainer.textContent = "";
       this.matchedExpander.removeAttribute("open");
       return;
     }
 
     this.tree.numVisibleProperties++;
-    this.valueNode.innerHTML = this.propertyInfo.value;
+    this.valueNode.textContent = this.propertyInfo.value;
     this.refreshAllSelectors();
   },
 
   /**
    * Refresh the panel matched rules.
    */
   refreshMatchedSelectors: function PropertyView_refreshMatchedSelectors()
   {
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -1119,22 +1119,26 @@ CssRuleView.prototype = {
   /**
    * Update the rule view's context menu by disabling irrelevant menuitems and
    * enabling relevant ones.
    *
    * @param aEvent The event object
    */
   _onMenuUpdate: function CssRuleView_onMenuUpdate(aEvent)
   {
+    let node = this.doc.popupNode;
+
     // Copy selection.
-    let disable = this.doc.defaultView.getSelection().isCollapsed;
+    let editorSelection = node.className == "styleinspector-propertyeditor" &&
+                          node.selectionEnd - node.selectionStart != 0;
+    let disable = this.doc.defaultView.getSelection().isCollapsed &&
+                  !editorSelection;
     this._copyItem.disabled = disable;
 
     // Copy property, copy property name & copy property value.
-    let node = this.doc.popupNode;
     if (!node) {
       return;
     }
 
     if (!node.classList.contains("ruleview-property") &&
         !node.classList.contains("ruleview-computed")) {
       while (node = node.parentElement) {
         if (node.classList.contains("ruleview-property") ||
@@ -1154,26 +1158,36 @@ CssRuleView.prototype = {
 
   /**
    * Copy selected text from the rule view.
    *
    * @param aEvent The event object
    */
   _onCopy: function CssRuleView_onCopy(aEvent)
   {
-    let win = this.doc.defaultView;
-    let text = win.getSelection().toString();
+    let target = this.doc.popupNode || aEvent.target;
+    let text;
 
-    // Remove any double newlines.
-    text = text.replace(/(\r?\n)\r?\n/g, "$1");
+    if (target.nodeName == "input") {
+      let start = Math.min(target.selectionStart, target.selectionEnd);
+      let end = Math.max(target.selectionStart, target.selectionEnd);
+      let count = end - start;
+      text = target.value.substr(start, count);
+    } else {
+      let win = this.doc.defaultView;
+      text = win.getSelection().toString();
 
-    // Remove "inline"
-    let inline = _strings.GetStringFromName("rule.sourceInline");
-    let rx = new RegExp("^" + inline + "\\r?\\n?", "g");
-    text = text.replace(rx, "");
+      // Remove any double newlines.
+      text = text.replace(/(\r?\n)\r?\n/g, "$1");
+
+      // Remove "inline"
+      let inline = _strings.GetStringFromName("rule.sourceInline");
+      let rx = new RegExp("^" + inline + "\\r?\\n?", "g");
+      text = text.replace(rx, "");
+    }
 
     clipboardHelper.copyString(text, this.doc);
 
     if (aEvent) {
       aEvent.preventDefault();
     }
   },
 
@@ -1272,22 +1286,23 @@ CssRuleView.prototype = {
   /**
    * Copy a property name from the rule view.
    *
    * @param aEvent The event object
    */
   _onCopyProperty: function CssRuleView_onCopyProperty(aEvent)
   {
     let node = this.doc.popupNode;
+
     if (!node) {
       return;
     }
 
     if (!node.classList.contains("ruleview-propertyname")) {
-      node = node.querySelector(".ruleview-propertyname");
+      node = node.parentNode.parentNode.querySelector(".ruleview-propertyname");
     }
 
     if (node) {
       clipboardHelper.copyString(node.textContent, this.doc);
     }
   },
 
  /**
@@ -1298,17 +1313,17 @@ CssRuleView.prototype = {
   _onCopyPropertyValue: function CssRuleView_onCopyPropertyValue(aEvent)
   {
     let node = this.doc.popupNode;
     if (!node) {
       return;
     }
 
     if (!node.classList.contains("ruleview-propertyvalue")) {
-      node = node.querySelector(".ruleview-propertyvalue");
+      node = node.parentNode.parentNode.querySelector(".ruleview-propertyvalue");
     }
 
     if (node) {
       clipboardHelper.copyString(node.textContent, this.doc);
     }
   }
 };
 
@@ -1376,16 +1391,29 @@ RuleEditor.prototype = {
 
     code.addEventListener("click", function() {
       let selection = this.doc.defaultView.getSelection();
       if (selection.isCollapsed) {
         this.newProperty();
       }
     }.bind(this), false);
 
+    this.element.addEventListener("mousedown", function() {
+      let editorNodes =
+        this.doc.querySelectorAll(".styleinspector-propertyeditor");
+
+      if (editorNodes) {
+        for (let node of editorNodes) {
+          if (node.inplaceEditor) {
+            node.inplaceEditor._clear();
+          }
+        }
+      }
+    }.bind(this), false);
+
     this.propertyList = createChild(code, "ul", {
       class: "ruleview-propertylist"
     });
 
     this.populate();
 
     this.closeBrace = createChild(code, "div", {
       class: "ruleview-ruleclose",
--- a/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
@@ -83,18 +83,18 @@ function testClip()
       "    font-size: 14pt;[\\r\\n]+" +
       "    font-family: helvetica,sans-serif;[\\r\\n]+" +
       "    color: rgb\\(170, 170, 170\\);[\\r\\n]+" +
       "}[\\r\\n]*";
 
     SimpleTest.waitForClipboard(function IUI_boundCopyPropCheck() {
         return checkClipboardData(expectedPattern);
       },
-      checkCopyRule, checkCopyRuleWithEditorSelected, function() {
-        failedClipboard(expectedPattern, checkCopyRuleWithEditorSelected);
+      checkCopyRule, checkCopyProperty, function() {
+        failedClipboard(expectedPattern, checkCopyProperty);
       });
   });
 }
 
 function checkCopyRule() {
   let contentDoc = ruleViewFrame().contentDocument;
   let props = contentDoc.querySelectorAll(".ruleview-property");
 
@@ -113,67 +113,16 @@ function checkCopyRule() {
     ruleViewFrame().contentWindow);
 
   ruleView()._boundCopyRule();
   let menu = contentDoc.querySelector("#rule-view-context-menu");
   ok(menu, "we have the context menu");
   menu.hidePopup();
 }
 
-function checkCopyRuleWithEditorSelected()
-{
-  let contentDoc = ruleViewFrame().contentDocument;
-  let rules = contentDoc.querySelectorAll(".ruleview-rule");
-  let propNodes = contentDoc.querySelectorAll(".ruleview-property");
-  let propNode = propNodes[2];
-  let propNameNode = propNode.querySelector(".ruleview-propertyname");
-
-  ok(propNameNode, "we have the property name node");
-
-  info("Checking that _boundCopyRule()  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]*";
-
-  let elementRuleEditor = rules[0]._ruleEditor;
-  waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
-    ok(aEditor, "we have the editor");
-
-    waitForBlur.editor = aEditor;
-
-    // We need the context menu to open in the correct place in order for
-    // popupNode to be propertly set.
-    EventUtils.synthesizeMouse(aEditor.input, 1, 1,
-      { type: "contextmenu", button: 2 }, ruleViewFrame().contentWindow);
-
-    SimpleTest.waitForClipboard(function IUI_boundCopyCheckWithSelection() {
-      let menu = contentDoc.querySelector("#rule-view-context-menu");
-      ok(menu, "we have the context menu");
-      menu.hidePopup();
-
-      return checkClipboardData(expectedPattern);
-    }, ruleView()._boundCopyRule, waitForBlur, function() {
-      failedClipboard(expectedPattern, checkCopyProperty);
-    });
-  });
-  EventUtils.synthesizeMouse(propNameNode, 1, 1, { }, ruleViewFrame().contentWindow);
-}
-
-function waitForBlur()
-{
-  waitForEditorBlur(waitForBlur.editor, function() {
-    waitForBlur.editor = null;
-    checkCopyProperty();
-  });
-  waitForBlur.editor.input.blur();
-}
-
 function checkCopyProperty()
 {
   let contentDoc = ruleViewFrame().contentDocument;
   let props = contentDoc.querySelectorAll(".ruleview-property");
   let prop = props[2];
 
   info("Checking that _onCopyDeclaration() returns " +
        "the correct clipboard value");
@@ -192,32 +141,32 @@ function checkCopyProperty()
     failedClipboard(expectedPattern, checkCopyPropertyName);
   });
 }
 
 function checkCopyPropertyName()
 {
   info("Checking that _onCopyProperty() returns " +
        "the correct clipboard value");
-  let expectedPattern = "font-family";
+  let expectedPattern = "margin";
 
   SimpleTest.waitForClipboard(function IUI_boundCopyPropNameCheck() {
     return checkClipboardData(expectedPattern);
   },
   ruleView()._boundCopyProperty,
   checkCopyPropertyValue, function() {
     failedClipboard(expectedPattern, checkCopyPropertyValue);
   });
 }
 
 function checkCopyPropertyValue()
 {
   info("Checking that _onCopyPropertyValue() " +
        " returns the correct clipboard value");
-  let expectedPattern = "helvetica,sans-serif";
+  let expectedPattern = "10em";
 
   SimpleTest.waitForClipboard(function IUI_boundCopyPropValueCheck() {
     return checkClipboardData(expectedPattern);
   },
   ruleView()._boundCopyPropertyValue,
   checkCopySelection, function() {
     failedClipboard(expectedPattern, checkCopySelection);
   });
@@ -243,21 +192,71 @@ function checkCopySelection()
                         "    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]*";
 
   SimpleTest.waitForClipboard(function IUI_boundCopyCheck() {
     return checkClipboardData(expectedPattern);
-  },ruleView()._boundCopy, finishup, function() {
-    failedClipboard(expectedPattern, finishup);
+  },ruleView()._boundCopy, testSimpleCopy, function() {
+    failedClipboard(expectedPattern, testSimpleCopy);
+  });
+}
+
+function testSimpleCopy()
+{
+  executeSoon(function() {
+    info("Checking that _onCopy() 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]*";
+
+    SimpleTest.waitForClipboard(function IUI_testSimpleCopy() {
+        return checkClipboardData(expectedPattern);
+      },
+      checkSimpleCopy, finishup, function() {
+        failedClipboard(expectedPattern, finishup);
+      });
   });
 }
 
+function checkSimpleCopy() {
+  let contentDoc = ruleViewFrame().contentDocument;
+  let props = contentDoc.querySelectorAll(".ruleview-code");
+
+  is(props.length, 2, "checking property length");
+
+  let prop = props[0];
+
+  selectNode(prop);
+
+  // 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 },
+    ruleViewFrame().contentWindow);
+
+  ruleView()._boundCopy();
+  let menu = contentDoc.querySelector("#rule-view-context-menu");
+  ok(menu, "we have the context menu");
+  menu.hidePopup();
+}
+
+function selectNode(aNode) {
+  let doc = aNode.ownerDocument;
+  let win = doc.defaultView;
+  let range = doc.createRange();
+
+  range.selectNode(aNode);
+  win.getSelection().addRange(range);
+}
+
 function checkClipboardData(aExpectedPattern)
 {
   let actual = SpecialPowers.getClipboardData("text/unicode");
   let expectedRegExp = new RegExp(aExpectedPattern, "g");
   return expectedRegExp.test(actual);
 }
 
 function failedClipboard(aExpectedPattern, aCallback)