Commit 0014aa02 authored by Regan Hsu's avatar Regan Hsu Committed by Commit Bot

[CrOS PhoneHub] Add Browser Tab Model to chrome://multidevice-internals

* Users can now set 2 tab model metadatas.
* If any fields are empty, the url metadata will be treated as nullopt.
* 3rd and 4th tabs will be added later.

Screenshots
https://screenshot.googleplex.com/AwV2evHm2c7QCMU
https://screenshot.googleplex.com/6q7tXZFAH3K8pSL
https://screenshot.googleplex.com/7rgRSDiU2EqKwXE
https://screenshot.googleplex.com/7mJtxtdg3GrDF6q

Bug: 1106937
Change-Id: I20872614efa1caa889267c1b01d4406255c60be1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2391647
Commit-Queue: Regan Hsu <hsuregan@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#804145}
parent d3c720e6
......@@ -8,17 +8,30 @@ import("//tools/polymer/html_to_js.gni")
js_type_check("closure_compile") {
is_polymer3 = true
deps = [
":browser_tabs_metadata_form",
":log_object",
":logging_tab",
":multidevice_internals",
":multidevice_logs_browser_proxy",
":multidevice_phonehub_browser_proxy",
":multidevice_phonehub_browser_proxy",
":phone_status_model_form",
":phonehub_tab",
":types",
]
}
js_library("browser_tabs_model_form") {
deps = [
":multidevice_phonehub_browser_proxy",
":types",
]
}
js_library("browser_tabs_metadata_form") {
deps = [ ":types" ]
}
js_library("phone_status_model_form") {
deps = [
":multidevice_phonehub_browser_proxy",
......@@ -28,6 +41,7 @@ js_library("phone_status_model_form") {
js_library("phonehub_tab") {
deps = [
":browser_tabs_model_form",
":multidevice_phonehub_browser_proxy",
":phone_status_model_form",
":types",
......@@ -91,5 +105,7 @@ html_to_js("web_components") {
"phonehub_tab.js",
"phone_status_model_form.js",
"shared_style.js",
"browser_tabs_metadata_form.js",
"browser_tabs_model_form.js",
]
}
<style include="cr-shared-style shared-style">
:host {
display: flex;
flex: 1 0 100%;
}
#faviconSelector {
align-items: normal;
}
</style>
<div class="column">
<div class="label">URL: </div>
<cr-input value="{{url_}}" id="urlInput" auto-validate required
error-message="This browser tab will be a nullopt if no url input">
</cr-input>
</div>
<div class="column">
<div class="label">Title: </div>
<cr-input value="{{title_}}" id="titleInput" auto-validate required
error-message="This browser tab will be a nullopt if no title input">
</cr-input>
</div>
<div class="column">
<div class="label">Last Accessed (ms): </div>
<cr-input value="{{lastAccessedTimeStamp_}}"
id="lastAccessedTimeStampInput" type="number" min="0"
on-change="onLastAccessTimeStampChanged_"
auto-validate error-message="Must be greater than 0" required>
</cr-input>
</div>
<div class="column" id="faviconSelector">
<div class="label">Favicon selector: </div>
<select id="faviconList" class="md-select"
on-change="onFaviconSelected_">
<template is="dom-repeat" items="[[faviconList_]]">
<option>[[getFaviconTypeName_(item)]]</option>
</template>
</select>
</div>
// Copyright 2020 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 'chrome://resources/cr_elements/shared_style_css.m.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
import './shared_style.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {BrowserTabsMetadataModel, FaviconType} from './types.js';
/**
* Maps a FaviconType to its title label in the dropdown.
* @type {!Map<FaviconType, String>}
*/
const faviconTypeToStringMap = new Map([
[FaviconType.PINK, 'Pink'],
[FaviconType.RED, 'Red'],
[FaviconType.GREEN, 'Green'],
[FaviconType.BLUE, 'Blue'],
[FaviconType.YELLOW, 'Yellow'],
]);
Polymer({
is: 'browser-tabs-metadata-form',
_template: html`{__html_template__}`,
properties: {
/** @type{BrowserTabsMetadataModel} */
browserTabMetadata: {
type: Object,
notify: true,
computed: 'getMetadata_(url_, title_, lastAccessedTimeStamp_, favicon_)',
},
/** @private */
url_: {
type: String,
value: 'https://www.google.com/',
},
/** @private */
title_: {
type: String,
value: 'Google',
},
/** @private */
lastAccessedTimeStamp_: {
type: Number,
value: Date.now(),
},
/** @private{FaviconType} */
favicon_: {
type: Number,
value: FaviconType.PINK,
},
/** @private */
faviconList_: {
type: Array,
value: () => {
return [
FaviconType.PINK,
FaviconType.RED,
FaviconType.GREEN,
FaviconType.BLUE,
FaviconType.YELLOW,
];
},
readonly: true,
},
},
/**
* @param {FaviconType} faviconType
* @return {String}
* @private
*/
getFaviconTypeName_(faviconType) {
return faviconTypeToStringMap.get(faviconType);
},
/** @private */
onFaviconSelected_() {
const select = /** @type {!HTMLSelectElement} */
(this.$$('#faviconList'));
this.favicon_ = this.faviconList_[select.selectedIndex];
},
/**
* @return{BrowserTabsMetadataModel}
* @private
*/
getMetadata_() {
return {
url: this.url_,
title: this.title_,
lastAccessedTimeStamp: this.lastAccessedTimeStamp_,
favicon: this.favicon_,
};
},
/** @private */
onLastAccessTimeStampChanged_() {
const inputValue = this.$$('#lastAccessedTimeStampInput').value;
if (inputValue < 0) {
this.lastAccessedTimeStamp_ = 0;
return;
}
this.lastAccessedTimeStamp_ = Number(inputValue);
},
});
<style include="cr-shared-style shared-style">
:host {
display: flex;
height: 250px;
}
#fieldColumn {
flex: 2;
}
</style>
<div class="column">
<cr-button on-click="setFakeBrowserTabModel_" class="internals-button">
<span class="emphasize">Change Browser Tabs Status</span>
</cr-button>
<div class="label">
<span class="emphasize">Note:</span> Click the button above to propagate all
browser tab status values on the right hand side to the fake phonehub
manager. Note that if a field is empty, then that corresponding tab will
be nullopt.
</div>
</div>
<div class="column" id="fieldColumn">
<div class="cr-padded-text">
Toggle Tab Sync enabled
</div>
<cr-toggle checked="{{isTabSyncEnabled_}}">
</cr-toggle>
<template is="dom-if" if="[[isTabSyncEnabled_]]" restamp>
<div class="cr-row">
<div class="column">1: </div>
<browser-tabs-metadata-form
browser-tab-metadata="{{browserTabOneMetadata_}}">
</browser-tabs-metadata-form>
</div>
<div class="cr-row">
<div class="column">2: </div>
<browser-tabs-metadata-form
browser-tab-metadata="{{browserTabTwoMetadata_}}">
</browser-tabs-metadata-form>
</div>
</template>
</div>
// Copyright 2020 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 'chrome://resources/cr_elements/shared_style_css.m.js';
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
import './shared_style.js';
import './browser_tabs_metadata_form.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {MultidevicePhoneHubBrowserProxy} from './multidevice_phonehub_browser_proxy.js';
import {BrowserTabsMetadataModel, BrowserTabsModel} from './types.js';
Polymer({
is: 'browser-tabs-model-form',
_template: html`{__html_template__}`,
properties: {
/** @private */
isTabSyncEnabled_: {
type: Boolean,
value: false,
},
/** @private{BrowserTabsMetadataModel} */
browserTabOneMetadata_: {
type: Object,
},
/** @private{BrowserTabsMetadataModel} */
browserTabTwoMetadata_: {
type: Object,
},
},
/** @private{?MultidevicePhoneHubBrowserProxy}*/
browserProxy_: null,
/** @override */
created() {
this.browserProxy_ = MultidevicePhoneHubBrowserProxy.getInstance();
},
/** @private */
setFakeBrowserTabModel_() {
const browserTabsModel = {
isTabSyncEnabled: this.isTabSyncEnabled_,
browserTabOneMetadata:
this.isTabSyncEnabled_ ? this.browserTabOneMetadata_ : null,
browserTabTwoMetadata:
this.isTabSyncEnabled_ ? this.browserTabTwoMetadata_ : null,
};
this.browserProxy_.setBrowserTabs(browserTabsModel);
},
});
......@@ -12,6 +12,14 @@
</outputs>
<release seq="1">
<includes>
<include name="IDR_MULTIDEVICE_INTERNALS_BROWSER_TABS_METADATA_FORM_JS"
file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\browser_tabs_metadata_form.js"
use_base_dir="false"
type="BINDATA"/>
<include name="IDR_MULTIDEVICE_INTERNALS_BROWSER_TABS_MODEL_FORM_JS"
file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\browser_tabs_model_form.js"
use_base_dir="false"
type="BINDATA"/>
<include name="IDR_MULTIDEVICE_INTERNALS_INDEX_HTML"
file="index.html"
type="BINDATA"/>
......
......@@ -3,7 +3,7 @@
// found in the LICENSE file.
import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
import {FeatureStatus, PhoneStatusModel} from './types.js';
import {BrowserTabsModel, FeatureStatus, PhoneStatusModel} from './types.js';
/**
* JavaScript hooks into the native WebUI handler for Phonehub tab.
......@@ -33,6 +33,15 @@ export class MultidevicePhoneHubBrowserProxy {
setFakePhoneStatus(phoneStatusModel) {
chrome.send('setFakePhoneStatus', [phoneStatusModel]);
}
/**
* Sets the browser tabs model.
* @param {!BrowserTabsModel} browserTabsModel The browser tab model with fake
* values.
*/
setBrowserTabs(browserTabsModel) {
chrome.send('setBrowserTabs', [browserTabsModel]);
}
}
addSingletonGetter(MultidevicePhoneHubBrowserProxy);
......@@ -4,15 +4,6 @@
flex: 0 0 100%;
}
.column {
align-items: center;
display: flex;
flex: 1;
flex-wrap: wrap;
justify-content: center;
margin: 10px;
}
.phone-status-property {
flex-basis: 100%;
justify-content: center;
......@@ -23,14 +14,6 @@
flex-wrap: wrap;
}
.label {
width: 100%;
}
cr-button {
width: 100%;
}
select {
margin-bottom: 10px;
width: 100%;
......
......@@ -63,13 +63,13 @@ Polymer({
/** @private{MobileStatus} */
mobileStatus_: {
type: Number,
value: MobileStatus.SIM_WITH_RECEPTION,
value: MobileStatus.NO_SIM,
},
/** @private{SignalStrength}*/
signalStrength_: {
type: Number,
value: SignalStrength.TWO_BARS,
value: SignalStrength.ZERO_BARS,
},
/** @private */
......@@ -217,6 +217,7 @@ Polymer({
/**
* @param {MobileStatus} mobileStatus
* @return {String}
* @private
*/
getMobileStatusName_(mobileStatus) {
......@@ -225,6 +226,7 @@ Polymer({
/**
* @param {SignalStrength} signalStrength
* @return {String}
* @private
*/
getSignalStrengthName_(signalStrength) {
......@@ -233,6 +235,7 @@ Polymer({
/**
* @param {ChargingState} chargingState
* @return {String}
* @private
*/
getChargingStateName_(chargingState) {
......@@ -241,6 +244,7 @@ Polymer({
/**
* @param {BatterySaverState} batterySaverState
* @return {String}
* @private
*/
getBatterySaverStateName_(batterySaverState) {
......
......@@ -8,10 +8,6 @@
display: flex;
flex: 0 0 100%;
}
.cr-padded-text {
margin: 10px;
}
</style>
<div class="cr-row">
......@@ -34,12 +30,15 @@
</select>
<div class="cr-padded-text" hidden="[[isFeatureEnabledAndConnected_]]">
More controls will appear if the feature status is set to
<div class="emphasize">ENABLED_AND_CONNECTED</div>.
<span class="emphasize">ENABLED_AND_CONNECTED</span>.
</div>
</div>
<template is="dom-if" if="[[isFeatureEnabledAndConnected_]]" restamp>
<div class="cr-row">
<phone-status-model-form></phone-status-model-form>
</div>
<div class="cr-row">
<browser-tabs-model-form></browser-tabs-model-form>
</div>
</template>
</template>
......@@ -5,8 +5,9 @@
import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
import 'chrome://resources/cr_elements/md_select_css.m.js';
import 'chrome://resources/cr_elements/shared_style_css.m.js';
import './shared_style.js';
import './browser_tabs_model_form.js';
import './phone_status_model_form.js';
import './shared_style.js';
import {flush, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {MultidevicePhoneHubBrowserProxy} from './multidevice_phonehub_browser_proxy.js';
......@@ -77,7 +78,7 @@ Polymer({
isFeatureEnabledAndConnected_: {
type: Boolean,
computed: 'isFeatureEnabledAndConnectedComputed_(featureStatus_)',
}
},
},
/** @private {?MultidevicePhoneHubBrowserProxy}*/
......
......@@ -13,8 +13,29 @@
margin: 5px;
}
.label {
width: 100%;
}
.column {
align-items: center;
display: flex;
flex: 1;
flex-wrap: wrap;
justify-content: center;
margin: 10px;
}
.column cr-button {
width: 100%;
}
.emphasize {
font-weight: bold;
}
.cr-padded-text {
margin: 10px;
}
</style>
</template>
......@@ -68,7 +68,7 @@ export const SignalStrength = {
/**
* Numerical values should not be changed because they must stay in sync with
* ChargingState inchromeos/components/phonehub/phone_status_model.h.
* ChargingState in chromeos/components/phonehub/phone_status_model.h.
* @enum{number}
*/
export const ChargingState = {
......@@ -79,7 +79,7 @@ export const ChargingState = {
/**
* Numerical values should not be changed because they must stay in sync with
* BatterySaverState inchromeos/components/phonehub/phone_status_model.h.
* BatterySaverState in chromeos/components/phonehub/phone_status_model.h.
* @enum{number}
*/
export const BatterySaverState = {
......@@ -87,6 +87,19 @@ export const BatterySaverState = {
ON: 1,
};
/**
* Numerical values should not be changed because they must stay in sync with
* FaviconType in chromeos/components/phonehub/phone_status_model.cc.
* @enum{number}
*/
export const FaviconType = {
PINK: 0,
RED: 1,
GREEN: 2,
BLUE: 3,
YELLOW: 4,
};
/**
* @typedef {{
* mobileStatus: !MobileStatus,
......@@ -98,3 +111,22 @@ export const BatterySaverState = {
* }}
*/
export let PhoneStatusModel;
/**
* @typedef {{
* url: string,
* title: string,
* lastAccessedTimeStamp: number,
* favicon: !FaviconType,
* }}
*/
export let BrowserTabsMetadataModel;
/**
* @typedef {{
* isTabSyncEnabled: boolean,
* browserTabOneMetadata: ?BrowserTabsMetadataModel,
* browserTabTwoMetadata: ?BrowserTabsMetadataModel,
* }}
*/
export let BrowserTabsModel;
......@@ -5,14 +5,89 @@
#include "chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_phone_hub_handler.h"
#include "ash/public/cpp/system_tray.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/phonehub/phone_hub_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/components/phonehub/fake_phone_hub_manager.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/image/image.h"
namespace chromeos {
namespace multidevice {
namespace {
// Fake Favicon colors used for coloring the fake favicon bitmaps.
enum class FaviconType {
kPink = 0,
kRed = 1,
kGreen = 2,
kBlue = 3,
kYellow = 4,
};
const SkBitmap FaviconNumToBitmap(FaviconType favicon_num) {
SkBitmap bitmap;
bitmap.allocN32Pixels(16, 16);
switch (favicon_num) {
case FaviconType::kPink:
bitmap.eraseARGB(0, 255, 192, 203);
break;
case FaviconType::kRed:
bitmap.eraseARGB(0, 255, 0, 0);
break;
case FaviconType::kGreen:
bitmap.eraseARGB(0, 0, 255, 0);
break;
case FaviconType::kBlue:
bitmap.eraseARGB(0, 0, 0, 255);
break;
case FaviconType::kYellow:
bitmap.eraseARGB(0, 255, 255, 0);
break;
default:
break;
}
return bitmap;
}
base::Optional<phonehub::BrowserTabsModel::BrowserTabMetadata>
DictToBrowserTabMetadataModel(
const base::DictionaryValue* browser_tab_metadata) {
std::string url;
if (!browser_tab_metadata->GetString("url", &url) || url.empty()) {
return base::nullopt;
}
base::string16 title;
if (!browser_tab_metadata->GetString("title", &title) || title.empty()) {
return base::nullopt;
}
// JavaScript time stamps don't fit in int.
double last_accessed_timestamp;
if (!browser_tab_metadata->GetDouble("lastAccessedTimeStamp",
&last_accessed_timestamp)) {
return base::nullopt;
}
int favicon_type_as_int;
if (!browser_tab_metadata->GetInteger("favicon", &favicon_type_as_int)) {
return base::nullopt;
}
auto favicon_type = static_cast<FaviconType>(favicon_type_as_int);
gfx::Image favicon =
gfx::Image::CreateFrom1xBitmap(FaviconNumToBitmap(favicon_type));
return phonehub::BrowserTabsModel::BrowserTabMetadata(
GURL(url), title, base::Time::FromJsTime(last_accessed_timestamp),
favicon);
}
} // namespace
MultidevicePhoneHubHandler::MultidevicePhoneHubHandler() = default;
MultidevicePhoneHubHandler::~MultidevicePhoneHubHandler() {
......@@ -36,6 +111,11 @@ void MultidevicePhoneHubHandler::RegisterMessages() {
"setFakePhoneStatus",
base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetFakePhoneStatus,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setBrowserTabs",
base::BindRepeating(&MultidevicePhoneHubHandler::HandleSetBrowserTabs,
base::Unretained(this)));
}
void MultidevicePhoneHubHandler::SetSystemPhoneHubManagerEnabled() {
......@@ -130,5 +210,50 @@ void MultidevicePhoneHubHandler::HandleSetFakePhoneStatus(
<< "\n battery percentage: " << battery_percentage;
}
void MultidevicePhoneHubHandler::HandleSetBrowserTabs(
const base::ListValue* args) {
const base::DictionaryValue* browser_tab_status_dict = nullptr;
CHECK(args->GetDictionary(0, &browser_tab_status_dict));
bool is_tab_sync_enabled;
CHECK(browser_tab_status_dict->GetBoolean("isTabSyncEnabled",
&is_tab_sync_enabled));
if (!is_tab_sync_enabled) {
fake_phone_hub_manager_->mutable_phone_model()->SetBrowserTabsModel(
phonehub::BrowserTabsModel(is_tab_sync_enabled));
PA_LOG(VERBOSE) << "Tab sync off; cleared browser tab metadata";
return;
}
base::Optional<phonehub::BrowserTabsModel::BrowserTabMetadata> metadata_one;
const base::DictionaryValue* browser_tab_one_metadata = nullptr;
if (browser_tab_status_dict->GetDictionary("browserTabOneMetadata",
&browser_tab_one_metadata)) {
metadata_one = DictToBrowserTabMetadataModel(browser_tab_one_metadata);
}
base::Optional<phonehub::BrowserTabsModel::BrowserTabMetadata> metadata_two;
const base::DictionaryValue* browser_tab_two_metadata = nullptr;
if (browser_tab_status_dict->GetDictionary("browserTabTwoMetadata",
&browser_tab_two_metadata)) {
metadata_two = DictToBrowserTabMetadataModel(browser_tab_two_metadata);
}
// TODO(hsuregan): Add metadata_three and metadata_four.
std::vector<base::Optional<phonehub::BrowserTabsModel::BrowserTabMetadata>>
metadatas{{metadata_one, metadata_two}};
std::sort(metadatas.begin(), metadatas.end());
fake_phone_hub_manager_->mutable_phone_model()->SetBrowserTabsModel(
phonehub::BrowserTabsModel(is_tab_sync_enabled, metadatas[1],
metadatas[0]));
if (metadatas[1].has_value())
PA_LOG(VERBOSE) << "Set most recent browser tab to" << *metadatas[1];
if (metadatas[0].has_value())
PA_LOG(VERBOSE) << "Set second most recent browser tab to" << *metadatas[0];
}
} // namespace multidevice
} // namespace chromeos
......@@ -35,6 +35,7 @@ class MultidevicePhoneHubHandler : public content::WebUIMessageHandler {
void HandleSetFakePhoneHubManagerEnabled(const base::ListValue* args);
void HandleSetFeatureStatus(const base::ListValue* args);
void HandleSetFakePhoneStatus(const base::ListValue* args);
void HandleSetBrowserTabs(const base::ListValue* args);
std::unique_ptr<phonehub::FakePhoneHubManager> fake_phone_hub_manager_;
};
......
......@@ -34,6 +34,12 @@ bool BrowserTabsModel::BrowserTabMetadata::operator!=(
return !(*this == other);
}
bool BrowserTabsModel::BrowserTabMetadata::operator<(
const BrowserTabMetadata& other) const {
return std::tie(last_accessed_timestamp) <
std::tie(other.last_accessed_timestamp);
}
BrowserTabsModel::BrowserTabsModel(
bool is_tab_sync_enabled,
const base::Optional<BrowserTabMetadata>& most_recent_tab,
......
......@@ -28,6 +28,7 @@ class BrowserTabsModel {
bool operator==(const BrowserTabMetadata& other) const;
bool operator!=(const BrowserTabMetadata& other) const;
bool operator<(const BrowserTabMetadata& other) const;
GURL url;
base::string16 title;
......
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