diff --git a/components/BUILD.gn b/components/BUILD.gn
index 0ca8e4d387f56..3077d05f1b289 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -128,6 +128,7 @@ test("components_unittests") {
     "//components/omnibox/browser:unit_tests",
     "//components/open_from_clipboard:unit_tests",
     "//components/os_crypt:unit_tests",
+    "//components/page_image_annotation/core:unit_tests",
     "//components/password_manager/core/browser:unit_tests",
     "//components/password_manager/core/common:unit_tests",
     "//components/payments/core:unit_tests",
diff --git a/components/page_image_annotation/DEPS b/components/page_image_annotation/DEPS
new file mode 100644
index 0000000000000..e798118442ade
--- /dev/null
+++ b/components/page_image_annotation/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  # Page image annotation is a layered component; subdirectories must explicitly
+  # introduce the ability to use the content layer as appropriate.
+  "-components/page_image_annotation/content",
+]
diff --git a/components/page_image_annotation/OWNERS b/components/page_image_annotation/OWNERS
new file mode 100644
index 0000000000000..b2737f856ac43
--- /dev/null
+++ b/components/page_image_annotation/OWNERS
@@ -0,0 +1,3 @@
+amoylan@chromium.org
+dmazzoni@chromium.org
+martis@chromium.org
diff --git a/components/page_image_annotation/README.md b/components/page_image_annotation/README.md
new file mode 100644
index 0000000000000..142c3dc01a684
--- /dev/null
+++ b/components/page_image_annotation/README.md
@@ -0,0 +1,11 @@
+# //components/page_image_annotation
+
+Library for using the image annotation service on images on webpages.
+
+The image annotation service performs general image processing / labeling tasks
+in Chromium. This library enables use of the service with webpages; for example,
+by tracking images on webpages and sending their pixel data to the service.
+
+This is a layered component
+(https://sites.google.com/a/chromium.org/dev/developers/design-documents/layered-components-design)
+which allows it to be shared cleanly on iOS.
diff --git a/components/page_image_annotation/content/DEPS b/components/page_image_annotation/content/DEPS
new file mode 100644
index 0000000000000..e67f75c8b196f
--- /dev/null
+++ b/components/page_image_annotation/content/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+components/page_image_annotation/core",
+  "+content/public/common",
+]
diff --git a/components/page_image_annotation/content/renderer/BUILD.gn b/components/page_image_annotation/content/renderer/BUILD.gn
new file mode 100644
index 0000000000000..02e13a137aefd
--- /dev/null
+++ b/components/page_image_annotation/content/renderer/BUILD.gn
@@ -0,0 +1,17 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("renderer") {
+  sources = [
+    "content_page_annotator_driver.cc",
+    "content_page_annotator_driver.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/page_image_annotation/core",
+    "//content/public/common",
+    "//content/public/renderer",
+  ]
+}
diff --git a/components/page_image_annotation/content/renderer/DEPS b/components/page_image_annotation/content/renderer/DEPS
new file mode 100644
index 0000000000000..083448299f9f4
--- /dev/null
+++ b/components/page_image_annotation/content/renderer/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+components/page_image_annotation/core",
+  "+content/public/renderer",
+  "+ui/base",
+]
diff --git a/components/page_image_annotation/content/renderer/content_page_annotator_driver.cc b/components/page_image_annotation/content/renderer/content_page_annotator_driver.cc
new file mode 100644
index 0000000000000..1fc6f8cc51e59
--- /dev/null
+++ b/components/page_image_annotation/content/renderer/content_page_annotator_driver.cc
@@ -0,0 +1,45 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/page_image_annotation/content/renderer/content_page_annotator_driver.h"
+
+#include "content/public/renderer/render_frame.h"
+
+namespace page_image_annotation {
+
+ContentPageAnnotatorDriver::ContentPageAnnotatorDriver(
+    content::RenderFrame* const render_frame)
+    : RenderFrameObserver(render_frame),
+      RenderFrameObserverTracker<ContentPageAnnotatorDriver>(render_frame) {}
+
+ContentPageAnnotatorDriver::~ContentPageAnnotatorDriver() {}
+
+// static
+ContentPageAnnotatorDriver* ContentPageAnnotatorDriver::GetOrCreate(
+    content::RenderFrame* const render_frame) {
+  ContentPageAnnotatorDriver* const existing = Get(render_frame);
+  if (existing)
+    return existing;
+
+  return new ContentPageAnnotatorDriver(render_frame);
+}
+
+PageAnnotator& ContentPageAnnotatorDriver::GetPageAnnotator() {
+  return page_annotator_;
+}
+
+void ContentPageAnnotatorDriver::DidCommitProvisionalLoad(
+    const bool /* is_same_document_navigation */,
+    const ui::PageTransition /* transition */) {
+  // TODO(crbug.com/915076): schedule repeated DOM traversals to track image
+  //                         addition / modification / removal.
+}
+
+void ContentPageAnnotatorDriver::OnDestruct() {
+  // TODO(crbug.com/915076): cancel DOM traversal.
+
+  delete this;
+}
+
+}  // namespace page_image_annotation
diff --git a/components/page_image_annotation/content/renderer/content_page_annotator_driver.h b/components/page_image_annotation/content/renderer/content_page_annotator_driver.h
new file mode 100644
index 0000000000000..826df9ce57514
--- /dev/null
+++ b/components/page_image_annotation/content/renderer/content_page_annotator_driver.h
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAGE_IMAGE_ANNOTATION_CONTENT_RENDERER_CONTENT_PAGE_ANNOTATOR_DRIVER_H_
+#define COMPONENTS_PAGE_IMAGE_ANNOTATION_CONTENT_RENDERER_CONTENT_PAGE_ANNOTATOR_DRIVER_H_
+
+#include "base/macros.h"
+#include "components/page_image_annotation/core/page_annotator.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "ui/base/page_transition_types.h"
+
+namespace page_image_annotation {
+
+class ContentPageAnnotatorDriver
+    : public content::RenderFrameObserver,
+      public content::RenderFrameObserverTracker<ContentPageAnnotatorDriver> {
+ public:
+  ~ContentPageAnnotatorDriver() override;
+
+  static ContentPageAnnotatorDriver* GetOrCreate(
+      content::RenderFrame* render_frame);
+
+  PageAnnotator& GetPageAnnotator();
+
+ private:
+  // We delete ourselves on frame destruction, so disallow construction on the
+  // stack.
+  ContentPageAnnotatorDriver(content::RenderFrame* render_frame);
+
+  // content::RenderFrameObserver:
+  void DidCommitProvisionalLoad(bool is_same_document_navigation,
+                                ui::PageTransition transition) override;
+  void OnDestruct() override;
+
+  PageAnnotator page_annotator_;
+
+  DISALLOW_COPY_AND_ASSIGN(ContentPageAnnotatorDriver);
+};
+
+}  // namespace page_image_annotation
+
+#endif  //  COMPONENTS_PAGE_IMAGE_ANNOTATION_CONTENT_RENDERER_CONTENT_PAGE_ANNOTATOR_DRIVER_H_
diff --git a/components/page_image_annotation/core/BUILD.gn b/components/page_image_annotation/core/BUILD.gn
new file mode 100644
index 0000000000000..396b16d84bcaf
--- /dev/null
+++ b/components/page_image_annotation/core/BUILD.gn
@@ -0,0 +1,29 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+static_library("core") {
+  sources = [
+    "page_annotator.cc",
+    "page_annotator.h",
+  ]
+
+  deps = [
+    "//base",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [
+    "page_annotator_unittest.cc",
+  ]
+
+  deps = [
+    ":core",
+    "//base",
+    "//base/test:test_support",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/page_image_annotation/core/page_annotator.cc b/components/page_image_annotation/core/page_annotator.cc
new file mode 100644
index 0000000000000..3f516ec634537
--- /dev/null
+++ b/components/page_image_annotation/core/page_annotator.cc
@@ -0,0 +1,66 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/page_image_annotation/core/page_annotator.h"
+
+namespace page_image_annotation {
+
+PageAnnotator::Observer::~Observer() {}
+
+PageAnnotator::Subscription::Subscription(
+    const Observer* const observer,
+    base::WeakPtr<PageAnnotator> page_annotator)
+    : observer_(observer), page_annotator_(page_annotator) {}
+
+PageAnnotator::Subscription::Subscription(Subscription&& subscription) =
+    default;
+
+PageAnnotator::Subscription::~Subscription() {
+  Cancel();
+}
+
+void PageAnnotator::Subscription::Cancel() {
+  if (page_annotator_)
+    page_annotator_->RemoveObserver(observer_);
+}
+
+PageAnnotator::PageAnnotator() : weak_ptr_factory_(this) {}
+
+PageAnnotator::~PageAnnotator() {}
+
+void PageAnnotator::ImageAdded(const uint64_t node_id,
+                               const std::string& source_id) {
+  // TODO(crbug.com/916363): create a connection to the image annotation service
+  //                         for this image.
+  for (Observer& observer : observers_) {
+    observer.OnImageAdded(node_id);
+  }
+}
+
+void PageAnnotator::ImageModified(const uint64_t node_id,
+                                  const std::string& source_id) {
+  // TODO(crbug.com/916363): reset the service connection for this image.
+
+  for (Observer& observer : observers_) {
+    observer.OnImageModified(node_id);
+  }
+}
+
+void PageAnnotator::ImageRemoved(const uint64_t node_id) {
+  // TODO(crbug.com/916363): close the service connection for this image.
+  for (Observer& observer : observers_) {
+    observer.OnImageRemoved(node_id);
+  }
+}
+
+PageAnnotator::Subscription PageAnnotator::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+  return Subscription(observer, weak_ptr_factory_.GetWeakPtr());
+}
+
+void PageAnnotator::RemoveObserver(const Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+}  // namespace page_image_annotation
diff --git a/components/page_image_annotation/core/page_annotator.h b/components/page_image_annotation/core/page_annotator.h
new file mode 100644
index 0000000000000..b6edf1e1f9699
--- /dev/null
+++ b/components/page_image_annotation/core/page_annotator.h
@@ -0,0 +1,90 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PAGE_IMAGE_ANNOTATION_CORE_PAGE_ANNOTATOR_H_
+#define COMPONENTS_PAGE_IMAGE_ANNOTATION_CORE_PAGE_ANNOTATOR_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
+
+namespace page_image_annotation {
+
+// Notifies clients of page images that can be annotated and forwards annotation
+// requests for these images to the image annotation service.
+//
+// TODO(crbug.com/916363): this class is not yet complete - add more logic (e.g.
+//                         communication with the service).
+class PageAnnotator {
+ public:
+  // Clients (i.e. classes that annotate page images) should implement this
+  // interface.
+  class Observer : public base::CheckedObserver {
+   public:
+    ~Observer() override;
+
+    // These methods are called during page lifecycle to notify the observer
+    // about changes to page images.
+
+    // Called exactly once per image, at the point that the image appears on the
+    // page (or at the point that the observer subscribes to the page annotator,
+    // if the image already exists on page).
+    virtual void OnImageAdded(uint64_t node_id) = 0;
+
+    // Called at the point that an image source is updated.
+    virtual void OnImageModified(uint64_t node_id) = 0;
+
+    // Called at the point that an image disappears from the page.
+    virtual void OnImageRemoved(uint64_t node_id) = 0;
+  };
+
+  // A subscription instance must be held by each observer of the page
+  // annotator; an observer will receive updates from the page annotator until
+  // the Cancel method of the subscription is called (this occurs automatically
+  // on subscription destruction).
+  //
+  // Typically, both the page annotator and its observers are scoped to the
+  // lifetime of a render frame. Destruction of such objects can proceed in an
+  // unspecified order, so subscriptions are used to ensure the page annotator
+  // only communicates with an observers that are still alive.
+  class Subscription {
+   public:
+    Subscription(const Observer* observer,
+                 base::WeakPtr<PageAnnotator> page_annotator);
+    Subscription(Subscription&& subscription);
+    ~Subscription();
+
+    // Unsubscribe from updates from the page annotator.
+    void Cancel();
+
+   private:
+    const Observer* observer_;
+    base::WeakPtr<PageAnnotator> page_annotator_;
+
+    DISALLOW_COPY_AND_ASSIGN(Subscription);
+  };
+
+  PageAnnotator();
+  ~PageAnnotator();
+
+  // Called by platform drivers.
+  void ImageAdded(uint64_t node_id, const std::string& source_id);
+  void ImageModified(uint64_t node_id, const std::string& source_id);
+  void ImageRemoved(uint64_t node_id);
+
+  Subscription AddObserver(Observer* observer) WARN_UNUSED_RESULT;
+
+ private:
+  void RemoveObserver(const Observer* observer);
+
+  base::ObserverList<Observer> observers_;
+  base::WeakPtrFactory<PageAnnotator> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(PageAnnotator);
+};
+
+}  // namespace page_image_annotation
+
+#endif  // COMPONENTS_PAGE_IMAGE_ANNOTATION_CORE_PAGE_ANNOTATOR_H_
diff --git a/components/page_image_annotation/core/page_annotator_unittest.cc b/components/page_image_annotation/core/page_annotator_unittest.cc
new file mode 100644
index 0000000000000..eca2cbbed5498
--- /dev/null
+++ b/components/page_image_annotation/core/page_annotator_unittest.cc
@@ -0,0 +1,64 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/page_image_annotation/core/page_annotator.h"
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace page_image_annotation {
+
+using testing::Eq;
+
+// Tests that destroying subscriptions successfully prevents notifications.
+TEST(PageAnnotatorTest, Subscriptions) {
+  class TestObserver : public PageAnnotator::Observer {
+   public:
+    TestObserver(PageAnnotator* const page_annotator)
+        : sub_(page_annotator->AddObserver(this)), last_id_(0ul) {}
+
+    void OnImageAdded(const uint64_t node_id) override { last_id_ = node_id; }
+    void OnImageModified(const uint64_t node_id) override {
+      last_id_ = node_id;
+    }
+    void OnImageRemoved(const uint64_t node_id) override { last_id_ = node_id; }
+
+    PageAnnotator::Subscription sub_;
+    uint64_t last_id_;
+  };
+
+  PageAnnotator page_annotator;
+  TestObserver o1(&page_annotator), o2(&page_annotator);
+
+  page_annotator.ImageAdded(1ul, "test.jpg");
+  EXPECT_THAT(o1.last_id_, Eq(1ul));
+  EXPECT_THAT(o2.last_id_, Eq(1ul));
+
+  page_annotator.ImageAdded(2ul, "example.png");
+  EXPECT_THAT(o1.last_id_, Eq(2ul));
+  EXPECT_THAT(o2.last_id_, Eq(2ul));
+
+  page_annotator.ImageModified(1ul, "demo.gif");
+  EXPECT_THAT(o1.last_id_, Eq(1ul));
+  EXPECT_THAT(o2.last_id_, Eq(1ul));
+
+  page_annotator.ImageRemoved(2ul);
+  EXPECT_THAT(o1.last_id_, Eq(2ul));
+  EXPECT_THAT(o2.last_id_, Eq(2ul));
+
+  o1.sub_.Cancel();
+  page_annotator.ImageAdded(3ul, "placeholder.bmp");
+  EXPECT_THAT(o1.last_id_, Eq(2ul));
+  EXPECT_THAT(o2.last_id_, Eq(3ul));
+
+  o2.sub_.Cancel();
+  page_annotator.ImageRemoved(1ul);
+  EXPECT_THAT(o1.last_id_, Eq(2ul));
+  EXPECT_THAT(o2.last_id_, Eq(3ul));
+}
+
+// TODO(crbug.com/916363): add more tests when behavior is added to the
+//                         PageAnnotator class.
+
+}  // namespace page_image_annotation