Bug 721920 - Honor the 'lang' and 'xml:lang' attributes in SVG as we do in HTML. r=bz.
authorJonathan Watt <jwatt@jwatt.org>
Sun, 19 Feb 2012 20:49:34 +0000
changeset 87206 d9c03190bd476ed53edc8e2125b7e3984dc45b7c
parent 87205 c4311adde5acb5dd57e99a4f13f434086608e027
child 87207 5326871f69618196da075b760a2e10b4b26b5fe6
push id551
push userrcampbell@mozilla.com
push dateWed, 22 Feb 2012 16:49:06 +0000
treeherderfx-team@4f425ab85667 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs721920
milestone13.0a1
Bug 721920 - Honor the 'lang' and 'xml:lang' attributes in SVG as we do in HTML. r=bz.
content/base/public/nsIContent.h
content/svg/content/src/nsSVGElement.cpp
content/svg/content/src/nsSVGElement.h
content/svg/content/test/Makefile.in
content/svg/content/test/test_lang.xhtml
layout/reftests/svg/lang-attribute-01.svg
layout/reftests/svg/lang-attribute-02.svg
layout/reftests/svg/lang-attribute-03.svg
layout/reftests/svg/reftest.list
layout/style/Declaration.h
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -915,27 +915,27 @@ public:
    * If the content is a part of HTML editor, this returns editing
    * host content.  When the content is in designMode, this returns its body
    * element.  Also, when the content isn't editable, this returns null.
    */
   nsIContent* GetEditingHost();
 
   /**
    * Determing language. Look at the nearest ancestor element that has a lang
-   * attribute in the XML namespace or is an HTML element and has a lang in
+   * attribute in the XML namespace or is an HTML/SVG element and has a lang in
    * no namespace attribute.
    */
   void GetLang(nsAString& aResult) const {
     for (const nsIContent* content = this; content; content = content->GetParent()) {
       if (content->GetAttrCount() > 0) {
         // xml:lang has precedence over lang on HTML elements (see
         // XHTML1 section C.7).
         bool hasAttr = content->GetAttr(kNameSpaceID_XML, nsGkAtoms::lang,
                                           aResult);
-        if (!hasAttr && content->IsHTML()) {
+        if (!hasAttr && (content->IsHTML() || content->IsSVG())) {
           hasAttr = content->GetAttr(kNameSpaceID_None, nsGkAtoms::lang,
                                      aResult);
         }
         NS_ASSERTION(hasAttr || aResult.IsEmpty(),
                      "GetAttr that returns false should not make string non-empty");
         if (hasAttr) {
           return;
         }
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -903,16 +903,25 @@ nsSVGElement::WalkContentStyleRules(nsRu
       animContentStyleRule->RuleMatched();
       aRuleWalker->Forward(animContentStyleRule);
     }
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP_(bool)
+nsSVGElement::IsAttributeMapped(const nsIAtom* name) const
+{
+  if (name == nsGkAtoms::lang) {
+    return true;
+  }
+  return nsSVGElementBase::IsAttributeMapped(name);
+}
+
 // PresentationAttributes-FillStroke
 /* static */ const nsGenericElement::MappedAttributeEntry
 nsSVGElement::sFillStrokeMap[] = {
   { &nsGkAtoms::fill },
   { &nsGkAtoms::fill_opacity },
   { &nsGkAtoms::fill_rule },
   { &nsGkAtoms::stroke },
   { &nsGkAtoms::stroke_dasharray },
@@ -1150,19 +1159,34 @@ MappedAttrParser::ParseMappedAttrValue(n
   if (!mDecl) {
     mDecl = new css::Declaration();
     mDecl->InitializeEmpty();
   }
 
   // Get the nsCSSProperty ID for our mapped attribute.
   nsCSSProperty propertyID =
     nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName));
-  bool changed; // outparam for ParseProperty. (ignored)
-  mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI,
-                        mNodePrincipal, mDecl, &changed, false);
+  if (propertyID != eCSSProperty_UNKNOWN) {
+    bool changed; // outparam for ParseProperty. (ignored)
+    mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI,
+                          mNodePrincipal, mDecl, &changed, false);
+    return;
+  }
+  NS_ABORT_IF_FALSE(aMappedAttrName == nsGkAtoms::lang,
+                    "Only 'lang' should be unrecognized!");
+  // nsCSSParser doesn't know about 'lang', so we need to handle it specially.
+  if (aMappedAttrName == nsGkAtoms::lang) {
+    propertyID = eCSSProperty__x_lang;
+    nsCSSExpandedDataBlock block;
+    mDecl->ExpandTo(&block);
+    nsCSSValue cssValue(PromiseFlatString(aMappedAttrValue), eCSSUnit_Ident);
+    block.AddLonghandProperty(propertyID, cssValue);
+    mDecl->ValueAppended(propertyID);
+    mDecl->CompressFrom(&block);
+  }
 }
 
 already_AddRefed<css::StyleRule>
 MappedAttrParser::CreateStyleRule()
 {
   if (!mDecl) {
     return nsnull; // No mapped attributes were parsed
   }
@@ -1198,16 +1222,26 @@ nsSVGElement::UpdateContentStyleRule()
   MappedAttrParser mappedAttrParser(doc->CSSLoader(), doc->GetDocumentURI(),
                                     GetBaseURI(), NodePrincipal());
 
   for (PRUint32 i = 0; i < attrCount; ++i) {
     const nsAttrName* attrName = mAttrsAndChildren.AttrNameAt(i);
     if (!attrName->IsAtom() || !IsAttributeMapped(attrName->Atom()))
       continue;
 
+    if (attrName->NamespaceID() != kNameSpaceID_None &&
+        !attrName->Equals(nsGkAtoms::lang, kNameSpaceID_XML)) {
+      continue;
+    }
+
+    if (attrName->Equals(nsGkAtoms::lang, kNameSpaceID_None) &&
+        HasAttr(kNameSpaceID_XML, nsGkAtoms::lang)) {
+      continue; // xml:lang has precedence
+    }
+
     if (Tag() == nsGkAtoms::svg) {
       // Special case: we don't want <svg> 'width'/'height' mapped into style
       // if the attribute value isn't a valid <length> according to SVG (which
       // only supports a subset of the CSS <length> values). We don't enforce
       // this by checking the attribute value in nsSVGSVGElement::
       // IsAttributeMapped since we don't want that method to depend on the
       // value of the attribute that is being checked. Rather we just prevent
       // the actual mapping here, as necessary.
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -119,16 +119,18 @@ public:
 
   virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
                                               PRInt32 aModType) const;
 
   virtual bool IsNodeOfType(PRUint32 aFlags) const;
 
   NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker);
 
+  NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const;
+
   static const MappedAttributeEntry sFillStrokeMap[];
   static const MappedAttributeEntry sGraphicsMap[];
   static const MappedAttributeEntry sTextContentElementsMap[];
   static const MappedAttributeEntry sFontSpecificationMap[];
   static const MappedAttributeEntry sGradientStopMap[];
   static const MappedAttributeEntry sViewportsMap[];
   static const MappedAttributeEntry sMarkersMap[];
   static const MappedAttributeEntry sColorMap[];
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -69,16 +69,17 @@ include $(topsrcdir)/config/rules.mk
 		test_dataTypesModEvents.html \
 		dataTypes-helper.svg \
 		getCTM-helper.svg \
 		test_getCTM.html \
 		test_getElementById.xhtml \
 		test_getSubStringLength.xhtml \
 		getSubStringLength-helper.svg \
 		test_isSupported.xhtml \
+		test_lang.xhtml \
 		test_nonAnimStrings.xhtml \
 		test_pathAnimInterpolation.xhtml \
 		test_pathSeg.xhtml \
 		test_pointAtLength.xhtml \
 		test_pointer-events.xhtml \
 		test_pointer-events-2.xhtml \
 		test_scientific.html \
 		scientific-helper.svg \
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_lang.xhtml
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=721920
+-->
+<head>
+  <title>Test for Bug 721920</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <style type="text/css">
+
+svg text { word-spacing: 1em; }
+
+  </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=721920">Mozilla Bug 721920</a>
+<p id="display">
+  <svg xmlns="http://www.w3.org/2000/svg" width="400" height="300">
+    <g lang="zh-Hans">
+      <text id="s0" y="40" style="font-size: 0">汉字</text>
+      <text id="s4" y="80" style="font-size: 4px">汉字</text>
+      <text id="s12" y="120" style="font-size: 12px">汉字</text>
+      <text id="s28" y="160" style="font-size: 28px">汉字</text>
+    </g>
+  </svg>
+</p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+//<![CDATA[
+
+/** Test for Bug 721920 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var elts = [
+  document.getElementById("s0"),
+  document.getElementById("s4"),
+  document.getElementById("s12"),
+  document.getElementById("s28")
+];
+
+function fs(idx) {
+  // The computed font size actually *doesn't* currently reflect the
+  // minimum font size preference, but things in em units do.  Hence
+  // why we use word-spacing here.
+  // test_bug401046.html uses margin-bottom instead, but there's an
+  // SVG bug that prevents that working in SVG (bug 728723).
+  return getComputedStyle(elts[idx], "").wordSpacing;
+}
+
+SpecialPowers.pushPrefEnv({'clear': [['font.minimum-size.zh-CN']]}, step1);
+
+function step1() {
+    is(fs(0), "0px", "at min font size 0, 0px should compute to 0px");
+    is(fs(1), "4px", "at min font size 0, 4px should compute to 4px");
+    is(fs(2), "12px", "at min font size 0, 12px should compute to 12px");
+    is(fs(3), "28px", "at min font size 0, 28px should compute to 28px");
+
+    SpecialPowers.pushPrefEnv({'set': [['font.minimum-size.zh-CN', 7]]}, step2);
+}
+
+function step2() {
+    is(fs(0), "0px", "at min font size 7, 0px should compute to 0px");
+    is(fs(1), "7px", "at min font size 7, 4px should compute to 7px");
+    is(fs(2), "12px", "at min font size 7, 12px should compute to 12px");
+    is(fs(3), "28px", "at min font size 7, 28px should compute to 28px");
+
+    SpecialPowers.pushPrefEnv({'set': [['font.minimum-size.zh-CN', 18]]}, step3);
+}
+
+function step3() {
+    is(fs(0), "0px", "at min font size 18, 0px should compute to 0px");
+    is(fs(1), "18px", "at min font size 18, 4px should compute to 18px");
+    is(fs(2), "18px", "at min font size 18, 12px should compute to 18px");
+    is(fs(3), "28px", "at min font size 18, 28px should compute to 28px");
+
+    SpecialPowers.pushPrefEnv({'clear': [['font.minimum-size.zh-CN']]}, SimpleTest.finish);
+}
+
+//]]>
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/lang-attribute-01.svg
@@ -0,0 +1,16 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" lang="foo">
+  <title>Test the 'lang' attribute in SVG</title>
+  <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=368840 -->
+  <style type="text/css">
+
+rect:lang(foo) {
+  fill: lime;
+}
+
+  </style>
+  <rect width="100%" height="100%" fill="red"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/lang-attribute-02.svg
@@ -0,0 +1,16 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" xml:lang="foo">
+  <title>Test the 'xml:lang' attribute in SVG</title>
+  <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=368840 -->
+  <style type="text/css">
+
+rect:lang(foo) {
+  fill: lime;
+}
+
+  </style>
+  <rect width="100%" height="100%" fill="red"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/lang-attribute-03.svg
@@ -0,0 +1,16 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" xml:lang="foo" lang="bar">
+  <title>Test the 'xml:lang' attribute in SVG</title>
+  <!-- From https://bugzilla.mozilla.org/show_bug.cgi?id=368840 -->
+  <style type="text/css">
+
+rect:lang(foo) {
+  fill: lime;
+}
+
+  </style>
+  <rect width="100%" height="100%" fill="red"/>
+</svg>
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -148,16 +148,19 @@ fails-if(Android) == filter-extref-diffe
 == foreignObject-style-change-01.svg pass.svg
 == getElementById-a-element-01.svg pass.svg
 fails-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated) == gradient-live-01a.svg gradient-live-01-ref.svg # bug 696674
 fails-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated) == gradient-live-01b.svg gradient-live-01-ref.svg # bug 696674
 fails-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated) == gradient-live-01c.svg gradient-live-01-ref.svg # bug 696674
 fails-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)&&!layersGPUAccelerated) == gradient-live-01d.svg gradient-live-01-ref.svg # bug 696674
 fails == inline-in-xul-basic-01.xul pass.svg
 == invalid-text-01.svg pass.svg
+== lang-attribute-01.svg pass.svg
+== lang-attribute-02.svg pass.svg
+== lang-attribute-03.svg pass.svg
 == linearGradient-basic-01.svg pass.svg
 == linearGradient-basic-02.svg pass.svg
 == markers-and-group-opacity-01.svg markers-and-group-opacity-01-ref.svg
 == marker-attribute-01.svg pass.svg
 == marker-viewBox-01.svg marker-viewBox-01-ref.svg
 == mask-basic-01.svg pass.svg
 == mask-basic-02.svg mask-basic-02-ref.svg
 == mask-extref-dataURI-01.svg pass.svg
--- a/layout/style/Declaration.h
+++ b/layout/style/Declaration.h
@@ -113,16 +113,17 @@ public:
   /**
    * Initialize this declaration as holding no data.  Cannot fail.
    */
   void InitializeEmpty();
 
   /**
    * Transfer all of the state from |aExpandedData| into this declaration.
    * After calling, |aExpandedData| should be in its initial state.
+   * Callers must make sure mOrder is updated as necessary.
    */
   void CompressFrom(nsCSSExpandedDataBlock *aExpandedData) {
     NS_ABORT_IF_FALSE(!mData, "oops");
     NS_ABORT_IF_FALSE(!mImportantData, "oops");
     aExpandedData->Compress(getter_Transfers(mData),
                             getter_Transfers(mImportantData));
     aExpandedData->AssertInitialState();
   }