[iOS Clean] Wired up ContextMenuCommands.

This CL updates WebContextMenuCoordinator and its associated mediator
class to support more actions.  It will load URLs and create new
WebStates, but still does not perform corresponding animations to show
newly created Tabs and their loaded content.

BUG=none

Review-Url: https://codereview.chromium.org/2862783002
Cr-Commit-Position: refs/heads/master@{#475257}
diff --git a/ios/clean/chrome/browser/ui/commands/BUILD.gn b/ios/clean/chrome/browser/ui/commands/BUILD.gn
index 1e5d270..eda1a9b 100644
--- a/ios/clean/chrome/browser/ui/commands/BUILD.gn
+++ b/ios/clean/chrome/browser/ui/commands/BUILD.gn
@@ -4,6 +4,7 @@
 
 source_set("commands") {
   sources = [
+    "context_menu_commands.h",
     "find_in_page_search_commands.h",
     "find_in_page_visibility_commands.h",
     "navigation_commands.h",
diff --git a/ios/clean/chrome/browser/ui/commands/context_menu_commands.h b/ios/clean/chrome/browser/ui/commands/context_menu_commands.h
new file mode 100644
index 0000000..a789255
--- /dev/null
+++ b/ios/clean/chrome/browser/ui/commands/context_menu_commands.h
@@ -0,0 +1,31 @@
+// Copyright 2017 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 IOS_CLEAN_CHROME_BROWSER_UI_COMMANDS_CONTEXT_MENU_COMMANDS_H_
+#define IOS_CLEAN_CHROME_BROWSER_UI_COMMANDS_CONTEXT_MENU_COMMANDS_H_
+
+@class ContextMenuContext;
+
+// Commands relating to the context menu.
+@protocol ContextMenuCommands<NSObject>
+@optional
+// Executes |context|'s script.
+- (void)executeContextMenuScript:(ContextMenuContext*)context;
+// Opens |context|'s link in a new tab.
+- (void)openContextMenuLinkInNewTab:(ContextMenuContext*)context;
+// Opens |context|'s link in a new Incognito tab.
+- (void)openContextMenuLinkInNewIncognitoTab:(ContextMenuContext*)context;
+// Copies |context|'s link to the paste board.
+- (void)copyContextMenuLink:(ContextMenuContext*)context;
+// Saves's the image at |context|'s image URL to the camera roll.
+- (void)saveContextMenuImage:(ContextMenuContext*)context;
+// Opens the image at |context|'s image URL .
+- (void)openContextMenuImage:(ContextMenuContext*)context;
+// Opens the image at |context|'s image URL  in a new tab.
+- (void)openContextMenuImageInNewTab:(ContextMenuContext*)context;
+// Hides the context menu UI.
+- (void)hideContextMenu:(ContextMenuContext*)context;
+@end
+
+#endif  // IOS_CLEAN_CHROME_BROWSER_UI_COMMANDS_CONTEXT_MENU_COMMANDS_H_
diff --git a/ios/clean/chrome/browser/ui/context_menu/BUILD.gn b/ios/clean/chrome/browser/ui/context_menu/BUILD.gn
index 24307b6d..7d1bfea 100644
--- a/ios/clean/chrome/browser/ui/context_menu/BUILD.gn
+++ b/ios/clean/chrome/browser/ui/context_menu/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("context_menu") {
   sources = [
+    "context_menu_context_impl.h",
+    "context_menu_context_impl.mm",
     "context_menu_mediator.h",
     "context_menu_mediator.mm",
     "web_context_menu_coordinator.h",
@@ -14,6 +16,8 @@
 
   deps = [
     ":context_menu_ui",
+    "//ios/chrome/browser/web_state_list:web_state_list",
+    "//ios/clean/chrome/browser/ui/commands:commands",
     "//ios/shared/chrome/browser/ui/browser_list",
     "//ios/shared/chrome/browser/ui/commands",
     "//ios/shared/chrome/browser/ui/coordinators",
@@ -26,11 +30,18 @@
 source_set("context_menu_ui") {
   sources = [
     "context_menu_consumer.h",
-    "context_menu_consumer.mm",
+    "context_menu_context.h",
+    "context_menu_context.mm",
+    "context_menu_item.h",
+    "context_menu_item.mm",
     "context_menu_view_controller.h",
     "context_menu_view_controller.mm",
   ]
 
+  deps = [
+    "//base:base",
+  ]
+
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
@@ -48,6 +59,7 @@
     "//base",
     "//base/test:test_support",
     "//ios/chrome/test/base",
+    "//ios/web",
     "//ios/web:test_support",
     "//testing/gtest",
   ]
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.h b/ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.h
index f54c7df..97305db 100644
--- a/ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.h
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.h
@@ -5,37 +5,28 @@
 #ifndef IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_CONSUMER_H_
 #define IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_CONSUMER_H_
 
-#import <Foundation/Foundation.h>
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_item.h"
 
-// Object encapsulating configuration information for an item in a context menu.
-@interface ContextMenuItem : NSObject
-
-// Create a new item with |title| and |command|.
-+ (instancetype)itemWithTitle:(NSString*)title command:(NSInvocation*)command;
-
-// The title associated with the item. This is usually the text the user will
-// see.
-@property(nonatomic, readonly) NSString* title;
-
-// The selector and parameters that will be called when the item is
-// selected.
-@property(nonatomic, readonly) NSInvocation* command;
-
-@end
+@class ContextMenuContext;
 
 // A ContextMenuConsumer (typically a view controller) uses data provided by
 // this protocol to display an run a context menu.
 @protocol ContextMenuConsumer
 
+// Sets the context that should be sent with the ContextMenuCommands dispatched
+// by ContextMenuItems added to this consumer.
+- (void)setContextMenuContext:(ContextMenuContext*)context;
+
 // Informs the consumer that the overall title of the context menu will be
 // |title|. This method should be called only once in the lifetime of the
 // consumer.
 - (void)setContextMenuTitle:(NSString*)title;
 
 // Informs the consumer that the context menu should display the items defined
-// in |items|. This method should be called only once in the lifetime of the
-// consumer.
-- (void)setContextMenuItems:(NSArray<ContextMenuItem*>*)items;
+// in |items|.  |cancelItem| will be added as the last option in the menu. This
+// method should be called only once in the lifetime of the consumer.
+- (void)setContextMenuItems:(NSArray<ContextMenuItem*>*)items
+                 cancelItem:(ContextMenuItem*)cancelItem;
 
 @end
 
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.mm b/ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.mm
deleted file mode 100644
index ac014792..0000000
--- a/ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.mm
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2017 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.
-
-#import "ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface ContextMenuItem ()
-@property(nonatomic, readwrite) NSString* title;
-@property(nonatomic, readwrite) NSInvocation* command;
-@end
-
-@implementation ContextMenuItem
-
-@synthesize title = _title;
-@synthesize command = _command;
-
-+ (instancetype)itemWithTitle:(NSString*)title command:(NSInvocation*)command {
-  ContextMenuItem* item = [[self alloc] init];
-  item.title = title;
-  item.command = command;
-  return item;
-}
-
-@end
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_context.h b/ios/clean/chrome/browser/ui/context_menu/context_menu_context.h
new file mode 100644
index 0000000..dc858c06
--- /dev/null
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_context.h
@@ -0,0 +1,15 @@
+// Copyright 2017 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 IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_CONTEXT_H_
+#define IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_CONTEXT_H_
+
+#import <Foundation/Foundation.h>
+
+// An object sent along with ContextMenuCommands that can be used as an
+// identifier for a specific context menu instance.
+@interface ContextMenuContext : NSObject
+@end
+
+#endif  // IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_CONTEXT_H_
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_context.mm b/ios/clean/chrome/browser/ui/context_menu/context_menu_context.mm
new file mode 100644
index 0000000..eb6bc53
--- /dev/null
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_context.mm
@@ -0,0 +1,12 @@
+// Copyright 2017 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.
+
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation ContextMenuContext
+@end
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h b/ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h
new file mode 100644
index 0000000..264bb92
--- /dev/null
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h
@@ -0,0 +1,43 @@
+// Copyright 2017 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 IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_CONTEXT_IMPL_H_
+#define IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_CONTEXT_IMPL_H_
+
+#include "base/strings/string16.h"
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context.h"
+
+namespace web {
+struct ContextMenuParams;
+}
+
+class GURL;
+
+// Context object used to populate the context menu UI and to handle commands
+// from that UI.
+@interface ContextMenuContextImpl : ContextMenuContext
+
+// ContextMenuContextImpls must be initialized with |params|.
+- (instancetype)initWithParams:(const web::ContextMenuParams&)params
+    NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
+// The title to use for the menu.
+@property(nonatomic, readonly, strong) NSString* menuTitle;
+
+// If the context menu was triggered by long-pressing a link, |linkURL| will be
+// that link's URL.
+@property(nonatomic, readonly) const GURL& linkURL;
+
+// If the context menu was triggered by long-pressing an image, |imageURL| will
+// be the URL for that image.
+@property(nonatomic, readonly) const GURL& imageURL;
+
+// If the context menu was triggered by long-pressing a JavaScript link,
+// |script| will be the script for that link.
+@property(nonatomic, readonly) const base::string16& script;
+
+@end
+
+#endif  // IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_CONTEXT_IMPL_H_
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.mm b/ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.mm
new file mode 100644
index 0000000..8bce2c8
--- /dev/null
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.mm
@@ -0,0 +1,55 @@
+// Copyright 2017 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.
+
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h"
+
+#include "base/strings/utf_string_conversions.h"
+#import "ios/web/public/web_state/context_menu_params.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface ContextMenuContextImpl () {
+  // The parameters passed on initialization.
+  web::ContextMenuParams _params;
+  // The context menu link's script, if of the javascript: scheme.
+  base::string16 _script;
+}
+
+@end
+
+@implementation ContextMenuContextImpl
+
+- (instancetype)initWithParams:(const web::ContextMenuParams&)params {
+  if ((self = [super init])) {
+    _params = params;
+    const GURL& linkURL = _params.link_url;
+    if (linkURL.is_valid() && linkURL.SchemeIs(url::kJavaScriptScheme))
+      _script = base::UTF8ToUTF16(linkURL.GetContent());
+  }
+  return self;
+}
+
+#pragma mark - Accessors
+
+- (NSString*)menuTitle {
+  return _params.menu_title.get();
+}
+
+- (const GURL&)linkURL {
+  return _params.link_url;
+}
+
+- (const GURL&)imageURL {
+  return _params.src_url;
+}
+
+- (const base::string16&)script {
+  return _script;
+}
+
+@end
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_item.h b/ios/clean/chrome/browser/ui/context_menu/context_menu_item.h
new file mode 100644
index 0000000..deb4d7ff
--- /dev/null
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_item.h
@@ -0,0 +1,28 @@
+// Copyright 2017 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 IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_ITEM_H_
+#define IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_ITEM_H_
+
+#import <Foundation/Foundation.h>
+#include <vector>
+
+// Object encapsulating configuration information for an item in a context menu.
+@interface ContextMenuItem : NSObject
+
+// Create a new item with |title| and |commands|.
++ (instancetype)itemWithTitle:(NSString*)title
+                     commands:(const std::vector<SEL>&)commands;
+
+// The title associated with the item. This is usually the text the user will
+// see.
+@property(nonatomic, readonly, copy) NSString* title;
+
+// The ContextMenuCommands to be dispatched with menu's ContextMenuContext.
+// They will be dispatched in the order in which they appear in the vector.
+@property(nonatomic, readonly, assign) const std::vector<SEL>& commands;
+
+@end
+
+#endif  // IOS_CLEAN_CHROME_BROWSER_UI_CONTEXT_MENU_CONTEXT_MENU_ITEM_H_
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_item.mm b/ios/clean/chrome/browser/ui/context_menu/context_menu_item.mm
new file mode 100644
index 0000000..7af0941
--- /dev/null
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_item.mm
@@ -0,0 +1,48 @@
+// Copyright 2017 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.
+
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_item.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface ContextMenuItem () {
+  // Backing object for |commands|.
+  std::vector<SEL> _commands;
+}
+
+// Convenience initializer for use by the factory method.
+- (instancetype)initWithTitle:(NSString*)title
+                     commands:(const std::vector<SEL>&)commands;
+
+@end
+
+@implementation ContextMenuItem
+
+@synthesize title = _title;
+
+- (instancetype)initWithTitle:(NSString*)title
+                     commands:(const std::vector<SEL>&)commands {
+  if ((self = [super init])) {
+    _title = [title copy];
+    _commands = commands;
+  }
+  return self;
+}
+
+#pragma mark - Accessors
+
+- (const std::vector<SEL>&)commands {
+  return _commands;
+}
+
+#pragma mark - Public
+
++ (instancetype)itemWithTitle:(NSString*)title
+                     commands:(const std::vector<SEL>&)commands {
+  return [[ContextMenuItem alloc] initWithTitle:title commands:commands];
+}
+
+@end
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.h b/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.h
index 3639b33..f36a157 100644
--- a/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.h
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.h
@@ -8,13 +8,20 @@
 #import <Foundation/Foundation.h>
 
 @protocol ContextMenuConsumer;
+@class ContextMenuContextImpl;
 
 // A mediator object that provides configuration information for a context
 // menu.
 @interface ContextMenuMediator : NSObject
 
-// Creates a new mediator with the non-nil consumer |consumer|.
-- (instancetype)initWithConsumer:(id<ContextMenuConsumer>)consumer;
+// Populates |consumer| with alert items for actions appropriate for |context|.
++ (void)updateConsumer:(id<ContextMenuConsumer>)consumer
+           withContext:(ContextMenuContextImpl*)context;
+
+// A ContextMenuConsumer only requires configuration only once, then is
+// immutable.  As a result, there is no need to instantiate an object to manage
+// ongoing consumer updates; use |+updateConsumer:withContext:| instead.
+- (instancetype)init NS_UNAVAILABLE;
 
 @end
 
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.mm b/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.mm
index fade9f3..6b2a302 100644
--- a/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.mm
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.mm
@@ -4,41 +4,110 @@
 
 #import "ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.h"
 
+#import "ios/clean/chrome/browser/ui/commands/context_menu_commands.h"
 #import "ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.h"
-#import "ios/web/public/web_state/context_menu_params.h"
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h"
+#import "ios/shared/chrome/browser/ui/browser_list/browser.h"
+#import "ios/web/public/url_scheme_util.h"
+#include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
 @interface ContextMenuMediator ()
-@property(nonatomic, weak) id<ContextMenuConsumer> consumer;
+
+// The ContextMenuItem to use for script menus.
++ (ContextMenuItem*)scriptItem;
+
+// The ContextMenuItems to use for link menus.
++ (NSArray<ContextMenuItem*>*)linkItems;
+
+// The ContextMenuItems to use for image menus.
++ (NSArray<ContextMenuItem*>*)imageItems;
+
+// The cancel item to use.
++ (ContextMenuItem*)cancelItem;
+
 @end
 
 @implementation ContextMenuMediator
-@synthesize consumer = _consumer;
 
-- (instancetype)initWithConsumer:(id<ContextMenuConsumer>)consumer {
-  if ((self = [super init])) {
-    _consumer = consumer;
-    [self updateConsumer];
-  }
-  return self;
++ (void)updateConsumer:(id<ContextMenuConsumer>)consumer
+           withContext:(ContextMenuContextImpl*)context {
+  DCHECK(consumer);
+  DCHECK(context);
+  // Set the context and menu title.
+  [consumer setContextMenuContext:context];
+  [consumer setContextMenuTitle:context.menuTitle];
+  // Add the appropriate items.
+  NSMutableArray<ContextMenuItem*>* items = [[NSMutableArray alloc] init];
+  if (context.script.size())
+    [items addObject:[self scriptItem]];
+  BOOL showLinkOptions =
+      context.linkURL.is_valid() && web::UrlHasWebScheme(context.linkURL);
+  if (showLinkOptions)
+    [items addObjectsFromArray:[self linkItems]];
+  BOOL showImageOptions = context.imageURL.is_valid();
+  if (showImageOptions)
+    [items addObjectsFromArray:[self imageItems]];
+  [consumer setContextMenuItems:[items copy] cancelItem:[self cancelItem]];
 }
 
-// Update the consumer.
-- (void)updateConsumer {
-  // PLACEHOLDER. Fake title.
-  [self.consumer setContextMenuTitle:@"http://some/link.html"];
-  NSMutableArray<ContextMenuItem*>* items =
-      [[NSMutableArray<ContextMenuItem*> alloc] init];
+#pragma mark -
 
-  // PLACEHOLDER. Two non-functional items.
-  [items
-      addObject:[ContextMenuItem itemWithTitle:@"Open in New Tab" command:nil]];
-  [items
-      addObject:[ContextMenuItem itemWithTitle:@"Copy Link URL" command:nil]];
-  [self.consumer setContextMenuItems:[items copy]];
++ (ContextMenuItem*)scriptItem {
+  std::vector<SEL> commands(2U);
+  commands[0] = @selector(executeContextMenuScript:);
+  commands[1] = @selector(hideContextMenu:);
+  return [ContextMenuItem itemWithTitle:@"Execute Script" commands:commands];
+}
+
++ (NSArray<ContextMenuItem*>*)linkItems {
+  // Opening the link in a new Tab will stop this context menu's coordinator, so
+  // there's no need to hide it.
+  std::vector<SEL> newTabCommands(1U);
+  newTabCommands[0] = @selector(openContextMenuLinkInNewTab:);
+  // TODO: Add |-openContextMenuLinkInNewIncognitoTab:| as the first command for
+  // "Open In New Incognito Tab" once the incognito tab grid is implemented.
+  std::vector<SEL> newIncognitoTabCommands(1U);
+  newIncognitoTabCommands[0] = @selector(hideContextMenu:);
+  // TODO: Add |-copyContextMenuLink:| as the first command for "Copy Link" once
+  // copying to pasteboard is implemented.
+  std::vector<SEL> copyLinkCommands(1U);
+  newIncognitoTabCommands[0] = @selector(hideContextMenu:);
+  return @[
+    [ContextMenuItem itemWithTitle:@"Open In New Tab" commands:newTabCommands],
+    [ContextMenuItem itemWithTitle:@"Open In New Incognito Tab"
+                          commands:newIncognitoTabCommands],
+    [ContextMenuItem itemWithTitle:@"Copy Link" commands:copyLinkCommands],
+  ];
+}
+
++ (NSArray<ContextMenuItem*>*)imageItems {
+  // TODO: Add |-saveContextMenuImage:| as the first command for "Save Image"
+  // once camera roll access has been implemented.
+  std::vector<SEL> saveImageCommands(1U);
+  saveImageCommands[0] = @selector(hideContextMenu:);
+  std::vector<SEL> openImageCommands(2U);
+  openImageCommands[0] = @selector(openContextMenuImage:);
+  openImageCommands[1] = @selector(hideContextMenu:);
+  // Opening the image in a new Tab will stop this context menu's coordinator,
+  // so there's no need to hide it.
+  std::vector<SEL> openImageInNewTabCommands(1U);
+  openImageInNewTabCommands[0] = @selector(openContextMenuImageInNewTab:);
+  return @[
+    [ContextMenuItem itemWithTitle:@"Save Image" commands:saveImageCommands],
+    [ContextMenuItem itemWithTitle:@"Open Image" commands:openImageCommands],
+    [ContextMenuItem itemWithTitle:@"Open Image In New Tab"
+                          commands:openImageInNewTabCommands],
+  ];
+}
+
++ (ContextMenuItem*)cancelItem {
+  std::vector<SEL> cancelCommands(1U);
+  cancelCommands[0] = @selector(hideContextMenu:);
+  return [ContextMenuItem itemWithTitle:@"Cancel" commands:cancelCommands];
 }
 
 @end
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator_unittest.mm b/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator_unittest.mm
index a9cd5eb..dff8d124 100644
--- a/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator_unittest.mm
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_mediator_unittest.mm
@@ -10,43 +10,123 @@
 
 #include "base/memory/ptr_util.h"
 #import "ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.h"
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h"
+#import "ios/web/public/web_state/context_menu_params.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/gtest_mac.h"
 #include "testing/platform_test.h"
+#include "url/gurl.h"
 
 @interface TestConsumer : NSObject<ContextMenuConsumer>
+@property(nonatomic, strong) ContextMenuContext* context;
 @property(nonatomic, copy) NSString* title;
 @property(nonatomic, copy) NSArray<ContextMenuItem*>* items;
+@property(nonatomic, strong) ContextMenuItem* cancelItem;
 @end
 
 @implementation TestConsumer
+@synthesize context = _context;
 @synthesize title = _title;
 @synthesize items = _items;
+@synthesize cancelItem = _cancelItem;
+
+- (void)setContextMenuContext:(ContextMenuContext*)context {
+  self.context = context;
+}
 
 - (void)setContextMenuTitle:(NSString*)title {
   self.title = title;
 }
 
-- (void)setContextMenuItems:(NSArray<ContextMenuItem*>*)items {
+- (void)setContextMenuItems:(NSArray<ContextMenuItem*>*)items
+                 cancelItem:(ContextMenuItem*)cancelItem {
   self.items = items;
+  self.cancelItem = cancelItem;
 }
 
 @end
 
-TEST(ContextMenuMediatorTest, TestTitleAndItems) {
-  // A minimal test -- that once initialized, the mediator has set a non-empty
-  // title on the consumer, and passed a non-empty list of items with non-empty
-  // titles.
+// Tests that the menu title is set properly.
+TEST(ContextMenuMediatorTest, MenuTitle) {
+  // Create a context with |kMenuTitle|.
+  NSString* kMenuTitle = @"Menu Title";
+  web::ContextMenuParams params;
+  params.menu_title.reset(kMenuTitle);
+  ContextMenuContextImpl* context =
+      [[ContextMenuContextImpl alloc] initWithParams:params];
+  // Update the consumer and verify that |kMenuTitle| is set.
   TestConsumer* consumer = [[TestConsumer alloc] init];
-  ContextMenuMediator* mediator =
-      [[ContextMenuMediator alloc] initWithConsumer:consumer];
+  [ContextMenuMediator updateConsumer:consumer withContext:context];
+  EXPECT_NSEQ(kMenuTitle, consumer.title);
+}
 
-  EXPECT_NE(nil, mediator);
+// Tests that the script option is shown for javacript: scheme URLs.
+TEST(ContextMenuMediatorTest, ScriptItem) {
+  // Create a context with |kJavaScriptURL|.
+  GURL kJavaScriptURL("javascript:alert('test');");
+  web::ContextMenuParams params;
+  params.link_url = kJavaScriptURL;
+  ContextMenuContextImpl* context =
+      [[ContextMenuContextImpl alloc] initWithParams:params];
+  // Update the consumer and verify that the script option was added.
+  TestConsumer* consumer = [[TestConsumer alloc] init];
+  [ContextMenuMediator updateConsumer:consumer withContext:context];
+  ContextMenuItem* script_item = [consumer.items firstObject];
+  EXPECT_NSEQ(script_item.title, @"Execute Script");
+}
 
-  EXPECT_NE(0U, consumer.title.length);
-  EXPECT_NE(0U, consumer.items.count);
-
-  for (ContextMenuItem* item in consumer.items) {
-    EXPECT_NE(0U, item.title.length);
+// Tests that the link options are shown for valid link URLs.
+TEST(ContextMenuMediatorTest, LinkItems) {
+  // Create a context with a valid link URL.
+  web::ContextMenuParams params;
+  params.link_url = GURL("http://valid.url.com");
+  ;
+  ContextMenuContextImpl* context =
+      [[ContextMenuContextImpl alloc] initWithParams:params];
+  // Update the consumer and verify that the link options were added.
+  TestConsumer* consumer = [[TestConsumer alloc] init];
+  [ContextMenuMediator updateConsumer:consumer withContext:context];
+  NSArray* link_option_titles = @[
+    @"Open In New Tab",
+    @"Open In New Incognito Tab",
+    @"Copy Link",
+  ];
+  ASSERT_EQ(link_option_titles.count, consumer.items.count);
+  for (NSUInteger i = 0; i < link_option_titles.count; ++i) {
+    EXPECT_NSEQ(link_option_titles[i], consumer.items[i].title);
   }
 }
+
+// Tests that the image options are shown for valid image URLs.
+TEST(ContextMenuMediatorTest, ImageItems) {
+  // Create a context with a valid image URL.
+  web::ContextMenuParams params;
+  params.src_url = GURL("http://valid.url.com");
+  ;
+  ContextMenuContextImpl* context =
+      [[ContextMenuContextImpl alloc] initWithParams:params];
+  // Update the consumer and verify that the image options were added.
+  TestConsumer* consumer = [[TestConsumer alloc] init];
+  [ContextMenuMediator updateConsumer:consumer withContext:context];
+  NSArray* image_option_titles = @[
+    @"Save Image",
+    @"Open Image",
+    @"Open Image In New Tab",
+  ];
+  ASSERT_EQ(image_option_titles.count, consumer.items.count);
+  for (NSUInteger i = 0; i < image_option_titles.count; ++i) {
+    EXPECT_NSEQ(image_option_titles[i], consumer.items[i].title);
+  }
+}
+
+// Tests that the cancel item is always added.
+TEST(ContextMenuMediatorTest, CancelItem) {
+  // Create an empty context; the cancel item should be added regardless.
+  web::ContextMenuParams params;
+  ContextMenuContextImpl* context =
+      [[ContextMenuContextImpl alloc] initWithParams:params];
+  // Update the consumer and verify that the image options were added.
+  TestConsumer* consumer = [[TestConsumer alloc] init];
+  [ContextMenuMediator updateConsumer:consumer withContext:context];
+  EXPECT_NSEQ(@"Cancel", consumer.cancelItem.title);
+}
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.h b/ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.h
index 0555699..361cee1 100644
--- a/ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.h
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.h
@@ -9,13 +9,14 @@
 
 #import "ios/clean/chrome/browser/ui/context_menu/context_menu_consumer.h"
 
+@protocol ContextMenuCommands;
+@class ContextMenuContext;
+
 // A view controller that displays a context menu.
 @interface ContextMenuViewController : UIAlertController<ContextMenuConsumer>
 
-// Create an instance of this class with the dispatcher |dispatcher|.
-//
-// PLACEHOLDER: Once commands are defined, define a protocol for the dispatcher.
-- (instancetype)initWithDispatcher:(id)dispatcher;
+// Designated initializer that uses |dispatcher| to communicate commands.
+- (instancetype)initWithDispatcher:(id<ContextMenuCommands>)dispatcher;
 
 @end
 
diff --git a/ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.mm b/ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.mm
index 4178094..d1d120a 100644
--- a/ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.mm
+++ b/ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.mm
@@ -4,6 +4,9 @@
 
 #import "ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.h"
 
+#include "base/logging.h"
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context.h"
+
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
@@ -11,22 +14,44 @@
 namespace {
 // Typedef the block parameter for UIAlertAction for readability.
 typedef void (^AlertActionHandler)(UIAlertAction*);
+// Sends |commands| to |dispatcher| using |context| as the menu context.
+void DispatchContextMenuCommands(const std::vector<SEL>& commands,
+                                 id dispatcher,
+                                 ContextMenuContext* context) {
+  DCHECK(dispatcher);
+  DCHECK(context);
+  for (SEL command : commands) {
+    IMP command_imp = [dispatcher methodForSelector:command];
+    DCHECK(command_imp);
+    command_imp(dispatcher, command, context);
+  }
+}
 }
 
 @interface ContextMenuViewController ()
-// Object to which command messages will be sent.
-@property(nonatomic, weak) id dispatcher;
+// The dispatcher passed on initialization.
+@property(nonatomic, readonly, weak) id<ContextMenuCommands> dispatcher;
+// The context passed on initialization.
+@property(nonatomic, strong) ContextMenuContext* context;
+
+// Creates an UIAlertAction for |item| using |style| to manage the action's
+// appearance.
+- (UIAlertAction*)alertActionForItem:(ContextMenuItem*)item
+                               style:(UIAlertActionStyle)style;
+
 @end
 
 @implementation ContextMenuViewController
 @synthesize dispatcher = _dispatcher;
+@synthesize context = _context;
 
-- (instancetype)initWithDispatcher:(id)dispatcher {
+- (instancetype)initWithDispatcher:(id<ContextMenuCommands>)dispatcher {
   self =
       [[self class] alertControllerWithTitle:nil
                                      message:nil
                               preferredStyle:UIAlertControllerStyleActionSheet];
   if (self) {
+    DCHECK(dispatcher);
     _dispatcher = dispatcher;
   }
   return self;
@@ -34,26 +59,36 @@
 
 #pragma mark - ContextMenuConsumer
 
+- (void)setContextMenuContext:(ContextMenuContext*)context {
+  DCHECK(context);
+  self.context = context;
+}
+
 - (void)setContextMenuTitle:(NSString*)title {
   self.title = title;
 }
 
-- (void)setContextMenuItems:(NSArray<ContextMenuItem*>*)items {
+- (void)setContextMenuItems:(NSArray<ContextMenuItem*>*)items
+                 cancelItem:(ContextMenuItem*)cancelItem {
+  // Add an alert action for each item in |items|, then add |cancelItem|.
   for (ContextMenuItem* item in items) {
-    // Create a block that sends the invocation passed in with the item's
-    // configuration to the dispatcher.
-    AlertActionHandler handler = ^(UIAlertAction* action) {
-      [item.command invokeWithTarget:self.dispatcher];
-    };
-    [self addAction:[UIAlertAction actionWithTitle:item.title
-                                             style:UIAlertActionStyleDefault
-                                           handler:handler]];
+    [self addAction:[self alertActionForItem:item
+                                       style:UIAlertActionStyleDefault]];
   }
+  [self addAction:[self alertActionForItem:cancelItem
+                                     style:UIAlertActionStyleCancel]];
+}
 
-  // Always add a cancel action.
-  [self addAction:[UIAlertAction actionWithTitle:@"Cancel"
-                                           style:UIAlertActionStyleCancel
-                                         handler:nil]];
+#pragma mark -
+
+- (UIAlertAction*)alertActionForItem:(ContextMenuItem*)item
+                               style:(UIAlertActionStyle)style {
+  DCHECK(item);
+  // Create a block that dispatches |item|'s ContextMenuCommands.
+  AlertActionHandler handler = ^(UIAlertAction* action) {
+    DispatchContextMenuCommands(item.commands, self.dispatcher, self.context);
+  };
+  return [UIAlertAction actionWithTitle:item.title style:style handler:handler];
 }
 
 @end
diff --git a/ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.h b/ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.h
index 3ef41bc..c5fa923 100644
--- a/ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.h
+++ b/ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.h
@@ -9,11 +9,20 @@
 
 #import "ios/shared/chrome/browser/ui/coordinators/browser_coordinator.h"
 
+@class ContextMenuContextImpl;
+
 // A coordinator for a UI element that displays the web view associated with
 // |webState|.
 // HACK: Named WebContentMenuCoordinator to avoid collision with the
 // old architecture ContextMenuCoordinator class.
 @interface WebContextMenuCoordinator : BrowserCoordinator
+
+// Initializer that takes the parameters used to specify which context menu
+// options to display.
+- (instancetype)initWithContext:(ContextMenuContextImpl*)context
+    NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
 @end
 
 #endif  // IOS_CLEAN_CHROME_BROWSER_UI_WEB_CONTEXT_MENU_CONTEXT_MENU_COORDINATOR_H_
diff --git a/ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.mm b/ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.mm
index 097c8055..9e375d99 100644
--- a/ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.mm
+++ b/ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.mm
@@ -4,6 +4,9 @@
 
 #import "ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.h"
 
+#include "base/logging.h"
+#import "ios/clean/chrome/browser/ui/commands/context_menu_commands.h"
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h"
 #import "ios/clean/chrome/browser/ui/context_menu/context_menu_mediator.h"
 #import "ios/clean/chrome/browser/ui/context_menu/context_menu_view_controller.h"
 #import "ios/shared/chrome/browser/ui/browser_list/browser.h"
@@ -13,21 +16,51 @@
 #error "This file requires ARC support."
 #endif
 
-@interface WebContextMenuCoordinator ()
-@property(nonatomic, strong) ContextMenuMediator* mediator;
+@interface WebContextMenuCoordinator ()<ContextMenuCommands>
+// The menu context.
+@property(nonatomic, strong, readonly) ContextMenuContextImpl* context;
+// The view controller that displayes the context menu UI.
 @property(nonatomic, strong) ContextMenuViewController* viewController;
+
 @end
 
 @implementation WebContextMenuCoordinator
+@synthesize context = _context;
 @synthesize viewController = _viewController;
-@synthesize mediator = _mediator;
+
+- (instancetype)initWithContext:(ContextMenuContextImpl*)context {
+  if ((self = [super init])) {
+    DCHECK(context);
+    _context = context;
+  }
+  return self;
+}
+
+#pragma mark - BrowserCoordinator
 
 - (void)start {
+  id<ContextMenuCommands> contextMenuDispatcher =
+      static_cast<id<ContextMenuCommands>>(self.browser->dispatcher());
   self.viewController = [[ContextMenuViewController alloc]
-      initWithDispatcher:static_cast<id>(self.browser->dispatcher())];
-  self.mediator =
-      [[ContextMenuMediator alloc] initWithConsumer:self.viewController];
+      initWithDispatcher:contextMenuDispatcher];
+  [ContextMenuMediator updateConsumer:self.viewController
+                          withContext:self.context];
+  [self.browser->dispatcher()
+      startDispatchingToTarget:self
+                   forSelector:@selector(hideContextMenu:)];
   [super start];
 }
 
+- (void)stop {
+  [self.browser->dispatcher() stopDispatchingToTarget:self];
+  [super stop];
+}
+
+#pragma mark - ContextMenuCommands
+
+- (void)hideContextMenu:(ContextMenuContext*)context {
+  DCHECK_EQ(self.context, context);
+  [self stop];
+}
+
 @end
diff --git a/ios/clean/chrome/browser/ui/tab_grid/BUILD.gn b/ios/clean/chrome/browser/ui/tab_grid/BUILD.gn
index 4f11f72f..b7eddbb 100644
--- a/ios/clean/chrome/browser/ui/tab_grid/BUILD.gn
+++ b/ios/clean/chrome/browser/ui/tab_grid/BUILD.gn
@@ -19,6 +19,7 @@
     "//ios/chrome/browser/web_state_list",
     "//ios/clean/chrome/browser",
     "//ios/clean/chrome/browser/ui/commands",
+    "//ios/clean/chrome/browser/ui/context_menu",
     "//ios/clean/chrome/browser/ui/settings",
     "//ios/clean/chrome/browser/ui/tab",
     "//ios/clean/chrome/browser/ui/tab_collection",
diff --git a/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm b/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
index 091bbec..1049789 100644
--- a/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
+++ b/ios/clean/chrome/browser/ui/tab_grid/tab_grid_coordinator.mm
@@ -6,12 +6,15 @@
 
 #include <memory>
 
+#include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/clean/chrome/browser/ui/commands/context_menu_commands.h"
 #import "ios/clean/chrome/browser/ui/commands/settings_commands.h"
 #import "ios/clean/chrome/browser/ui/commands/tab_grid_commands.h"
 #import "ios/clean/chrome/browser/ui/commands/tools_menu_commands.h"
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h"
 #import "ios/clean/chrome/browser/ui/settings/settings_coordinator.h"
 #import "ios/clean/chrome/browser/ui/tab/tab_coordinator.h"
 #import "ios/clean/chrome/browser/ui/tab_grid/tab_grid_mediator.h"
@@ -30,12 +33,14 @@
 #error "This file requires ARC support."
 #endif
 
-@interface TabGridCoordinator ()<SettingsCommands,
+@interface TabGridCoordinator ()<ContextMenuCommands,
+                                 SettingsCommands,
                                  TabGridCommands,
                                  ToolsMenuCommands>
 @property(nonatomic, strong) TabGridViewController* viewController;
 @property(nonatomic, weak) SettingsCoordinator* settingsCoordinator;
 @property(nonatomic, weak) ToolsCoordinator* toolsMenuCoordinator;
+@property(nonatomic, weak) TabCoordinator* activeTabCoordinator;
 @property(nonatomic, readonly) WebStateList& webStateList;
 @property(nonatomic, strong) TabGridMediator* mediator;
 @end
@@ -44,6 +49,7 @@
 @synthesize viewController = _viewController;
 @synthesize settingsCoordinator = _settingsCoordinator;
 @synthesize toolsMenuCoordinator = _toolsMenuCoordinator;
+@synthesize activeTabCoordinator = _activeTabCoordinator;
 @synthesize mediator = _mediator;
 
 #pragma mark - Properties
@@ -58,6 +64,7 @@
   self.mediator = [[TabGridMediator alloc] init];
   self.mediator.webStateList = &self.webStateList;
 
+  [self registerForContextMenuCommands];
   [self registerForSettingsCommands];
   [self registerForTabGridCommands];
   [self registerForToolsMenuCommands];
@@ -99,13 +106,53 @@
                          completion:nil];
 }
 
+#pragma mark - ContextMenuCommands
+
+- (void)openContextMenuLinkInNewTab:(ContextMenuContext*)context {
+  [self createAndShowNewTabInTabGrid];
+  ContextMenuContextImpl* contextImpl =
+      base::mac::ObjCCastStrict<ContextMenuContextImpl>(context);
+  [self openURL:net::NSURLWithGURL(contextImpl.linkURL)];
+}
+
+- (void)openContextMenuImageInNewTab:(ContextMenuContext*)context {
+  [self createAndShowNewTabInTabGrid];
+  ContextMenuContextImpl* contextImpl =
+      base::mac::ObjCCastStrict<ContextMenuContextImpl>(context);
+  [self openURL:net::NSURLWithGURL(contextImpl.imageURL)];
+}
+
+#pragma mark - SettingsCommands
+
+- (void)showSettings {
+  CommandDispatcher* dispatcher = self.browser->dispatcher();
+  [dispatcher startDispatchingToTarget:self
+                           forSelector:@selector(closeSettings)];
+  SettingsCoordinator* settingsCoordinator = [[SettingsCoordinator alloc] init];
+  [self addOverlayCoordinator:settingsCoordinator];
+  self.settingsCoordinator = settingsCoordinator;
+  [settingsCoordinator start];
+}
+
+- (void)closeSettings {
+  CommandDispatcher* dispatcher = self.browser->dispatcher();
+  [dispatcher stopDispatchingForSelector:@selector(closeSettings)];
+  [self.settingsCoordinator stop];
+  [self.settingsCoordinator.parentCoordinator
+      removeChildCoordinator:self.settingsCoordinator];
+  // self.settingsCoordinator should be presumed to be nil after this point.
+}
+
 #pragma mark - TabGridCommands
 
 - (void)showTabGridTabAtIndex:(int)index {
   self.webStateList.ActivateWebStateAt(index);
   // PLACEHOLDER: The tab coordinator should be able to get the active webState
   // on its own.
+  [self.activeTabCoordinator stop];
+  [self removeChildCoordinator:self.activeTabCoordinator];
   TabCoordinator* tabCoordinator = [[TabCoordinator alloc] init];
+  self.activeTabCoordinator = tabCoordinator;
   tabCoordinator.webState = self.webStateList.GetWebStateAt(index);
   tabCoordinator.presentationKey =
       [NSIndexPath indexPathForItem:index inSection:0];
@@ -155,27 +202,6 @@
   [self removeChildCoordinator:self.toolsMenuCoordinator];
 }
 
-#pragma mark - SettingsCommands
-
-- (void)showSettings {
-  CommandDispatcher* dispatcher = self.browser->dispatcher();
-  [dispatcher startDispatchingToTarget:self
-                           forSelector:@selector(closeSettings)];
-  SettingsCoordinator* settingsCoordinator = [[SettingsCoordinator alloc] init];
-  [self addOverlayCoordinator:settingsCoordinator];
-  self.settingsCoordinator = settingsCoordinator;
-  [settingsCoordinator start];
-}
-
-- (void)closeSettings {
-  CommandDispatcher* dispatcher = self.browser->dispatcher();
-  [dispatcher stopDispatchingForSelector:@selector(closeSettings)];
-  [self.settingsCoordinator stop];
-  [self.settingsCoordinator.parentCoordinator
-      removeChildCoordinator:self.settingsCoordinator];
-  // self.settingsCoordinator should be presumed to be nil after this point.
-}
-
 #pragma mark - URLOpening
 
 - (void)openURL:(NSURL*)URL {
@@ -195,6 +221,19 @@
 
 #pragma mark - PrivateMethods
 
+- (void)registerForContextMenuCommands {
+  // Right now these are unregistered in |-stop|.  However, once incognito is
+  // implemented, these commands will need to be unregistered before switching
+  // to incognito mode, as "open in new tab" commands are meant to be handled
+  // by the incognito TabGridCoordinator.
+  [self.browser->dispatcher()
+      startDispatchingToTarget:self
+                   forSelector:@selector(openContextMenuLinkInNewTab:)];
+  [self.browser->dispatcher()
+      startDispatchingToTarget:self
+                   forSelector:@selector(openContextMenuImageInNewTab:)];
+}
+
 - (void)registerForSettingsCommands {
   [self.browser->dispatcher() startDispatchingToTarget:self
                                            forSelector:@selector(showSettings)];
diff --git a/ios/clean/chrome/browser/ui/web_contents/BUILD.gn b/ios/clean/chrome/browser/ui/web_contents/BUILD.gn
index ea95263..3e27619 100644
--- a/ios/clean/chrome/browser/ui/web_contents/BUILD.gn
+++ b/ios/clean/chrome/browser/ui/web_contents/BUILD.gn
@@ -16,8 +16,10 @@
     ":web_contents_ui",
     "//ios/chrome/browser",
     "//ios/chrome/browser/web_state_list",
+    "//ios/clean/chrome/browser/ui/commands",
     "//ios/clean/chrome/browser/ui/context_menu",
     "//ios/shared/chrome/browser/ui/browser_list",
+    "//ios/shared/chrome/browser/ui/commands",
     "//ios/shared/chrome/browser/ui/coordinators",
     "//ios/web",
     "//ui/base",
diff --git a/ios/clean/chrome/browser/ui/web_contents/web_coordinator.mm b/ios/clean/chrome/browser/ui/web_contents/web_coordinator.mm
index 040b690d..6198cab 100644
--- a/ios/clean/chrome/browser/ui/web_contents/web_coordinator.mm
+++ b/ios/clean/chrome/browser/ui/web_contents/web_coordinator.mm
@@ -4,12 +4,17 @@
 
 #import "ios/clean/chrome/browser/ui/web_contents/web_coordinator.h"
 
+#include "base/mac/foundation_util.h"
 #include "base/memory/ptr_util.h"
+#import "ios/clean/chrome/browser/ui/commands/context_menu_commands.h"
+#import "ios/clean/chrome/browser/ui/context_menu/context_menu_context_impl.h"
 #import "ios/clean/chrome/browser/ui/context_menu/web_context_menu_coordinator.h"
 #import "ios/clean/chrome/browser/ui/web_contents/web_contents_mediator.h"
 #import "ios/clean/chrome/browser/ui/web_contents/web_contents_view_controller.h"
 #import "ios/shared/chrome/browser/ui/browser_list/browser.h"
+#import "ios/shared/chrome/browser/ui/commands/command_dispatcher.h"
 #import "ios/shared/chrome/browser/ui/coordinators/browser_coordinator+internal.h"
+#include "ios/web/public/navigation_manager.h"
 #include "ios/web/public/web_state/web_state.h"
 #import "ios/web/public/web_state/web_state_delegate_bridge.h"
 
@@ -17,7 +22,7 @@
 #error "This file requires ARC support."
 #endif
 
-@interface WebCoordinator ()<CRWWebStateDelegate> {
+@interface WebCoordinator ()<ContextMenuCommands, CRWWebStateDelegate> {
   std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
 }
 @property(nonatomic, strong) WebContentsViewController* viewController;
@@ -57,18 +62,53 @@
 }
 
 - (void)childCoordinatorDidStart:(BrowserCoordinator*)childCoordinator {
-  DCHECK([childCoordinator isKindOfClass:[WebContextMenuCoordinator class]]);
+  // Register to receive relevant ContextMenuCommands.
+  if ([childCoordinator isKindOfClass:[WebContextMenuCoordinator class]]) {
+    [self.browser->dispatcher()
+        startDispatchingToTarget:self
+                     forSelector:@selector(executeContextMenuScript:)];
+    [self.browser->dispatcher()
+        startDispatchingToTarget:self
+                     forSelector:@selector(openContextMenuImage:)];
+  }
   [self.viewController presentViewController:childCoordinator.viewController
                                     animated:YES
                                   completion:nil];
 }
 
+- (void)childCoordinatorWillStop:(BrowserCoordinator*)childCoordinator {
+  // Unregister ContextMenuCommands once the UI has been dismissed.
+  if ([childCoordinator isKindOfClass:[WebContextMenuCoordinator class]]) {
+    [self.browser->dispatcher()
+        stopDispatchingForSelector:@selector(executeContextMenuScript:)];
+    [self.browser->dispatcher()
+        stopDispatchingForSelector:@selector(openContextMenuImage:)];
+  }
+}
+
+#pragma mark - ContextMenuCommand
+
+- (void)executeContextMenuScript:(ContextMenuContext*)context {
+  ContextMenuContextImpl* contextImpl =
+      base::mac::ObjCCastStrict<ContextMenuContextImpl>(context);
+  self.webState->ExecuteJavaScript(contextImpl.script);
+}
+
+- (void)openContextMenuImage:(ContextMenuContext*)context {
+  ContextMenuContextImpl* contextImpl =
+      base::mac::ObjCCastStrict<ContextMenuContextImpl>(context);
+  web::NavigationManager::WebLoadParams loadParams(contextImpl.imageURL);
+  self.webState->GetNavigationManager()->LoadURLWithParams(loadParams);
+}
+
 #pragma mark - CRWWebStateDelegate
 
 - (BOOL)webState:(web::WebState*)webState
     handleContextMenu:(const web::ContextMenuParams&)params {
+  ContextMenuContextImpl* context =
+      [[ContextMenuContextImpl alloc] initWithParams:params];
   WebContextMenuCoordinator* contextMenu =
-      [[WebContextMenuCoordinator alloc] init];
+      [[WebContextMenuCoordinator alloc] initWithContext:context];
   [self addChildCoordinator:contextMenu];
   [contextMenu start];
   return YES;