Commit 2cc06f19 authored by Gauthier Ambard's avatar Gauthier Ambard Committed by Commit Bot

[iOS] Add SwitchToTab button to the omnibox popup

This CL adds the SwitchToTab image and handling to the omnibox popup
row. It allows to switch easily to an opened tab based on omnibox
suggestions.

Bug: 893121
Cq-Include-Trybots: luci.chromium.try:ios-simulator-cronet;luci.chromium.try:ios-simulator-full-configs
Change-Id: Ib440267802c1210ea416066c679a2ff4e963fbb6
Reviewed-on: https://chromium-review.googlesource.com/c/1268344
Commit-Queue: Gauthier Ambard <gambard@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avatarStepan Khapugin <stkhapugin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599155}
parent 1d2509fc
......@@ -4740,6 +4740,29 @@ applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
[nativeController focusFakebox];
}
- (void)unfocusOmniboxAndSwitchToTabWithURL:(const GURL&)URL {
// TODO(crbug.com/893121): Add animations.
// Cancelling the omnibox edit makes |URL| unsafe as it is not longer
// retained.
GURL retainedURL = URL;
[self.dispatcher cancelOmniboxEdit];
// TODO(crbug.com/893121): This should probably live in the WebState.
WebStateList* webStateList = self.tabModel.webStateList;
web::WebState* currentWebState = webStateList->GetActiveWebState();
for (NSInteger index = 0; index < webStateList->count(); index++) {
web::WebState* webState = webStateList->GetWebStateAt(index);
if (webState != currentWebState &&
retainedURL == webState->GetVisibleURL()) {
self.tabModel.webStateList->ActivateWebStateAt(index);
return;
}
}
}
#pragma mark - TabModelObserver methods
// Observer method, tab inserted.
......
......@@ -25,6 +25,7 @@
#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
#import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
#import "ios/chrome/browser/tabs/tab.h"
#import "ios/chrome/browser/tabs/tab_helper_util.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/tabs/tab_model_observer.h"
#import "ios/chrome/browser/tabs/tab_private.h"
......@@ -42,6 +43,7 @@
#import "ios/chrome/browser/web/error_page_content.h"
#include "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
#include "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_opener.h"
#import "ios/chrome/browser/web_state_list/web_usage_enabler/web_state_list_web_usage_enabler.h"
#import "ios/chrome/browser/web_state_list/web_usage_enabler/web_state_list_web_usage_enabler_factory.h"
#include "ios/chrome/grit/ios_strings.h"
......@@ -51,7 +53,11 @@
#import "ios/net/protocol_handler_util.h"
#import "ios/testing/ocmock_complex_type_helper.h"
#include "ios/web/public/referrer.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/test_navigation_manager.h"
#import "ios/web/public/test/fakes/test_web_state.h"
#include "ios/web/public/test/test_web_thread_bundle.h"
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#import "ios/web/public/web_state/ui/crw_native_content_provider.h"
#import "ios/web/web_state/ui/crw_web_controller.h"
#import "ios/web/web_state/web_state_impl.h"
......@@ -241,6 +247,18 @@ class BrowserViewControllerTest : public BlockCleanupTest {
BlockCleanupTest::TearDown();
}
// Returns a new unique_ptr containing a test webstate.
std::unique_ptr<web::TestWebState> CreateTestWebState() {
auto web_state = std::make_unique<web::TestWebState>();
web_state->SetBrowserState(chrome_browser_state_.get());
web_state->SetNavigationManager(
std::make_unique<web::TestNavigationManager>());
id mockJsInjectionReceiver = OCMClassMock([CRWJSInjectionReceiver class]);
web_state->SetJSInjectionReceiver(mockJsInjectionReceiver);
AttachTabHelpers(web_state.get(), true);
return web_state;
}
MOCK_METHOD0(OnCompletionCalled, void());
web::TestWebThreadBundle thread_bundle_;
......@@ -256,6 +274,33 @@ class BrowserViewControllerTest : public BlockCleanupTest {
UIWindow* window_;
};
TEST_F(BrowserViewControllerTest, TestSwitchToTab) {
WebStateList* web_state_list = tabModel_.webStateList;
ASSERT_EQ(0, web_state_list->count());
std::unique_ptr<web::TestWebState> web_state = CreateTestWebState();
web::WebState* web_state_ptr = web_state.get();
web_state->SetCurrentURL(GURL("http://test/1"));
web_state_list->InsertWebState(0, std::move(web_state),
WebStateList::INSERT_FORCE_INDEX,
WebStateOpener());
std::unique_ptr<web::TestWebState> web_state_2 = CreateTestWebState();
web::WebState* web_state_ptr_2 = web_state_2.get();
GURL url("http://test/2");
web_state_2->SetCurrentURL(url);
web_state_list->InsertWebState(1, std::move(web_state_2),
WebStateList::INSERT_FORCE_INDEX,
WebStateOpener());
web_state_list->ActivateWebStateAt(0);
ASSERT_EQ(web_state_ptr, web_state_list->GetActiveWebState());
[bvc_.dispatcher unfocusOmniboxAndSwitchToTabWithURL:url];
EXPECT_EQ(web_state_ptr_2, web_state_list->GetActiveWebState());
}
TEST_F(BrowserViewControllerTest, TestTabSelected) {
[bvc_ tabSelected:tab_ notifyToolbar:YES];
EXPECT_EQ([[tab_ view] superview], [bvc_ contentArea]);
......
......@@ -14,6 +14,7 @@
#import "ios/chrome/browser/ui/commands/qr_scanner_commands.h"
#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
class GURL;
@class OpenNewTabCommand;
@class ReadingListAddCommand;
......@@ -116,6 +117,9 @@
// omnibox.
- (void)focusFakebox;
// Unfocus omnibox then switch to the first tab displaying |URL|.
- (void)unfocusOmniboxAndSwitchToTabWithURL:(const GURL&)URL;
@end
#endif // IOS_CHROME_BROWSER_UI_COMMANDS_BROWSER_COMMANDS_H_
......@@ -22,7 +22,7 @@
// Tells the delegate when a suggestion in|row| was chosen for appending to
// omnibox.
- (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
didSelectRowForAppending:(NSUInteger)row;
didTapTrailingButtonForRow:(NSUInteger)row;
// Tells the delegate when a suggestion in |row| was removed.
- (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
didSelectRowForDeletion:(NSUInteger)row;
......
......@@ -52,6 +52,7 @@ source_set("popup_internal") {
"self_sizing_table_view.mm",
]
deps = [
"resources:omnibox_popup_tab_match",
"//base",
"//components/image_fetcher/ios",
"//components/omnibox/browser",
......@@ -73,9 +74,12 @@ source_set("unit_tests") {
]
deps = [
":popup",
":popup_internal",
"//base",
"//components/omnibox/browser",
"//ios/chrome/app/strings",
"//ios/chrome/browser",
"//ios/chrome/browser/ui/omnibox:omnibox_internal",
"//testing/gtest",
"//ui/base",
]
......
......@@ -61,6 +61,7 @@
self.mediator =
[[OmniboxPopupMediator alloc] initWithFetcher:std::move(imageFetcher)
delegate:_popupView.get()];
self.mediator.dispatcher = (id<BrowserCommands>)self.dispatcher;
self.popupViewController = [[OmniboxPopupViewController alloc] init];
self.popupViewController.incognito = self.browserState->IsOffTheRecord();
......
......@@ -11,6 +11,7 @@
#import "ios/chrome/browser/ui/omnibox/autocomplete_result_consumer.h"
#import "ios/chrome/browser/ui/omnibox/image_retriever.h"
@protocol BrowserCommands;
@class OmniboxPopupPresenter;
namespace image_fetcher {
......@@ -48,6 +49,7 @@ class OmniboxPopupMediatorDelegate {
// Updates the popup with the |results|.
- (void)updateWithResults:(const AutocompleteResult&)results;
@property(nonatomic, weak) id<BrowserCommands> dispatcher;
@property(nonatomic, weak) id<AutocompleteResultConsumer> consumer;
@property(nonatomic, assign, getter=isIncognito) BOOL incognito;
// Whether the popup is open.
......
......@@ -10,6 +10,7 @@
#include "components/omnibox/browser/autocomplete_input.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/omnibox/browser/autocomplete_result.h"
#import "ios/chrome/browser/ui/commands/browser_commands.h"
#import "ios/chrome/browser/ui/omnibox/autocomplete_match_formatter.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_presenter.h"
......@@ -113,19 +114,22 @@
}
- (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
didSelectRowForAppending:(NSUInteger)row {
didTapTrailingButtonForRow:(NSUInteger)row {
const AutocompleteMatch& match =
((const AutocompleteResult&)_currentResult).match_at(row);
if (AutocompleteMatch::IsSearchType(match.type)) {
base::RecordAction(
base::UserMetricsAction("MobileOmniboxRefineSuggestion.Search"));
if (match.has_tab_match) {
[self.dispatcher unfocusOmniboxAndSwitchToTabWithURL:match.destination_url];
} else {
base::RecordAction(
base::UserMetricsAction("MobileOmniboxRefineSuggestion.Url"));
if (AutocompleteMatch::IsSearchType(match.type)) {
base::RecordAction(
base::UserMetricsAction("MobileOmniboxRefineSuggestion.Search"));
} else {
base::RecordAction(
base::UserMetricsAction("MobileOmniboxRefineSuggestion.Url"));
}
_delegate->OnMatchSelectedForAppending(match);
}
_delegate->OnMatchSelectedForAppending(match);
}
- (void)autocompleteResultConsumer:(id<AutocompleteResultConsumer>)sender
......
......@@ -24,8 +24,11 @@
@property(nonatomic, readonly, strong) UIImageView* imageView;
@property(nonatomic, readonly, strong) UIImageView* answerImageView;
@property(nonatomic, readonly, strong) UIButton* appendButton;
@property(nonatomic, readonly, strong) UIButton* trailingButton;
@property(nonatomic, assign) CGFloat rowHeight;
// Whether this row is displaying a TabMatch. If YES, the trailing icon is
// updated to reflect that.
@property(nonatomic, assign, getter=isTabMatch) BOOL tabMatch;
// Initialize the row with the given incognito state. The colors and styling are
// dependent on whether or not the row is displayed in incognito mode.
......
......@@ -20,8 +20,8 @@ const CGFloat kImageDimensionLength = 19.0;
// Side (w or h) length for the leading image view.
const CGFloat kImageViewSizeUIRefresh = 28.0;
const CGFloat kImageViewCornerRadiusUIRefresh = 7.0;
const CGFloat kAppendButtonTrailingMargin = 4;
const CGFloat kAppendButtonSize = 48.0;
const CGFloat kTrailingButtonTrailingMargin = 4;
const CGFloat kTrailingButtonSize = 48.0;
}
@interface OmniboxPopupRow () {
......@@ -29,7 +29,7 @@ const CGFloat kAppendButtonSize = 48.0;
}
// Set the append button normal and highlighted images.
- (void)updateAppendButtonImages;
- (void)updateTrailingButtonImages;
@end
......@@ -38,7 +38,7 @@ const CGFloat kAppendButtonSize = 48.0;
@synthesize textTruncatingLabel = _textTruncatingLabel;
@synthesize detailTruncatingLabel = _detailTruncatingLabel;
@synthesize detailAnswerLabel = _detailAnswerLabel;
@synthesize appendButton = _appendButton;
@synthesize trailingButton = _trailingButton;
@synthesize answerImageView = _answerImageView;
@synthesize imageView = _imageView;
@synthesize rowHeight = _rowHeight;
......@@ -73,12 +73,12 @@ const CGFloat kAppendButtonSize = 48.0;
_detailAnswerLabel.lineBreakMode = NSLineBreakByTruncatingTail;
[self.contentView addSubview:_detailAnswerLabel];
_appendButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_appendButton setContentMode:UIViewContentModeRight];
[self updateAppendButtonImages];
_trailingButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_trailingButton setContentMode:UIViewContentModeRight];
[self updateTrailingButtonImages];
// TODO(justincohen): Consider using the UITableViewCell's accessory view.
// The current implementation is from before using a UITableViewCell.
[self.contentView addSubview:_appendButton];
[self.contentView addSubview:_trailingButton];
// Before UI Refresh, the leading icon is only displayed on iPad. In UI
// Refresh, it's only in Regular x Regular size class.
......@@ -134,11 +134,11 @@ const CGFloat kAppendButtonSize = 48.0;
LayoutRect trailingAccessoryLayout =
LayoutRectMake(CGRectGetWidth(self.contentView.bounds) -
kAppendButtonSize - kAppendButtonTrailingMargin,
kTrailingButtonSize - kTrailingButtonTrailingMargin,
CGRectGetWidth(self.contentView.bounds),
floor((_rowHeight - kAppendButtonSize) / 2),
kAppendButtonSize, kAppendButtonSize);
_appendButton.frame = LayoutRectGetRect(trailingAccessoryLayout);
floor((_rowHeight - kTrailingButtonSize) / 2),
kTrailingButtonSize, kTrailingButtonSize);
_trailingButton.frame = LayoutRectGetRect(trailingAccessoryLayout);
}
- (void)updateLeadingImage:(UIImage*)image {
......@@ -173,27 +173,38 @@ const CGFloat kAppendButtonSize = 48.0;
[self updateHighlightBackground:highlighted];
}
- (void)updateAppendButtonImages {
int appendResourceID = _incognito
? IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND_INCOGNITO
: IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND;
UIImage* appendImage = NativeReversableImage(appendResourceID, YES);
- (void)setTabMatch:(BOOL)tabMatch {
_tabMatch = tabMatch;
[self updateTrailingButtonImages];
}
- (void)updateTrailingButtonImages {
UIImage* appendImage = nil;
if (self.tabMatch) {
appendImage = [UIImage imageNamed:@"omnibox_popup_tab_match"];
} else {
int appendResourceID = _incognito
? IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND_INCOGNITO
: IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND;
appendImage = NativeReversableImage(appendResourceID, YES);
}
if (IsUIRefreshPhase1Enabled()) {
appendImage =
[appendImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
_appendButton.tintColor = _incognito ? [UIColor colorWithWhite:1 alpha:0.5]
: [UIColor colorWithWhite:0 alpha:0.3];
_trailingButton.tintColor = _incognito
? [UIColor colorWithWhite:1 alpha:0.5]
: [UIColor colorWithWhite:0 alpha:0.3];
} else {
int appendSelectedResourceID =
_incognito ? IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND_INCOGNITO_HIGHLIGHTED
: IDR_IOS_OMNIBOX_KEYBOARD_VIEW_APPEND_HIGHLIGHTED;
UIImage* appendImageSelected =
NativeReversableImage(appendSelectedResourceID, YES);
[_appendButton setImage:appendImageSelected
forState:UIControlStateHighlighted];
[_trailingButton setImage:appendImageSelected
forState:UIControlStateHighlighted];
}
[_appendButton setImage:appendImage forState:UIControlStateNormal];
[_trailingButton setImage:appendImage forState:UIControlStateNormal];
}
- (NSString*)accessibilityLabel {
......
......@@ -153,10 +153,10 @@ UIColor* BackgroundColorIncognito() {
[NSString stringWithFormat:@"omnibox suggestion %i", i];
row.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[rowsBuilder addObject:row];
[row.appendButton addTarget:self
action:@selector(appendButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
[row.appendButton setTag:i];
[row.trailingButton addTarget:self
action:@selector(trailingButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
[row.trailingButton setTag:i];
row.rowHeight = kRowHeight;
}
_rows = [rowsBuilder copy];
......@@ -284,7 +284,7 @@ UIColor* BackgroundColorIncognito() {
const CGFloat kDetailCellTopPadding = 26;
const CGFloat kTextLabelHeight = 24;
const CGFloat kTextDetailLabelHeight = 22;
const CGFloat kAppendButtonWidth = 40;
const CGFloat kTrailingButtonWidth = 40;
const CGFloat kAnswerLabelHeight = 36;
const CGFloat kAnswerImageWidth = 30;
const CGFloat kAnswerImageLeftPadding = -1;
......@@ -308,7 +308,7 @@ UIColor* BackgroundColorIncognito() {
kTextCellLeadingPadding + kAnswerImageLeftPadding;
if (alignmentRight) {
imageLeftPadding =
row.frame.size.width - (kAnswerImageWidth + kAppendButtonWidth);
row.frame.size.width - (kAnswerImageWidth + kTrailingButtonWidth);
}
CGFloat imageTopPadding = kDetailCellTopPadding + kAnswerImageTopPadding;
row.answerImageView.frame =
......@@ -396,22 +396,24 @@ UIColor* BackgroundColorIncognito() {
[row updateLeadingImage:image];
}
row.tabMatch = match.isTabMatch;
// Show append button for search history/search suggestions as the right
// control element (aka an accessory element of a table view cell).
row.appendButton.hidden = !match.isAppendable;
[row.appendButton cancelTrackingWithEvent:nil];
row.trailingButton.hidden = !match.isAppendable && !match.isTabMatch;
[row.trailingButton cancelTrackingWithEvent:nil];
// If a right accessory element is present or the text alignment is right
// aligned, adjust the width to align with the accessory element.
if (match.isAppendable || alignmentRight) {
LayoutRect layout =
LayoutRectForRectInBoundingRect(textLabel.frame, self.view.frame);
layout.size.width -= kAppendButtonWidth;
layout.size.width -= kTrailingButtonWidth;
textLabel.frame = LayoutRectGetRect(layout);
layout =
LayoutRectForRectInBoundingRect(detailTextLabel.frame, self.view.frame);
layout.size.width -=
kAppendButtonWidth + (match.hasImage ? answerImagePadding : 0);
kTrailingButtonWidth + (match.hasImage ? answerImagePadding : 0);
detailTextLabel.frame = LayoutRectGetRect(layout);
}
......@@ -524,9 +526,10 @@ UIColor* BackgroundColorIncognito() {
#pragma mark -
#pragma mark Action for append UIButton
- (void)appendButtonTapped:(id)sender {
- (void)trailingButtonTapped:(id)sender {
NSUInteger row = [sender tag];
[self.delegate autocompleteResultConsumer:self didSelectRowForAppending:row];
[self.delegate autocompleteResultConsumer:self
didTapTrailingButtonForRow:row];
}
#pragma mark -
......
......@@ -4,6 +4,9 @@
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_view_controller.h"
#include "components/omnibox/browser/autocomplete_match.h"
#import "ios/chrome/browser/ui/omnibox/autocomplete_match_formatter.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"
......@@ -23,14 +26,14 @@ class OmniboxPopupViewControllerTest : public PlatformTest {
OmniboxPopupViewController* popup_view_controller_;
};
TEST_F(OmniboxPopupViewControllerTest, hasCellsWhenShortcutsEnabled) {
TEST_F(OmniboxPopupViewControllerTest, HasCellsWhenShortcutsEnabled) {
// This test makes an assumption that this view controller is a datasource for
// a table view. Rewrite this test if this is not the case anymore.
EXPECT_TRUE([popup_view_controller_
conformsToProtocol:@protocol(UITableViewDataSource)]);
id<UITableViewDataSource> datasource =
(id<UITableViewDataSource>)popup_view_controller_;
UITableView* tableView = [[UITableView alloc] init];
UITableView* table_view = [[UITableView alloc] init];
// A stub view controller.
UIViewController* shortcutsViewController = [[UIViewController alloc] init];
......@@ -40,17 +43,52 @@ TEST_F(OmniboxPopupViewControllerTest, hasCellsWhenShortcutsEnabled) {
// Check that the shorcuts row doesn't appear.
[popup_view_controller_ updateMatches:@[] withAnimation:NO];
EXPECT_EQ([datasource tableView:tableView numberOfRowsInSection:0], 0);
EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 0);
// Enable shortcuts and verify they appear. When enabling, the view controller
// has to be non-nil.
popup_view_controller_.shortcutsViewController = shortcutsViewController;
popup_view_controller_.shortcutsEnabled = YES;
EXPECT_EQ([datasource tableView:tableView numberOfRowsInSection:0], 1);
EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 1);
// Disable and verify it disappears again.
popup_view_controller_.shortcutsEnabled = NO;
EXPECT_EQ([datasource tableView:tableView numberOfRowsInSection:0], 0);
EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 0);
}
TEST_F(OmniboxPopupViewControllerTest, HasTabMatch) {
EXPECT_TRUE([popup_view_controller_
conformsToProtocol:@protocol(UITableViewDataSource)]);
id<UITableViewDataSource> datasource =
(id<UITableViewDataSource>)popup_view_controller_;
UITableView* table_view = [[UITableView alloc] init];
// Check that if the match has a tab match, the cell's trailing button is
// visible.
AutocompleteMatch match;
match.has_tab_match = true;
AutocompleteMatchFormatter* formatter =
[[AutocompleteMatchFormatter alloc] initWithMatch:match];
[popup_view_controller_ updateMatches:@[ formatter ] withAnimation:NO];
EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 1);
OmniboxPopupRow* cell = static_cast<OmniboxPopupRow*>([datasource
tableView:table_view
cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]);
EXPECT_FALSE(cell.trailingButton.hidden);
// Check that it is not the case if the tab match isn't visible.
match.has_tab_match = false;
formatter = [[AutocompleteMatchFormatter alloc] initWithMatch:match];
[popup_view_controller_ updateMatches:@[ formatter ] withAnimation:NO];
EXPECT_EQ([datasource tableView:table_view numberOfRowsInSection:0], 1);
cell = static_cast<OmniboxPopupRow*>([datasource
tableView:table_view
cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]);
EXPECT_TRUE(cell.trailingButton.hidden);
}
} // namespace
# 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("//build/config/ios/asset_catalog.gni")
imageset("omnibox_popup_tab_match") {
sources = [
"omnibox_popup_tab_match.imageset/Contents.json",
"omnibox_popup_tab_match.imageset/omnibox_popup_tab_match.png",
"omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@2x.png",
"omnibox_popup_tab_match.imageset/omnibox_popup_tab_match@3x.png",
]
}
{
"images": [
{
"idiom": "universal",
"scale": "1x",
"filename": "omnibox_popup_tab_match.png"
},
{
"idiom": "universal",
"scale": "2x",
"filename": "omnibox_popup_tab_match@2x.png"
},
{
"idiom": "universal",
"scale": "3x",
"filename": "omnibox_popup_tab_match@3x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment