Bug 1272653 - Make Get Element Attribute spec compatible; r=automatedtester a=test-only
authorAndreas Tolfsen <ato@mozilla.com>
Mon, 16 May 2016 21:24:57 +0100
changeset 333251 83c3f083db414cfc5e095d14a766b7b25cc7f111
parent 333250 06e08681af3a0b0e17ac59ee3df0bf4a4abeb23f
child 333252 28dbd70650414cf92b6d21c0bdbfaba8dac8332d
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersautomatedtester, test-only
bugs1272653
milestone48.0a2
Bug 1272653 - Make Get Element Attribute spec compatible; r=automatedtester a=test-only MozReview-Commit-ID: K5PsnmRrDJk
testing/marionette/element.js
testing/marionette/harness/marionette/tests/unit/test_element_state.py
testing/marionette/listener.js
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -31,16 +31,17 @@ const logger = Log.repository.getLogger(
  */
 
 this.EXPORTED_SYMBOLS = [
   "element",
   "ElementManager",
 ];
 
 const DOCUMENT_POSITION_DISCONNECTED = 1;
+const XMLNS = "http://www.w3.org/1999/xhtml";
 
 const uuidGen = Cc["@mozilla.org/uuid-generator;1"]
     .getService(Ci.nsIUUIDGenerator);
 
 this.element = {};
 
 element.LegacyKey = "ELEMENT";
 element.Key = "element-6066-11e4-a52e-4f735466cecf";
@@ -906,8 +907,59 @@ element.getInteractableElementTree = fun
 element.isKeyboardInteractable = function(el) {
   return true;
 };
 
 element.isXULElement = function(el) {
   let ns = atom.getElementAttribute(el, "namespaceURI");
   return ns.indexOf("there.is.only.xul") >= 0;
 };
+
+const boolEls = {
+  audio: ["autoplay", "controls", "loop", "muted"],
+  button: ["autofocus", "disabled", "formnovalidate"],
+  details: ["open"],
+  dialog: ["open"],
+  fieldset: ["disabled"],
+  form: ["novalidate"],
+  iframe: ["allowfullscreen"],
+  img: ["ismap"],
+  input: ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"],
+  keygen: ["autofocus", "disabled"],
+  menuitem: ["checked", "default", "disabled"],
+  object: ["typemustmatch"],
+  ol: ["reversed"],
+  optgroup: ["disabled"],
+  option: ["disabled", "selected"],
+  script: ["async", "defer"],
+  select: ["autofocus", "disabled", "multiple", "required"],
+  textarea: ["autofocus", "disabled", "readonly", "required"],
+  track: ["default"],
+  video: ["autoplay", "controls", "loop", "muted"],
+};
+
+/**
+ * Tests if the attribute is a boolean attribute on element.
+ *
+ * @param {DOMElement} el
+ *     Element to test if |attr| is a boolean attribute on.
+ * @param {string} attr
+ *     Attribute to test is a boolean attribute.
+ *
+ * @return {boolean}
+ *     True if the attribute is boolean, false otherwise.
+ */
+element.isBooleanAttribute = function(el, attr) {
+  if (el.namespaceURI !== XMLNS) {
+    return false;
+  }
+
+  // global boolean attributes that apply to all HTML elements,
+  // except for custom elements
+  if ((attr == "hidden" || attr == "itemscope") && !el.localName.includes("-")) {
+    return true;
+  }
+
+  if (!boolEls.hasOwnProperty(el.localName)) {
+    return false;
+  }
+  return boolEls[el.localName].includes(attr)
+};
--- a/testing/marionette/harness/marionette/tests/unit/test_element_state.py
+++ b/testing/marionette/harness/marionette/tests/unit/test_element_state.py
@@ -1,20 +1,58 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+import types
 import urllib
 
 from marionette import MarionetteTestCase
 from marionette_driver.by import By
 
 
-def inline(doc):
-    return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
+boolean_attributes = {
+  "audio": ["autoplay", "controls", "loop", "muted"],
+  "button": ["autofocus", "disabled", "formnovalidate"],
+  "details": ["open"],
+  "dialog": ["open"],
+  "fieldset": ["disabled"],
+  "form": ["novalidate"],
+  "iframe": ["allowfullscreen"],
+  "img": ["ismap"],
+  "input": ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"],
+  "menuitem": ["checked", "default", "disabled"],
+  "object": ["typemustmatch"],
+  "ol": ["reversed"],
+  "optgroup": ["disabled"],
+  "option": ["disabled", "selected"],
+  "script": ["async", "defer"],
+  "select": ["autofocus", "disabled", "multiple", "required"],
+  "textarea": ["autofocus", "disabled", "readonly", "required"],
+  "track": ["default"],
+  "video": ["autoplay", "controls", "loop", "muted"],
+}
+
+
+def inline(doc, doctype="html"):
+    if doctype == "html":
+        return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
+    elif doctype == "xhtml":
+        return "data:application/xhtml+xml,%s" % urllib.quote(
+r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+  <head>
+    <title>XHTML might be the future</title>
+  </head>
+
+  <body>
+    %s
+  </body>
+</html>""" % doc)
 
 
 attribute = inline("<input foo=bar>")
 input = inline("<input>")
 disabled = inline("<input disabled=baz>")
 check = inline("<input type=checkbox>")
 
 
@@ -34,27 +72,66 @@ class TestIsElementDisplayed(MarionetteT
         self.marionette.navigate(test_html)
         l = self.marionette.find_element(By.NAME, "myCheckBox")
         self.assertTrue(l.is_displayed())
         self.marionette.execute_script("arguments[0].hidden = true;", [l])
         self.assertFalse(l.is_displayed())
 
 
 class TestGetElementAttribute(MarionetteTestCase):
-    def test_get(self):
-        test_html = self.marionette.absolute_url("test.html")
-        self.marionette.navigate(test_html)
-        l = self.marionette.find_element(By.ID, "mozLink")
-        self.assertEqual("mozLink", l.get_attribute("id"))
+    def test_normal_attribute(self):
+        self.marionette.navigate(inline("<p style=foo>"))
+        el = self.marionette.find_element(By.TAG_NAME, "p")
+        attr = el.get_attribute("style")
+        self.assertIsInstance(attr, types.StringTypes)
+        self.assertEqual("foo", attr)
+
+    def test_boolean_attributes(self):
+        for tag, attrs in boolean_attributes.iteritems():
+            for attr in attrs:
+                print("testing boolean attribute <%s %s>" % (tag, attr))
+                doc = inline("<%s %s>" % (tag, attr))
+                self.marionette.navigate(doc)
+                el = self.marionette.find_element(By.TAG_NAME, tag)
+                res = el.get_attribute(attr)
+                self.assertIsInstance(res, types.StringTypes)
+                self.assertEqual("true", res)
+
+    def test_global_boolean_attributes(self):
+        self.marionette.navigate(inline("<p hidden>foo"))
+        el = self.marionette.find_element(By.TAG_NAME, "p")
+        attr = el.get_attribute("hidden")
+        self.assertIsInstance(attr, types.StringTypes)
+        self.assertEqual("true", attr)
 
-    def test_boolean(self):
-        test_html = self.marionette.absolute_url("html5/boolean_attributes.html")
-        self.marionette.navigate(test_html)
-        disabled = self.marionette.find_element(By.ID, "disabled")
-        self.assertEqual('true', disabled.get_attribute("disabled"))
+        self.marionette.navigate(inline("<p>foo"))
+        el = self.marionette.find_element(By.TAG_NAME, "p")
+        attr = el.get_attribute("hidden")
+        self.assertIsNone(attr)
+
+        self.marionette.navigate(inline("<p itemscope>foo"))
+        el = self.marionette.find_element(By.TAG_NAME, "p")
+        attr = el.get_attribute("itemscope")
+        self.assertIsInstance(attr, types.StringTypes)
+        self.assertEqual("true", attr)
+
+        self.marionette.navigate(inline("<p>foo"))
+        el = self.marionette.find_element(By.TAG_NAME, "p")
+        attr = el.get_attribute("itemscope")
+        self.assertIsNone(attr)
+
+    # TODO(ato): Test for custom elements
+
+    def test_xhtml(self):
+        doc = inline("<p hidden=\"true\">foo</p>", doctype="xhtml")
+        self.marionette.navigate(doc)
+        el = self.marionette.find_element(By.TAG_NAME, "p")
+        attr = el.get_attribute("hidden")
+        self.assertIsInstance(attr, types.StringTypes)
+        self.assertEqual("true", attr)
 
 
 class TestGetElementProperty(MarionetteTestCase):
     def test_get(self):
         self.marionette.navigate(disabled)
         el = self.marionette.find_element(By.TAG_NAME, "input")
         prop = el.get_property("disabled")
         self.assertIsInstance(prop, bool)
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -1056,17 +1056,25 @@ function clickElement(id) {
   return interaction.clickElement(
       el,
       !!capabilities.raisesAccessibilityExceptions,
       capabilities.specificationLevel >= 1);
 }
 
 function getElementAttribute(id, name) {
   let el = elementManager.getKnownElement(id, curContainer);
-  return atom.getElementAttribute(el, name, curContainer.frame);
+  if (element.isBooleanAttribute(el, name)) {
+    if (el.hasAttribute(name)) {
+      return "true";
+    } else {
+      return null;
+    }
+  } else {
+    return el.getAttribute(name);
+  }
 }
 
 function getElementProperty(id, name) {
   let el = elementManager.getKnownElement(id, curContainer);
   return el[name];
 }
 
 /**