Implement ImageAnnotationStatus for accessibility on Windows.

Introduces a new accessibility attribute, ImageAnnotationStatus,
representing the state of automatic annotations on an image.
Based on the status of this attribute, augments the name
and role description of a node for Windows, using localized
strings.

The status will eventually need to be populated by
BlinkAXTreeSource.

TBR=rockot@chromium.org

Bug: 905419

Change-Id: I310be8669ea032cb8ec13c56fbc11bc0ffee53fd
Reviewed-on: https://chromium-review.googlesource.com/c/1471353
Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: Katie Dektar <katie@chromium.org>
Reviewed-by: Will Harris <wfh@chromium.org>
Reviewed-by: Nektarios Paisios <nektar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#633025}
diff --git a/content/app/strings/content_strings.grd b/content/app/strings/content_strings.grd
index 0ce1cc31..bc295409 100644
--- a/content/app/strings/content_strings.grd
+++ b/content/app/strings/content_strings.grd
@@ -245,6 +245,7 @@
         <ph name="WEEK">$1<ex>Week 38, 2014</ex></ph>, starting on <ph name="WEEK_START_DATE">$2<ex>September 15, 2014</ex></ph>
       </message>
 
+      <!-- Localized names for accessibility roles -->
       <message name="IDS_AX_ROLE_ARTICLE" desc="Accessibility role description for article">
         article
       </message>
@@ -604,6 +605,35 @@
         </message>
       </if>
 
+      <!-- Automatic image annotations for accessibility -->
+      <if expr="is_win">
+        <then>
+          <message name="IDS_AX_UNLABELED_IMAGE_ROLE_DESCRIPTION" desc="Accessibility role description for a graphic (image) on a web page that does not have a description for blind users.">
+            Unlabeled graphic
+          </message>
+        </then>
+        <else>
+          <message name="IDS_AX_UNLABELED_IMAGE_ROLE_DESCRIPTION" desc="Accessibility role description for an image on a web page that does not have a description for blind users.">
+            Unlabeled image
+          </message>
+        </else>
+      </if>
+      <message name="IDS_AX_IMAGE_ELIGIBLE_FOR_ANNOTATION" desc="Accessibility message spoken out loud to screen reader users to inform them that they can get a description of an image by opening the context menu.">
+        To get missing image descriptions, open the context menu.
+      </message>
+      <message name="IDS_AX_IMAGE_ANNOTATION_PENDING" desc="Accessibility message spoken out loud to screen reader users saying that the browser is in the middle of trying to get a description for an image.">
+        Getting description...
+      </message>
+      <message name="IDS_AX_IMAGE_ANNOTATION_EMPTY" desc="Accessibility message spoken out loud to screen reader users indicating that there is no description for this image.">
+        No description is available.
+      </message>
+      <message name="IDS_AX_IMAGE_ANNOTATION_ADULT" desc="Accessibility message spoken out loud to screen reader users indicating that an image appears to be something like nudity or pornography or other policy violation that is not appropriate for children.">
+        Appears to be adult content.
+      </message>
+      <message name="IDS_AX_IMAGE_ANNOTATION_PROCESS_FAILED" desc="Accessibility message spoken out loud to screen reader users that the browser was not able to get a description for an image.">
+        Unable to get a description.
+      </message>
+
       <message name="IDS_AX_AM_PM_FIELD_TEXT" desc="Accessible description of the AM/PM field in a date/time control">
         AM/PM
       </message>
diff --git a/content/browser/accessibility/accessibility_tree_formatter_blink.cc b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
index da6578c..250227f1 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_blink.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_blink.cc
@@ -81,6 +81,8 @@
       return ui::ToString(static_cast<ax::mojom::TextDirection>(value));
     case ax::mojom::IntAttribute::kTextPosition:
       return ui::ToString(static_cast<ax::mojom::TextPosition>(value));
+    case ax::mojom::IntAttribute::kImageAnnotationStatus:
+      return ui::ToString(static_cast<ax::mojom::ImageAnnotationStatus>(value));
     // No pretty printing necessary for these:
     case ax::mojom::IntAttribute::kActivedescendantId:
     case ax::mojom::IntAttribute::kAriaCellColumnIndex:
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 4cbb5aa..ed27057c 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -13,9 +13,11 @@
 #include "base/no_destructor.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "content/app/strings/grit/content_strings.h"
 #include "content/browser/accessibility/browser_accessibility_manager.h"
 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
 #include "content/common/accessibility_messages.h"
+#include "content/public/common/content_client.h"
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/accessibility/ax_text_utils.h"
 #include "ui/accessibility/platform/ax_unique_id.h"
@@ -1242,6 +1244,45 @@
   }
 }
 
+base::string16 BrowserAccessibility::GetLocalizedStringForImageAnnotationStatus(
+    ax::mojom::ImageAnnotationStatus status) const {
+  const ContentClient* content_client = content::GetContentClient();
+
+  int message_id = 0;
+  switch (status) {
+    case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
+      message_id = IDS_AX_IMAGE_ELIGIBLE_FOR_ANNOTATION;
+      break;
+    case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
+      message_id = IDS_AX_IMAGE_ANNOTATION_PENDING;
+      break;
+    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
+      message_id = IDS_AX_IMAGE_ANNOTATION_EMPTY;
+      break;
+    case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
+      message_id = IDS_AX_IMAGE_ANNOTATION_ADULT;
+      break;
+    case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
+      message_id = IDS_AX_IMAGE_ANNOTATION_PROCESS_FAILED;
+      break;
+    case ax::mojom::ImageAnnotationStatus::kNone:
+    case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
+    case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
+      return base::string16();
+  }
+
+  DCHECK(message_id);
+
+  return content_client->GetLocalizedString(message_id);
+}
+
+base::string16
+BrowserAccessibility::GetLocalizedRoleDescriptionForUnlabeledImage() const {
+  const ContentClient* content_client = content::GetContentClient();
+  return content_client->GetLocalizedString(
+      IDS_AX_UNLABELED_IMAGE_ROLE_DESCRIPTION);
+}
+
 bool BrowserAccessibility::ShouldIgnoreHoveredStateForTesting() {
   BrowserAccessibilityStateImpl* accessibility_state =
       BrowserAccessibilityStateImpl::GetInstance();
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index b30c3ae..db2ca08e0 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -386,6 +386,9 @@
   int32_t CellIndexToId(int32_t cell_index) const override;
 
   bool AccessibilityPerformAction(const ui::AXActionData& data) override;
+  base::string16 GetLocalizedStringForImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus status) const override;
+  base::string16 GetLocalizedRoleDescriptionForUnlabeledImage() const override;
   bool ShouldIgnoreHoveredStateForTesting() override;
   bool IsOffscreen() const override;
   ui::AXPlatformNode* GetTargetNodeForRelation(
diff --git a/ui/accessibility/ax_enum_util.cc b/ui/accessibility/ax_enum_util.cc
index c9f6a89..ede8a413 100644
--- a/ui/accessibility/ax_enum_util.cc
+++ b/ui/accessibility/ax_enum_util.cc
@@ -1483,6 +1483,8 @@
       return "previousFocusId";
     case ax::mojom::IntAttribute::kNextFocusId:
       return "nextFocusId";
+    case ax::mojom::IntAttribute::kImageAnnotationStatus:
+      return "imageAnnotationStatus";
   }
 
   return "";
@@ -1591,6 +1593,8 @@
     return ax::mojom::IntAttribute::kPreviousFocusId;
   if (0 == strcmp(int_attribute, "nextFocusId"))
     return ax::mojom::IntAttribute::kNextFocusId;
+  if (0 == strcmp(int_attribute, "imageAnnotationStatus"))
+    return ax::mojom::IntAttribute::kImageAnnotationStatus;
   return ax::mojom::IntAttribute::kNone;
 }
 
@@ -2477,4 +2481,49 @@
   return ax::mojom::TreeOrder::kNone;
 }
 
+const char* ToString(ax::mojom::ImageAnnotationStatus status) {
+  switch (status) {
+    case ax::mojom::ImageAnnotationStatus::kNone:
+      return "none";
+    case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
+      return "ineligibleForAnnotation";
+    case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
+      return "eligibleForAnnotation";
+    case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
+      return "annotationPending";
+    case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
+      return "annotationSucceeded";
+    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
+      return "annotationEmpty";
+    case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
+      return "annotationAdult";
+    case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
+      return "annotationProcessFailed";
+  }
+
+  return "";
+}
+
+ax::mojom::ImageAnnotationStatus ParseImageAnnotationStatus(
+    const char* status) {
+  if (0 == strcmp(status, "none"))
+    return ax::mojom::ImageAnnotationStatus::kNone;
+  if (0 == strcmp(status, "ineligibleForAnnotation"))
+    return ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation;
+  if (0 == strcmp(status, "eligibleForAnnotation"))
+    return ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation;
+  if (0 == strcmp(status, "annotationPending"))
+    return ax::mojom::ImageAnnotationStatus::kAnnotationPending;
+  if (0 == strcmp(status, "annotationSucceeded"))
+    return ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded;
+  if (0 == strcmp(status, "annotationEmpty"))
+    return ax::mojom::ImageAnnotationStatus::kAnnotationEmpty;
+  if (0 == strcmp(status, "annotationAdult"))
+    return ax::mojom::ImageAnnotationStatus::kAnnotationAdult;
+  if (0 == strcmp(status, "annotationProcessFailed"))
+    return ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed;
+
+  return ax::mojom::ImageAnnotationStatus::kNone;
+}
+
 }  // namespace ui
diff --git a/ui/accessibility/ax_enum_util.h b/ui/accessibility/ax_enum_util.h
index 91f6b5c..d558163a8 100644
--- a/ui/accessibility/ax_enum_util.h
+++ b/ui/accessibility/ax_enum_util.h
@@ -138,6 +138,11 @@
 AX_EXPORT const char* ToString(ax::mojom::TreeOrder tree_order);
 AX_EXPORT ax::mojom::TreeOrder ParseTreeOrder(const char* tree_order);
 
+// ax::mojom::ImageAnnotationStatus
+AX_EXPORT const char* ToString(ax::mojom::ImageAnnotationStatus status);
+AX_EXPORT ax::mojom::ImageAnnotationStatus ParseImageAnnotationStatus(
+    const char* status);
+
 }  // namespace ui
 
 #endif  // UI_ACCESSIBILITY_AX_ENUM_UTIL_H_
diff --git a/ui/accessibility/ax_enum_util_unittest.cc b/ui/accessibility/ax_enum_util_unittest.cc
index 754886e..4b248fc2 100644
--- a/ui/accessibility/ax_enum_util_unittest.cc
+++ b/ui/accessibility/ax_enum_util_unittest.cc
@@ -195,4 +195,9 @@
   TestEnumStringConversion<ax::mojom::TreeOrder>(ParseTreeOrder);
 }
 
+TEST(AXEnumUtilTest, ImageAnnotationStatus) {
+  TestEnumStringConversion<ax::mojom::ImageAnnotationStatus>(
+      ParseImageAnnotationStatus);
+}
+
 }  // namespace ui
diff --git a/ui/accessibility/ax_enums.mojom b/ui/accessibility/ax_enums.mojom
index d736341..26546b2 100644
--- a/ui/accessibility/ax_enums.mojom
+++ b/ui/accessibility/ax_enums.mojom
@@ -452,7 +452,7 @@
   kFontFamily,
   kHtmlTag,
   // Stores an automatic image annotation if one is available. Only valid on
-  // ax::mojom::Role::kImage.
+  // ax::mojom::Role::kImage. See kImageAnnotationStatus, too.
   kImageAnnotation,
   kImageDataUrl,
   kInnerHtml,
@@ -573,6 +573,9 @@
    // Focus traversal in views and Android.
   kPreviousFocusId,
   kNextFocusId,
+
+   // Image annotation status, of type ImageAnnotationStatus.
+  kImageAnnotationStatus,
 };
 
 enum FloatAttribute {
@@ -894,3 +897,34 @@
   kUnknown,  // The Tree ID is unknown.
   kToken,    // Every other tree ID must have a valid unguessable token.
 };
+
+enum ImageAnnotationStatus {
+   // Not an image, or image annotation feature not enabled.
+  kNone,
+
+   // Not loaded yet, already labeled by the author, or not eligible
+   // due to size, type, etc.
+  kIneligibleForAnnotation,
+
+   // Eligible to be automatically annotated if the user requests it.
+  kEligibleForAnnotation,
+
+   // An annotation has been requested but has not been received yet.
+  kAnnotationPending,
+
+   // An annotation has been provided and kImageAnnotation contains the
+   // annotation text.
+  kAnnotationSucceeded,
+
+   // The annotation request was processed successfully, but it was not
+   // possible to come up with an annotation for this image.
+  kAnnotationEmpty,
+
+   // The image is classified as adult content and no annotation will
+   // be generated.
+  kAnnotationAdult,
+
+   // The annotation process failed, e.g. unable to contact the server,
+   // request timed out, etc.
+  kAnnotationProcessFailed,
+};
diff --git a/ui/accessibility/ax_node_data.cc b/ui/accessibility/ax_node_data.cc
index 943ea1c..74ecc71 100644
--- a/ui/accessibility/ax_node_data.cc
+++ b/ui/accessibility/ax_node_data.cc
@@ -151,6 +151,7 @@
     case ax::mojom::IntAttribute::kAriaCellColumnIndex:
     case ax::mojom::IntAttribute::kAriaRowCount:
     case ax::mojom::IntAttribute::kAriaCellRowIndex:
+    case ax::mojom::IntAttribute::kImageAnnotationStatus:
       return false;
   }
 
@@ -752,6 +753,21 @@
   }
 }
 
+ax::mojom::ImageAnnotationStatus AXNodeData::GetImageAnnotationStatus() const {
+  return static_cast<ax::mojom::ImageAnnotationStatus>(
+      GetIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus));
+}
+
+void AXNodeData::SetImageAnnotationStatus(
+    ax::mojom::ImageAnnotationStatus status) {
+  if (HasIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus))
+    RemoveIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus);
+  if (status != ax::mojom::ImageAnnotationStatus::kNone) {
+    AddIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus,
+                    static_cast<int32_t>(status));
+  }
+}
+
 ax::mojom::Restriction AXNodeData::GetRestriction() const {
   return static_cast<ax::mojom::Restriction>(
       GetIntAttribute(ax::mojom::IntAttribute::kRestriction));
@@ -1100,6 +1116,11 @@
       case ax::mojom::IntAttribute::kPreviousFocusId:
         result += " previous_focus_id=" + value;
         break;
+      case ax::mojom::IntAttribute::kImageAnnotationStatus:
+        result += std::string(" image_annotation_status=") +
+                  ui::ToString(static_cast<ax::mojom::ImageAnnotationStatus>(
+                      int_attribute.second));
+        break;
       case ax::mojom::IntAttribute::kNone:
         break;
     }
diff --git a/ui/accessibility/ax_node_data.h b/ui/accessibility/ax_node_data.h
index 3a26596..fbbadb75 100644
--- a/ui/accessibility/ax_node_data.h
+++ b/ui/accessibility/ax_node_data.h
@@ -173,6 +173,8 @@
   void SetRestriction(ax::mojom::Restriction restriction);
   ax::mojom::TextDirection GetTextDirection() const;
   void SetTextDirection(ax::mojom::TextDirection text_direction);
+  ax::mojom::ImageAnnotationStatus GetImageAnnotationStatus() const;
+  void SetImageAnnotationStatus(ax::mojom::ImageAnnotationStatus status);
 
   // Return a string representation of this data, for debugging.
   virtual std::string ToString() const;
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index 9b37614..ba8e839 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -391,6 +391,15 @@
   return value;
 }
 
+base::string16 AXPlatformNodeBase::GetRoleDescription() const {
+  if (GetData().GetImageAnnotationStatus() ==
+      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation) {
+    return GetDelegate()->GetLocalizedRoleDescriptionForUnlabeledImage();
+  }
+
+  return GetString16Attribute(ax::mojom::StringAttribute::kRoleDescription);
+}
+
 AXPlatformNodeBase* AXPlatformNodeBase::GetSelectionContainer() const {
   if (!delegate_)
     return nullptr;
@@ -671,8 +680,13 @@
     AddAttributeToList("autocomplete", "list", attributes);
   }
 
-  AddAttributeToList(ax::mojom::StringAttribute::kRoleDescription,
-                     "roledescription", attributes);
+  base::string16 role_description = GetRoleDescription();
+  if (!role_description.empty() ||
+      HasStringAttribute(ax::mojom::StringAttribute::kRoleDescription)) {
+    AddAttributeToList("roledescription", base::UTF16ToUTF8(role_description),
+                       attributes);
+  }
+
   AddAttributeToList(ax::mojom::StringAttribute::kKeyShortcuts, "keyshortcuts",
                      attributes);
 
diff --git a/ui/accessibility/platform/ax_platform_node_base.h b/ui/accessibility/platform/ax_platform_node_base.h
index d3a83f39..0b2b491 100644
--- a/ui/accessibility/platform/ax_platform_node_base.h
+++ b/ui/accessibility/platform/ax_platform_node_base.h
@@ -230,6 +230,10 @@
   // text found in any embedded object.
   std::string GetInnerText() const;
 
+  // Get the role description from the node data or from the image annotation
+  // status.
+  base::string16 GetRoleDescription() const;
+
   // Cast a gfx::NativeViewAccessible to an AXPlatformNodeBase if it is one,
   // or return NULL if it's not an instance of this class.
   static AXPlatformNodeBase* FromNativeViewAccessible(
diff --git a/ui/accessibility/platform/ax_platform_node_delegate.h b/ui/accessibility/platform/ax_platform_node_delegate.h
index 6f3ad02..3cc239d2 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -179,6 +179,15 @@
   virtual bool AccessibilityPerformAction(const AXActionData& data) = 0;
 
   //
+  // Localized strings.
+  //
+
+  virtual base::string16 GetLocalizedRoleDescriptionForUnlabeledImage()
+      const = 0;
+  virtual base::string16 GetLocalizedStringForImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus status) const = 0;
+
+  //
   // Testing.
   //
 
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.cc b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
index d4501b2..4fe9726 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -170,6 +170,18 @@
   return false;
 }
 
+base::string16
+AXPlatformNodeDelegateBase::GetLocalizedStringForImageAnnotationStatus(
+    ax::mojom::ImageAnnotationStatus status) const {
+  return base::string16();
+}
+
+base::string16
+AXPlatformNodeDelegateBase::GetLocalizedRoleDescriptionForUnlabeledImage()
+    const {
+  return base::string16();
+}
+
 bool AXPlatformNodeDelegateBase::ShouldIgnoreHoveredStateForTesting() {
   return true;
 }
diff --git a/ui/accessibility/platform/ax_platform_node_delegate_base.h b/ui/accessibility/platform/ax_platform_node_delegate_base.h
index 93d15b5..726dfc0 100644
--- a/ui/accessibility/platform/ax_platform_node_delegate_base.h
+++ b/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -157,6 +157,14 @@
   bool AccessibilityPerformAction(const AXActionData& data) override;
 
   //
+  // Localized strings.
+  //
+
+  base::string16 GetLocalizedStringForImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus status) const override;
+  base::string16 GetLocalizedRoleDescriptionForUnlabeledImage() const override;
+
+  //
   // Testing.
   //
 
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index d893e15..9f847b9 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -184,6 +184,18 @@
 // cursor keys are used to scroll a webpage.
 constexpr float kSmallScrollIncrement = 40.0f;
 
+void AppendTextToString(base::string16 extra_text, base::string16* string) {
+  if (extra_text.empty())
+    return;
+
+  if (string->empty()) {
+    *string = extra_text;
+    return;
+  }
+
+  *string += base::string16(L". ") + extra_text;
+}
+
 }  // namespace
 
 void AXPlatformNodeWin::AddAttributeToList(const char* name,
@@ -797,18 +809,47 @@
       ax::mojom::StringAttribute::kKeyShortcuts, acc_key);
 }
 
-IFACEMETHODIMP AXPlatformNodeWin::get_accName(VARIANT var_id, BSTR* name) {
+IFACEMETHODIMP AXPlatformNodeWin::get_accName(VARIANT var_id, BSTR* name_bstr) {
   WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_ACC_NAME);
   AXPlatformNodeWin* target;
-  COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, name, target);
+  COM_OBJECT_VALIDATE_VAR_ID_1_ARG_AND_GET_TARGET(var_id, name_bstr, target);
 
   for (IAccessible2UsageObserver& observer :
        GetIAccessible2UsageObserverList()) {
     observer.OnAccNameCalled();
   }
 
-  return target->GetStringAttributeAsBstr(ax::mojom::StringAttribute::kName,
-                                          name);
+  bool has_name = target->HasStringAttribute(ax::mojom::StringAttribute::kName);
+  base::string16 name =
+      target->GetString16Attribute(ax::mojom::StringAttribute::kName);
+  auto status = GetData().GetImageAnnotationStatus();
+  switch (status) {
+    case ax::mojom::ImageAnnotationStatus::kNone:
+    case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
+      break;
+
+    case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
+    case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
+    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
+    case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
+    case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
+      AppendTextToString(
+          GetDelegate()->GetLocalizedStringForImageAnnotationStatus(status),
+          &name);
+      break;
+
+    case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
+      AppendTextToString(
+          GetString16Attribute(ax::mojom::StringAttribute::kImageAnnotation),
+          &name);
+      break;
+  }
+
+  if (name.empty() && !has_name)
+    return S_FALSE;
+
+  *name_bstr = SysAllocString(name.c_str());
+  return S_OK;
 }
 
 IFACEMETHODIMP AXPlatformNodeWin::get_accParent(IDispatch** disp_parent) {
@@ -1258,8 +1299,9 @@
   COM_OBJECT_VALIDATE_1_ARG(localized_extended_role);
   AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
 
-  return GetStringAttributeAsBstr(ax::mojom::StringAttribute::kRoleDescription,
-                                  localized_extended_role);
+  base::string16 role_description = GetRoleDescription();
+  *localized_extended_role = SysAllocString(role_description.c_str());
+  return S_OK;
 }
 
 //
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index 6b35cc7..2736b8d 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -2387,6 +2387,173 @@
   EXPECT_STREQ(L"extended role", role);
 }
 
+TEST_F(AXPlatformNodeWinTest, TestUnlabeledImageRoleDescription) {
+  AXNodeData root;
+  root.id = 1;
+  root.SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
+  Init(root);
+
+  ComPtr<IAccessible> root_obj(GetRootIAccessible());
+  ComPtr<IAccessible2> iaccessible2 = ToIAccessible2(root_obj);
+  ScopedBstr role_description;
+  EXPECT_EQ(S_OK, iaccessible2->get_localizedExtendedRole(
+                      role_description.Receive()));
+  EXPECT_STREQ(L"Unlabeled image", role_description);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestUnlabeledImageAttributes) {
+  AXNodeData root;
+  root.id = 1;
+  root.SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
+  Init(root);
+
+  ComPtr<IAccessible> root_obj(GetRootIAccessible());
+  ComPtr<IAccessible2> iaccessible2 = ToIAccessible2(root_obj);
+
+  ScopedBstr attributes_bstr;
+  EXPECT_EQ(S_OK, iaccessible2->get_attributes(attributes_bstr.Receive()));
+  base::string16 attributes(attributes_bstr);
+
+  std::vector<base::string16> attribute_vector = base::SplitString(
+      attributes, L";", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  bool found = false;
+  for (base::string16 attribute : attribute_vector) {
+    if (attribute == L"roledescription:Unlabeled image")
+      found = true;
+  }
+  EXPECT_TRUE(found);
+}
+
+TEST_F(AXPlatformNodeWinTest, TestAnnotatedImageName) {
+  std::vector<const wchar_t*> expected_names;
+
+  AXTreeUpdate tree;
+  tree.root_id = 1;
+  tree.nodes.resize(10);
+  tree.nodes[0].id = 1;
+  tree.nodes[0].child_ids = {2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+  // If the status is EligibleForAnnotation and there's no existing label,
+  // the name should be the discoverability string.
+  tree.nodes[1].id = 2;
+  tree.nodes[1].role = ax::mojom::Role::kImage;
+  tree.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                                   "Annotation");
+  tree.nodes[1].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
+  expected_names.push_back(
+      L"To get missing image descriptions, open the context menu.");
+
+  // If the status is EligibleForAnnotation, the discoverability string
+  // should be appended to the existing name.
+  tree.nodes[2].id = 3;
+  tree.nodes[2].role = ax::mojom::Role::kImage;
+  tree.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                                   "Annotation");
+  tree.nodes[2].SetName("ExistingLabel");
+  tree.nodes[2].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
+  expected_names.push_back(
+      L"ExistingLabel. To get missing image descriptions, open the context "
+      L"menu.");
+
+  // If the status is IneligibleForAnnotation, nothing should be appended.
+  tree.nodes[3].id = 4;
+  tree.nodes[3].role = ax::mojom::Role::kImage;
+  tree.nodes[3].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                                   "Annotation");
+  tree.nodes[3].SetName("ExistingLabel");
+  tree.nodes[3].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation);
+  expected_names.push_back(L"ExistingLabel");
+
+  // If the status is AnnotationPending, pending text should be appended
+  // to the name.
+  tree.nodes[4].id = 5;
+  tree.nodes[4].role = ax::mojom::Role::kImage;
+  tree.nodes[4].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                                   "Annotation");
+  tree.nodes[4].SetName("ExistingLabel");
+  tree.nodes[4].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kAnnotationPending);
+  expected_names.push_back(L"ExistingLabel. Getting description...");
+
+  // If the status is AnnotationSucceeded, and there's no annotation,
+  // nothing should be appended. (Ideally this shouldn't happen.)
+  tree.nodes[5].id = 6;
+  tree.nodes[5].role = ax::mojom::Role::kImage;
+  tree.nodes[5].SetName("ExistingLabel");
+  tree.nodes[5].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded);
+  expected_names.push_back(L"ExistingLabel");
+
+  // If the status is AnnotationSucceeded, the annotation should be appended
+  // to the existing label.
+  tree.nodes[6].id = 7;
+  tree.nodes[6].role = ax::mojom::Role::kImage;
+  tree.nodes[6].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                                   "Annotation");
+  tree.nodes[6].SetName("ExistingLabel");
+  tree.nodes[6].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded);
+  expected_names.push_back(L"ExistingLabel. Annotation");
+
+  // If the status is AnnotationEmpty, failure text should be appended
+  // to the name.
+  tree.nodes[7].id = 8;
+  tree.nodes[7].role = ax::mojom::Role::kImage;
+  tree.nodes[7].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                                   "Annotation");
+  tree.nodes[7].SetName("ExistingLabel");
+  tree.nodes[7].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kAnnotationEmpty);
+  expected_names.push_back(L"ExistingLabel. No description is available.");
+
+  // If the status is AnnotationAdult, appropriate text should be appended
+  // to the name.
+  tree.nodes[8].id = 9;
+  tree.nodes[8].role = ax::mojom::Role::kImage;
+  tree.nodes[8].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                                   "Annotation");
+  tree.nodes[8].SetName("ExistingLabel");
+  tree.nodes[8].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kAnnotationAdult);
+  expected_names.push_back(L"ExistingLabel. Appears to be adult content.");
+
+  // If the status is AnnotationProcessFailed, appropriate text should be
+  // appended to the name.
+  tree.nodes[9].id = 10;
+  tree.nodes[9].role = ax::mojom::Role::kImage;
+  tree.nodes[9].AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
+                                   "Annotation");
+  tree.nodes[9].SetName("ExistingLabel");
+  tree.nodes[9].SetImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed);
+  expected_names.push_back(L"ExistingLabel. Unable to get a description.");
+
+  // We should have one expected name per child of the root.
+  ASSERT_EQ(expected_names.size(), tree.nodes[0].child_ids.size());
+  int child_count = static_cast<int>(expected_names.size());
+
+  Init(tree);
+
+  ComPtr<IAccessible> root_obj(GetRootIAccessible());
+
+  for (int child_index = 0; child_index < child_count; child_index++) {
+    ComPtr<IDispatch> child_dispatch;
+    ASSERT_HRESULT_SUCCEEDED(root_obj->get_accChild(
+        ScopedVariant(child_index + 1), &child_dispatch));
+    ComPtr<IAccessible> child;
+    ASSERT_HRESULT_SUCCEEDED(child_dispatch.As(&child));
+
+    ScopedBstr name;
+    EXPECT_EQ(S_OK, child->get_accName(SELF, name.Receive()));
+    EXPECT_STREQ(expected_names[child_index], name);
+  }
+}
+
 TEST_F(AXPlatformNodeWinTest, TestIAccessibleTextGetNCharacters) {
   AXNodeData root;
   root.id = 0;
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index 108add630..ae328c9 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -7,6 +7,7 @@
 #include <unordered_map>
 
 #include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/accessibility/ax_table_info.h"
 #include "ui/accessibility/ax_tree_observer.h"
@@ -367,6 +368,35 @@
   return true;
 }
 
+base::string16 TestAXNodeWrapper::GetLocalizedRoleDescriptionForUnlabeledImage()
+    const {
+  return base::ASCIIToUTF16("Unlabeled image");
+}
+
+base::string16 TestAXNodeWrapper::GetLocalizedStringForImageAnnotationStatus(
+    ax::mojom::ImageAnnotationStatus status) const {
+  switch (status) {
+    case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
+      return base::ASCIIToUTF16(
+          "To get missing image descriptions, open the context menu.");
+    case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
+      return base::ASCIIToUTF16("Getting description...");
+    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
+      return base::ASCIIToUTF16("No description is available.");
+    case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
+      return base::ASCIIToUTF16("Appears to be adult content.");
+    case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
+      return base::ASCIIToUTF16("Unable to get a description.");
+    case ax::mojom::ImageAnnotationStatus::kNone:
+    case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
+    case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
+      return base::string16();
+  }
+
+  NOTREACHED();
+  return base::string16();
+}
+
 bool TestAXNodeWrapper::ShouldIgnoreHoveredStateForTesting() {
   return true;
 }
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.h b/ui/accessibility/platform/test_ax_node_wrapper.h
index 4513537c..9993b5d 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -76,6 +76,9 @@
   int32_t CellIndexToId(int32_t cell_index) const override;
   gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override;
   bool AccessibilityPerformAction(const AXActionData& data) override;
+  base::string16 GetLocalizedRoleDescriptionForUnlabeledImage() const override;
+  base::string16 GetLocalizedStringForImageAnnotationStatus(
+      ax::mojom::ImageAnnotationStatus status) const override;
   bool ShouldIgnoreHoveredStateForTesting() override;
   const ui::AXUniqueId& GetUniqueId() const override;
   std::set<AXPlatformNode*> GetReverseRelations(