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