Bug 971043 - Implement getTranslationNodes function to retrieve nodes from webpage that contains meaningful text for translation. r=smaug
authorFelipe Gomes <felipc@gmail.com>
Sat, 05 Apr 2014 00:21:08 -0300
changeset 195796 79db871959fdd3ca1dcfecf1f1cfdc4eb855a0ca
parent 195795 6d797ac4d78ee633b29f5cc609290a6560489e04
child 195797 459a4f222dc4f2716c27c6311339e1933a76028b
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs971043
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 971043 - Implement getTranslationNodes function to retrieve nodes from webpage that contains meaningful text for translation. r=smaug
content/base/public/FragmentOrElement.h
content/base/public/nsIContent.h
content/base/src/FragmentOrElement.cpp
content/base/src/nsGenericDOMDataNode.cpp
content/base/src/nsGenericDOMDataNode.h
dom/base/nsDOMWindowUtils.cpp
dom/base/nsDOMWindowUtils.h
dom/base/test/mochitest.ini
dom/base/test/test_getTranslationNodes.html
dom/base/test/test_getTranslationNodes_limit.html
dom/interfaces/base/nsIDOMWindowUtils.idl
--- a/content/base/public/FragmentOrElement.h
+++ b/content/base/public/FragmentOrElement.h
@@ -195,16 +195,17 @@ public:
   // Need to implement this here too to avoid hiding.
   nsresult SetText(const nsAString& aStr, bool aNotify)
   {
     return SetText(aStr.BeginReading(), aStr.Length(), aNotify);
   }
   virtual nsresult AppendText(const char16_t* aBuffer, uint32_t aLength,
                               bool aNotify) MOZ_OVERRIDE;
   virtual bool TextIsOnlyWhitespace() MOZ_OVERRIDE;
+  virtual bool HasTextForTranslation() MOZ_OVERRIDE;
   virtual void AppendTextTo(nsAString& aResult) MOZ_OVERRIDE;
   virtual bool AppendTextTo(nsAString& aResult,
                             const mozilla::fallible_t&) MOZ_OVERRIDE NS_WARN_UNUSED_RESULT; 
   virtual nsIContent *GetBindingParent() const MOZ_OVERRIDE;
   virtual nsXBLBinding *GetXBLBinding() const MOZ_OVERRIDE;
   virtual void SetXBLBinding(nsXBLBinding* aBinding,
                              nsBindingManager* aOldBindingManager = nullptr) MOZ_OVERRIDE;
   virtual ShadowRoot *GetShadowRoot() const MOZ_OVERRIDE;
--- a/content/base/public/nsIContent.h
+++ b/content/base/public/nsIContent.h
@@ -34,18 +34,18 @@ struct IMEState;
 enum nsLinkState {
   eLinkState_Unvisited  = 1,
   eLinkState_Visited    = 2,
   eLinkState_NotLink    = 3 
 };
 
 // IID for the nsIContent interface
 #define NS_ICONTENT_IID \
-{ 0xafa52dfb, 0x9d92, 0x4592, \
-  { 0xa1, 0xd2, 0x08, 0xc4, 0x92, 0x89, 0x7f, 0xce } }
+{ 0x1329e5b7, 0x4bcd, 0x450c, \
+  { 0xa2, 0x3a, 0x98, 0xc5, 0x85, 0xcd, 0x73, 0xf9 } }
 
 /**
  * A node of content in a document's content model. This interface
  * is supported by all content objects.
  */
 class nsIContent : public nsINode {
 public:
   typedef mozilla::widget::IMEState IMEState;
@@ -529,16 +529,24 @@ public:
 
   /**
    * Query method to see if the frame is nothing but whitespace
    * NOTE: Always returns false for elements
    */
   virtual bool TextIsOnlyWhitespace() = 0;
 
   /**
+   * Method to see if the text node contains data that is useful
+   * for a translation: i.e., it consists of more than just whitespace,
+   * digits and punctuation.
+   * NOTE: Always returns false for elements.
+   */
+  virtual bool HasTextForTranslation() = 0;
+
+  /**
    * Append the text content to aResult.
    * NOTE: This asserts and returns for elements
    */
   virtual void AppendTextTo(nsAString& aResult) = 0;
 
   /**
    * Append the text content to aResult.
    * NOTE: This asserts and returns for elements
--- a/content/base/src/FragmentOrElement.cpp
+++ b/content/base/src/FragmentOrElement.cpp
@@ -1914,16 +1914,22 @@ FragmentOrElement::AppendText(const char
 }
 
 bool
 FragmentOrElement::TextIsOnlyWhitespace()
 {
   return false;
 }
 
+bool
+FragmentOrElement::HasTextForTranslation()
+{
+  return false;
+}
+
 void
 FragmentOrElement::AppendTextTo(nsAString& aResult)
 {
   // We can remove this assertion if it turns out to be useful to be able
   // to depend on this appending nothing.
   NS_NOTREACHED("called FragmentOrElement::TextLength");
 }
 
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -985,16 +985,51 @@ nsGenericDOMDataNode::TextIsOnlyWhitespa
 
     ++cp;
   }
 
   SetFlags(NS_CACHED_TEXT_IS_ONLY_WHITESPACE | NS_TEXT_IS_ONLY_WHITESPACE);
   return true;
 }
 
+bool
+nsGenericDOMDataNode::HasTextForTranslation()
+{
+  if (mText.Is2b()) {
+    // The fragment contains non-8bit characters which means there
+    // was at least one "interesting" character to trigger non-8bit.
+    return true;
+  }
+
+  if (HasFlag(NS_CACHED_TEXT_IS_ONLY_WHITESPACE) &&
+      HasFlag(NS_TEXT_IS_ONLY_WHITESPACE)) {
+    return false;
+  }
+
+  const char* cp = mText.Get1b();
+  const char* end = cp + mText.GetLength();
+
+  unsigned char ch;
+  for (; cp < end; cp++) {
+    ch = *cp;
+
+    // These are the characters that are letters
+    // in the first 256 UTF-8 codepoints.
+    if ((ch >= 'a' && ch <= 'z') ||
+       (ch >= 'A' && ch <= 'Z') ||
+       (ch >= 192 && ch <= 214) ||
+       (ch >= 216 && ch <= 246) ||
+       (ch >= 248)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 void
 nsGenericDOMDataNode::AppendTextTo(nsAString& aResult)
 {
   mText.AppendTo(aResult);
 }
 
 bool
 nsGenericDOMDataNode::AppendTextTo(nsAString& aResult, const mozilla::fallible_t&)
--- a/content/base/src/nsGenericDOMDataNode.h
+++ b/content/base/src/nsGenericDOMDataNode.h
@@ -140,16 +140,17 @@ public:
   // Need to implement this here too to avoid hiding.
   nsresult SetText(const nsAString& aStr, bool aNotify)
   {
     return SetText(aStr.BeginReading(), aStr.Length(), aNotify);
   }
   virtual nsresult AppendText(const char16_t* aBuffer, uint32_t aLength,
                               bool aNotify) MOZ_OVERRIDE;
   virtual bool TextIsOnlyWhitespace() MOZ_OVERRIDE;
+  virtual bool HasTextForTranslation() MOZ_OVERRIDE;
   virtual void AppendTextTo(nsAString& aResult) MOZ_OVERRIDE;
   virtual bool AppendTextTo(nsAString& aResult,
                             const mozilla::fallible_t&) MOZ_OVERRIDE NS_WARN_UNUSED_RESULT;
   virtual void DestroyContent() MOZ_OVERRIDE;
   virtual void SaveSubtreeState() MOZ_OVERRIDE;
 
 #ifdef DEBUG
   virtual void List(FILE* out, int32_t aIndent) const MOZ_OVERRIDE;
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -82,16 +82,17 @@
 #include "FrameLayerBuilder.h"
 #include "nsDisplayList.h"
 #include "nsROCSSPrimitiveValue.h"
 #include "nsIBaseWindow.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "GeckoProfiler.h"
 #include "mozilla/Preferences.h"
+#include "nsIContentIterator.h"
 
 #ifdef XP_WIN
 #undef GetClassName
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
@@ -1592,16 +1593,101 @@ nsDOMWindowUtils::NodesFromRect(float aX
 
   nsCOMPtr<nsIDocument> doc = GetDocument();
   NS_ENSURE_STATE(doc);
 
   return doc->NodesFromRectHelper(aX, aY, aTopSize, aRightSize, aBottomSize, aLeftSize, 
                                   aIgnoreRootScrollFrame, aFlushLayout, aReturn);
 }
 
+NS_IMETHODIMP
+nsDOMWindowUtils::GetTranslationNodes(nsIDOMNode* aRoot,
+                                      nsITranslationNodeList** aRetVal)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  NS_ENSURE_ARG_POINTER(aRetVal);
+  nsCOMPtr<nsIContent> root = do_QueryInterface(aRoot);
+  NS_ENSURE_STATE(root);
+  nsCOMPtr<nsIDocument> doc = GetDocument();
+  NS_ENSURE_STATE(doc);
+
+  if (root->OwnerDoc() != doc) {
+    return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
+  }
+
+  nsTHashtable<nsPtrHashKey<nsIContent>> translationNodesHash(1000);
+  nsRefPtr<nsTranslationNodeList> list = new nsTranslationNodeList;
+
+  uint32_t limit = 15000;
+
+  // We begin iteration with content->GetNextNode because we want to explictly
+  // skip the root tag from being a translation node.
+  nsIContent* content = root;
+  while ((limit > 0) && (content = content->GetNextNode(root))) {
+    if (!content->IsHTML()) {
+      continue;
+    }
+
+    nsIAtom* localName = content->Tag();
+
+    // Skip elements that usually contain non-translatable text content.
+    if (localName == nsGkAtoms::script ||
+        localName == nsGkAtoms::iframe ||
+        localName == nsGkAtoms::frameset ||
+        localName == nsGkAtoms::frame ||
+        localName == nsGkAtoms::code ||
+        localName == nsGkAtoms::noscript ||
+        localName == nsGkAtoms::style) {
+      continue;
+    }
+
+    // An element is a translation node if it contains
+    // at least one text node that has meaningful data
+    // for translation
+    for (nsIContent* child = content->GetFirstChild();
+         child;
+         child = child->GetNextSibling()) {
+
+      if (child->HasTextForTranslation()) {
+        translationNodesHash.PutEntry(content);
+
+        bool isBlockFrame = false;
+        nsIFrame* frame = content->GetPrimaryFrame();
+        if (frame) {
+          isBlockFrame = frame->IsFrameOfType(nsIFrame::eBlockFrame);
+        }
+
+        bool isTranslationRoot = isBlockFrame;
+        if (!isBlockFrame) {
+          // If an element is not a block element, it still
+          // can be considered a translation root if the parent
+          // of this element didn't make into the list of nodes
+          // to be translated.
+          bool parentInList = false;
+          nsIContent* parent = content->GetParent();
+          if (parent) {
+            parentInList = translationNodesHash.Contains(parent);
+          }
+          isTranslationRoot = !parentInList;
+        }
+
+        list->AppendElement(content->AsDOMNode(), isTranslationRoot);
+        --limit;
+        break;
+      }
+    }
+  }
+
+  *aRetVal = list.forget().take();
+  return NS_OK;
+}
+
 static TemporaryRef<DataSourceSurface>
 CanvasToDataSourceSurface(nsIDOMHTMLCanvasElement* aCanvas)
 {
   nsCOMPtr<nsINode> node = do_QueryInterface(aCanvas);
   if (!node) {
     return nullptr;
   }
 
@@ -3878,8 +3964,45 @@ nsDOMWindowUtils::SetAudioVolume(float a
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
   }
   nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
   NS_ENSURE_STATE(window);
 
   return window->SetAudioVolume(aVolume);
 }
+
+NS_INTERFACE_MAP_BEGIN(nsTranslationNodeList)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_INTERFACE_MAP_ENTRY(nsITranslationNodeList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsTranslationNodeList)
+NS_IMPL_RELEASE(nsTranslationNodeList)
+
+NS_IMETHODIMP
+nsTranslationNodeList::Item(uint32_t aIndex, nsIDOMNode** aRetVal)
+{
+  NS_ENSURE_ARG_POINTER(aRetVal);
+  NS_IF_ADDREF(*aRetVal = mNodes.SafeElementAt(aIndex));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTranslationNodeList::IsTranslationRootAtIndex(uint32_t aIndex, bool* aRetVal)
+{
+  NS_ENSURE_ARG_POINTER(aRetVal);
+  if (aIndex >= mLength) {
+    *aRetVal = false;
+    return NS_OK;
+  }
+
+  *aRetVal = mNodeIsRoot.ElementAt(aIndex);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsTranslationNodeList::GetLength(uint32_t* aRetVal)
+{
+  NS_ENSURE_ARG_POINTER(aRetVal);
+  *aRetVal = mLength;
+  return NS_OK;
+}
--- a/dom/base/nsDOMWindowUtils.h
+++ b/dom/base/nsDOMWindowUtils.h
@@ -20,16 +20,42 @@ class nsPoint;
 class nsIDocument;
 
 namespace mozilla {
   namespace layers {
     class LayerTransactionChild;
   }
 }
 
+class nsTranslationNodeList MOZ_FINAL : public nsITranslationNodeList
+{
+public:
+  nsTranslationNodeList()
+  {
+    mNodes.SetCapacity(1000);
+    mNodeIsRoot.SetCapacity(1000);
+    mLength = 0;
+  }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITRANSLATIONNODELIST
+
+  void AppendElement(nsIDOMNode* aElement, bool aIsRoot)
+  {
+    mNodes.AppendElement(aElement);
+    mNodeIsRoot.AppendElement(aIsRoot);
+    mLength++;
+  }
+
+private:
+  nsTArray<nsCOMPtr<nsIDOMNode> > mNodes;
+  nsTArray<bool> mNodeIsRoot;
+  uint32_t mLength;
+};
+
 class nsDOMWindowUtils MOZ_FINAL : public nsIDOMWindowUtils,
                                    public nsSupportsWeakReference
 {
 public:
   nsDOMWindowUtils(nsGlobalWindow *aWindow);
   ~nsDOMWindowUtils();
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMWINDOWUTILS
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -20,16 +20,18 @@ support-files =
 [test_constructor-assignment.html]
 [test_constructor.html]
 [test_document.all_unqualified.html]
 [test_domcursor.html]
 [test_domrequest.html]
 [test_domwindowutils.html]
 [test_e4x_for_each.html]
 [test_error.html]
+[test_getTranslationNodes.html]
+[test_getTranslationNodes_limit.html]
 [test_gsp-qualified.html]
 [test_gsp-quirks.html]
 [test_gsp-standards.html]
 [test_history_document_open.html]
 [test_history_state_null.html]
 [test_innersize_scrollport.html]
 [test_messageChannel.html]
 [test_messageChannel_cloning.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_getTranslationNodes.html
@@ -0,0 +1,210 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for nsIDOMWindowUtils.getTranslationNodes</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<script type="application/javascript">
+  var utils = SpecialPowers.wrap(window).
+              QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+              getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+
+
+  function testTranslationRoot(rootNode) {
+    var translationNodes = utils.getTranslationNodes(rootNode);
+
+    var expectedResult = rootNode.getAttribute("expected");
+    var expectedLength = expectedResult.split(" ").length;
+
+    is(translationNodes.length, expectedLength,
+       "Correct number of translation nodes for testcase " + rootNode.id);
+
+    var resultList = [];
+    for (var i = 0; i < translationNodes.length; i++) {
+      var node = translationNodes.item(i).localName;
+      if (translationNodes.isTranslationRootAtIndex(i)) {
+        node += "[root]"
+      }
+      resultList.push(node);
+    }
+
+    is(resultList.length, translationNodes.length,
+       "Correct number of translation nodes for testcase " + rootNode.id);
+
+    is(resultList.join(" "), expectedResult,
+       "Correct list of translation nodes for testcase " + rootNode.id);
+  }
+
+  function runTest() {
+    isnot(utils, null, "nsIDOMWindowUtils");
+
+    var testcases = document.querySelectorAll("div[expected]");
+    for (var testcase of testcases) {
+      testTranslationRoot(testcase);
+    }
+
+    var testiframe = document.getElementById("testiframe");
+    var iframediv = testiframe.contentDocument.querySelector("div");
+    try {
+      var foo = utils.getTranslationNodes(iframediv);
+      ok(false, "Cannot use a node from a different document");
+    } catch (e) {
+      is(e.name, "WrongDocumentError", "Cannot use a node from a different document");
+    }
+
+    SimpleTest.finish();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+
+<!-- Test that an inline element inside a root is not a root -->
+<div id="testcase1"
+     expected="div[root] span">
+  <div>
+    lorem ipsum <span>dolor</span> sit amet
+  </div>
+</div>
+
+<!-- Test that a usually inline element becomes a root if it is
+     displayed as a block -->
+<div id="testcase2"
+     expected="div[root] span[root]">
+  <div>
+    lorem ipsum <span style="display: block;">dolor</span> sit amet
+  </div>
+</div>
+
+<!-- Test that the content-less <div> is ignored and only the
+     <p> with content is returned -->
+<div id="testcase3"
+     expected="p[root]">
+  <div>
+    <p>lorem ipsum</p>
+  </div>
+</div>
+
+<!-- Test that an inline element which the parent is not a root
+     becomes a root -->
+<div id="testcase4"
+     expected="span[root]">
+  <div>
+    <span>lorem ipsum</span>
+  </div>
+</div>
+
+<!-- Test siblings -->
+<div id="testcase5"
+     expected="li[root] li[root]">
+  <ul>
+    <li>lorem</li>
+    <li>ipsum</li>
+  </ul>
+</div>
+
+<!-- Test <ul> with content outside li -->
+<div id="testcase6"
+     expected="ul[root] li[root] li[root]">
+  <ul>Lorem
+    <li>lorem</li>
+    <li>ipsum</li>
+  </ul>
+</div>
+
+<!-- Test inline siblings -->
+<div id="testcase7"
+     expected="ul[root] li li">
+  <ul>Lorem
+    <li style="display: inline">lorem</li>
+    <li style="display: inline">ipsum</li>
+  </ul>
+</div>
+
+<!-- Test inline siblings becoming roots -->
+<div id="testcase8"
+     expected="li[root] li[root]">
+  <ul>
+    <li style="display: inline">lorem</li>
+    <li style="display: inline">ipsum</li>
+  </ul>
+</div>
+
+<!-- Test that nodes with only punctuation, whitespace
+     or numbers are ignored -->
+<div id="testcase9"
+     expected="li[root] li[root]">
+  <ul>
+    <li>lorem</li>
+    <li>ipsum</li>
+    <li>-.,;'/!@#$%^*()</li>
+    <li>0123456789</li>
+    <li>
+          </li>
+  </ul>
+</div>
+
+<!-- Test paragraphs -->
+<div id="testcase10"
+     expected="p[root] a b p[root] a b">
+  <p>Lorem ipsum <a href="a.htm">dolor</a> sit <b>amet</b>, consetetur</p>
+  <p>Lorem ipsum <a href="a.htm">dolor</a> sit <b>amet</b>, consetetur</p>
+</div>
+
+<!-- Test that a display:none element is not ignored -->
+<div id="testcase11"
+     expected="p[root] a b">
+  <p>Lorem ipsum <a href="a.htm">dolor</a> sit <b style="display:none">amet</b>, consetetur</p>
+</div>
+
+<!-- Test that deep nesting does not cause useless content to be returned -->
+<div id="testcase12"
+     expected="p[root]">
+  <div>
+    <div>
+      <div>
+        <p>Lorem ipsum</p>
+      </div>
+    </div>
+  </div>
+</div>
+
+<!-- Test that deep nesting does not cause useless content to be returned -->
+<div id="testcase13"
+     expected="div[root] p[root]">
+  <div>Lorem ipsum
+    <div>
+      <div>
+        <p>Lorem ipsum</p>
+      </div>
+    </div>
+  </div>
+</div>
+
+<!-- Test that non-html elements and elements that usually have non-translatable
+     content are ignored -->
+<div id="testcase14"
+     expected="div[root]">
+  <div>
+    Lorem Ipsum
+    <noscript>Lorem Ipsum</noscript>
+    <style>.dummyClass { color: blue; }</style>
+    <script> /* script tag */ </script>
+    <code> code </code>
+    <iframe id="testiframe"
+            src="data:text/html,<div>Lorem ipsum</div>">
+    </iframe>
+    <svg>lorem</svg>
+    <math>ipsum</math>
+  </div>
+</div>
+
+<!-- Test that nesting of inline elements won't produce roots as long as
+     the parents are in the list of translation nodes -->
+<div id="testcase15"
+     expected="p[root] a b span em">
+  <p>Lorem <a>ipsum <b>dolor <span>sit</span> amet</b></a>, <em>consetetur</em></p>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_getTranslationNodes_limit.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for nsIDOMWindowUtils.getTranslationNodes</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<script type="application/javascript">
+  var utils = SpecialPowers.wrap(window).
+              QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor).
+              getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+
+  function runTest() {
+    isnot(utils, null, "nsIDOMWindowUtils");
+
+    for (var i = 0; i < 16000; i++) {
+      var text = document.createTextNode("a");
+      var node = document.createElement("b");
+      node.appendChild(text);
+      document.body.appendChild(node);
+    }
+
+    var translationRoots = utils.getTranslationNodes(document.body);
+    is (translationRoots.length, 15000, "Translation nodes were limited to 15000 nodes.");
+
+    SimpleTest.finish();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -37,18 +37,19 @@ interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMTouch;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsICompositionStringSynthesizer;
+interface nsITranslationNodeList;
 
-[scriptable, uuid(f3148b3e-6db8-4a49-aa5c-de726449054d)]
+[scriptable, uuid(3d977df2-1c0e-4b61-bc21-c6ee757a9191)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -800,16 +801,26 @@ interface nsIDOMWindowUtils : nsISupport
                                in float aY,
                                in float aTopSize, 
                                in float aRightSize,
                                in float aBottomSize,
                                in float aLeftSize,
                                in boolean aIgnoreRootScrollFrame,
                                in boolean aFlushLayout);
 
+
+  /**
+   * Get a list of nodes that have meaningful textual content to
+   * be translated. The implementation of this algorithm is in flux
+   * as we experiment and refine which approach works best.
+   *
+   * This method requires chrome privileges.
+   */
+  nsITranslationNodeList getTranslationNodes(in nsIDOMNode aRoot);
+
   /**
    * Compare the two canvases, returning the number of differing pixels and
    * the maximum difference in a channel.  This will throw an error if
    * the dimensions of the two canvases are different.
    *
    * This method requires chrome privileges.
    */
   uint32_t compareCanvases(in nsIDOMHTMLCanvasElement aCanvas1,
@@ -1623,8 +1634,18 @@ interface nsIDOMWindowUtils : nsISupport
    attribute boolean audioMuted;
 
     /**
      * range: greater or equal to 0. The real volume level is affected by the
      * volume of all ancestor windows.
      */
     attribute float audioVolume;
 };
+
+[scriptable, uuid(c694e359-7227-4392-a138-33c0cc1f15a6)]
+interface nsITranslationNodeList : nsISupports {
+  readonly attribute unsigned long length;
+  nsIDOMNode item(in unsigned long index);
+
+  // A translation root is a block element, or an inline element
+  // which its parent is not a translation node.
+  boolean isTranslationRootAtIndex(in unsigned long index);
+};