The Great Blink mv for source files, part 2.

Move and rename files.

NOAUTOREVERT=true
NOPRESUBMIT=true
NOTREECHECKS=true
Bug: 768828
[email protected]
NOTRY=true

Change-Id: I66d3b155808bc5bdbf237b80208e1e552bcf7f28
Reviewed-on: https://chromium-review.googlesource.com/1001153
Reviewed-by: Blink Reformat <[email protected]>
Commit-Queue: Blink Reformat <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#549061}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 0aee4434a4dba42a42abaea9bfbc0cd196a63bc1
diff --git a/front_end/mobile_throttling/MobileThrottlingSelector.js b/front_end/mobile_throttling/MobileThrottlingSelector.js
new file mode 100644
index 0000000..b7c246f
--- /dev/null
+++ b/front_end/mobile_throttling/MobileThrottlingSelector.js
@@ -0,0 +1,52 @@
+// 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.
+
+MobileThrottling.MobileThrottlingSelector = class {
+  /**
+   * @param {function(!Array<!MobileThrottling.MobileThrottlingConditionsGroup>):!MobileThrottling.ConditionsList} populateCallback
+   * @param {function(number)} selectCallback
+   */
+  constructor(populateCallback, selectCallback) {
+    this._populateCallback = populateCallback;
+    this._selectCallback = selectCallback;
+    MobileThrottling.throttlingManager().addEventListener(
+        MobileThrottling.ThrottlingManager.Events.RateChanged, this._conditionsChanged, this);
+    SDK.multitargetNetworkManager.addEventListener(
+        SDK.MultitargetNetworkManager.Events.ConditionsChanged, this._conditionsChanged, this);
+    /** @type {!MobileThrottling.ConditionsList} */
+    this._options = this._populateOptions();
+    this._conditionsChanged();
+  }
+
+  /**
+   * @param {!MobileThrottling.Conditions} conditions
+   */
+  optionSelected(conditions) {
+    SDK.multitargetNetworkManager.setNetworkConditions(conditions.network);
+    MobileThrottling.throttlingManager().setCPUThrottlingRate(conditions.cpuThrottlingRate);
+  }
+
+  /**
+   * @return {!MobileThrottling.ConditionsList}
+   */
+  _populateOptions() {
+    const disabledGroup = {title: Common.UIString('Disabled'), items: [MobileThrottling.NoThrottlingConditions]};
+    const presetsGroup = {title: Common.UIString('Presets'), items: MobileThrottling.mobilePresets};
+    const advancedGroup = {title: Common.UIString('Advanced'), items: MobileThrottling.advancedMobilePresets};
+    return this._populateCallback([disabledGroup, presetsGroup, advancedGroup]);
+  }
+
+  _conditionsChanged() {
+    const networkConditions = SDK.multitargetNetworkManager.networkConditions();
+    const cpuThrottlingRate = MobileThrottling.throttlingManager().cpuThrottlingRate();
+    for (let index = 0; index < this._options.length; ++index) {
+      const option = this._options[index];
+      if (option && option.network === networkConditions && option.cpuThrottlingRate === cpuThrottlingRate) {
+        this._selectCallback(index);
+        return;
+      }
+    }
+    this._selectCallback(this._options.indexOf(MobileThrottling.CustomConditions));
+  }
+};
diff --git a/front_end/mobile_throttling/NetworkPanelIndicator.js b/front_end/mobile_throttling/NetworkPanelIndicator.js
new file mode 100644
index 0000000..a3de20b
--- /dev/null
+++ b/front_end/mobile_throttling/NetworkPanelIndicator.js
@@ -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.
+
+MobileThrottling.NetworkPanelIndicator = class {
+  constructor() {
+    // TODO: we should not access network from other modules.
+    if (!UI.inspectorView.hasPanel('network'))
+      return;
+    const manager = SDK.multitargetNetworkManager;
+    manager.addEventListener(SDK.MultitargetNetworkManager.Events.ConditionsChanged, updateVisibility);
+    manager.addEventListener(SDK.MultitargetNetworkManager.Events.BlockedPatternsChanged, updateVisibility);
+    manager.addEventListener(SDK.MultitargetNetworkManager.Events.InterceptorsChanged, updateVisibility);
+    updateVisibility();
+
+    function updateVisibility() {
+      let icon = null;
+      if (manager.isThrottling()) {
+        icon = UI.Icon.create('smallicon-warning');
+        icon.title = Common.UIString('Network throttling is enabled');
+      } else if (SDK.multitargetNetworkManager.isIntercepting()) {
+        icon = UI.Icon.create('smallicon-warning');
+        icon.title = Common.UIString('Requests may be rewritten');
+      } else if (manager.isBlocking()) {
+        icon = UI.Icon.create('smallicon-warning');
+        icon.title = Common.UIString('Requests may be blocked');
+      }
+      UI.inspectorView.setPanelIcon('network', icon);
+    }
+  }
+};
diff --git a/front_end/mobile_throttling/NetworkThrottlingSelector.js b/front_end/mobile_throttling/NetworkThrottlingSelector.js
new file mode 100644
index 0000000..cb1290f
--- /dev/null
+++ b/front_end/mobile_throttling/NetworkThrottlingSelector.js
@@ -0,0 +1,65 @@
+// 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.
+
+MobileThrottling.NetworkThrottlingSelector = class {
+  /**
+   * @param {function(!Array<!MobileThrottling.NetworkThrottlingConditionsGroup>):!Array<?SDK.NetworkManager.Conditions>} populateCallback
+   * @param {function(number)} selectCallback
+   * @param {!Common.Setting<!Array<!SDK.NetworkManager.Conditions>>} customNetworkConditionsSetting
+   */
+  constructor(populateCallback, selectCallback, customNetworkConditionsSetting) {
+    this._populateCallback = populateCallback;
+    this._selectCallback = selectCallback;
+    this._customNetworkConditionsSetting = customNetworkConditionsSetting;
+    this._customNetworkConditionsSetting.addChangeListener(this._populateOptions, this);
+    SDK.multitargetNetworkManager.addEventListener(
+        SDK.MultitargetNetworkManager.Events.ConditionsChanged, this._networkConditionsChanged, this);
+    /** @type {!Array<?SDK.NetworkManager.Conditions>} */
+    this._options;
+    this._populateOptions();
+  }
+
+  revealAndUpdate() {
+    Common.Revealer.reveal(this._customNetworkConditionsSetting);
+    this._networkConditionsChanged();
+  }
+
+  /**
+   * @param {!SDK.NetworkManager.Conditions} conditions
+   */
+  optionSelected(conditions) {
+    SDK.multitargetNetworkManager.setNetworkConditions(conditions);
+  }
+
+  _populateOptions() {
+    const disabledGroup = {title: Common.UIString('Disabled'), items: [SDK.NetworkManager.NoThrottlingConditions]};
+    const presetsGroup = {title: Common.UIString('Presets'), items: MobileThrottling.networkPresets};
+    const customGroup = {title: Common.UIString('Custom'), items: this._customNetworkConditionsSetting.get()};
+    this._options = this._populateCallback([disabledGroup, presetsGroup, customGroup]);
+    if (!this._networkConditionsChanged()) {
+      for (let i = this._options.length - 1; i >= 0; i--) {
+        if (this._options[i]) {
+          this.optionSelected(/** @type {!SDK.NetworkManager.Conditions} */ (this._options[i]));
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * @return {boolean} returns false if selected condition no longer exists
+   */
+  _networkConditionsChanged() {
+    const value = SDK.multitargetNetworkManager.networkConditions();
+    for (let index = 0; index < this._options.length; ++index) {
+      const option = this._options[index];
+      if (option && option.download === value.download && option.upload === value.upload &&
+          option.latency === value.latency && option.title === value.title) {
+        this._selectCallback(index);
+        return true;
+      }
+    }
+    return false;
+  }
+};
diff --git a/front_end/mobile_throttling/ThrottlingManager.js b/front_end/mobile_throttling/ThrottlingManager.js
new file mode 100644
index 0000000..ab8834b
--- /dev/null
+++ b/front_end/mobile_throttling/ThrottlingManager.js
@@ -0,0 +1,267 @@
+// 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.
+
+/**
+ * @implements {SDK.SDKModelObserver<!SDK.EmulationModel>}
+ */
+MobileThrottling.ThrottlingManager = class extends Common.Object {
+  constructor() {
+    super();
+    /** @type {!MobileThrottling.CPUThrottlingRates} */
+    this._cpuThrottlingRate = MobileThrottling.CPUThrottlingRates.NoThrottling;
+    /** @type {!Set<!UI.ToolbarComboBox>} */
+    this._cpuThrottlingControls = new Set();
+    this._cpuThrottlingRates = MobileThrottling.cpuThrottlingPresets;
+    /** @type {!Common.Setting<!Array<!SDK.NetworkManager.Conditions>>} */
+    this._customNetworkConditionsSetting = Common.moduleSetting('customNetworkConditions');
+    /** @type {!SDK.NetworkManager.Conditions} */
+    this._currentNetworkThrottlingConditions = SDK.NetworkManager.NoThrottlingConditions;
+    /** @type {!SDK.NetworkManager.Conditions} */
+    this._lastNetworkThrottlingConditions;
+
+    SDK.multitargetNetworkManager.addEventListener(SDK.MultitargetNetworkManager.Events.ConditionsChanged, () => {
+      this._lastNetworkThrottlingConditions = this._currentNetworkThrottlingConditions;
+      this._currentNetworkThrottlingConditions = SDK.multitargetNetworkManager.networkConditions();
+    });
+
+    SDK.targetManager.observeModels(SDK.EmulationModel, this);
+  }
+
+
+  /**
+   * @param {!HTMLSelectElement} selectElement
+   * @return {!MobileThrottling.NetworkThrottlingSelector}
+   */
+  decorateSelectWithNetworkThrottling(selectElement) {
+    let options = [];
+    const selector =
+        new MobileThrottling.NetworkThrottlingSelector(populate, select, this._customNetworkConditionsSetting);
+    selectElement.addEventListener('change', optionSelected, false);
+    return selector;
+
+    /**
+     * @param {!Array.<!MobileThrottling.NetworkThrottlingConditionsGroup>} groups
+     * @return {!Array<?SDK.NetworkManager.Conditions>}
+     */
+    function populate(groups) {
+      selectElement.removeChildren();
+      options = [];
+      for (let i = 0; i < groups.length; ++i) {
+        const group = groups[i];
+        const groupElement = selectElement.createChild('optgroup');
+        groupElement.label = group.title;
+        for (const conditions of group.items) {
+          const title = conditions.title;
+          const option = new Option(title, title);
+          groupElement.appendChild(option);
+          options.push(conditions);
+        }
+        if (i === groups.length - 1) {
+          groupElement.appendChild(new Option(Common.UIString('Add\u2026'), Common.UIString('Add\u2026')));
+          options.push(null);
+        }
+      }
+      return options;
+    }
+
+    function optionSelected() {
+      if (selectElement.selectedIndex === selectElement.options.length - 1)
+        selector.revealAndUpdate();
+      else
+        selector.optionSelected(options[selectElement.selectedIndex]);
+    }
+
+    /**
+     * @param {number} index
+     */
+    function select(index) {
+      if (selectElement.selectedIndex !== index)
+        selectElement.selectedIndex = index;
+    }
+  }
+
+  /**
+   * @return {!UI.ToolbarCheckbox}
+   */
+  createOfflineToolbarCheckbox() {
+    const checkbox = new UI.ToolbarCheckbox(
+        Common.UIString('Offline'), Common.UIString('Force disconnected from network'), forceOffline.bind(this));
+    SDK.multitargetNetworkManager.addEventListener(
+        SDK.MultitargetNetworkManager.Events.ConditionsChanged, networkConditionsChanged);
+    checkbox.setChecked(SDK.multitargetNetworkManager.networkConditions() === SDK.NetworkManager.OfflineConditions);
+
+    /**
+     * @this {!MobileThrottling.ThrottlingManager}
+     */
+    function forceOffline() {
+      if (checkbox.checked())
+        SDK.multitargetNetworkManager.setNetworkConditions(SDK.NetworkManager.OfflineConditions);
+      else
+        SDK.multitargetNetworkManager.setNetworkConditions(this._lastNetworkThrottlingConditions);
+    }
+
+    function networkConditionsChanged() {
+      checkbox.setChecked(SDK.multitargetNetworkManager.networkConditions() === SDK.NetworkManager.OfflineConditions);
+    }
+
+    return checkbox;
+  }
+
+
+  /**
+   * @return {!UI.ToolbarMenuButton}
+   */
+  createMobileThrottlingButton() {
+    const button = new UI.ToolbarMenuButton(appendItems);
+    button.setTitle(Common.UIString('Throttling'));
+    button.setGlyph('');
+    button.turnIntoSelect();
+    button.setDarkText();
+
+    /** @type {!MobileThrottling.ConditionsList} */
+    let options = [];
+    let selectedIndex = -1;
+    const selector = new MobileThrottling.MobileThrottlingSelector(populate, select);
+    return button;
+
+    /**
+     * @param {!UI.ContextMenu} contextMenu
+     */
+    function appendItems(contextMenu) {
+      for (let index = 0; index < options.length; ++index) {
+        const conditions = options[index];
+        if (!conditions)
+          continue;
+        if (conditions.title === MobileThrottling.CustomConditions.title &&
+            conditions.description === MobileThrottling.CustomConditions.description)
+          continue;
+        contextMenu.defaultSection().appendCheckboxItem(
+            Common.UIString(conditions.title),
+            selector.optionSelected.bind(selector, /** @type {!MobileThrottling.Conditions} */ (conditions)),
+            selectedIndex === index);
+      }
+    }
+
+    /**
+     * @param {!Array.<!MobileThrottling.MobileThrottlingConditionsGroup>} groups
+     * @return {!MobileThrottling.ConditionsList}
+     */
+    function populate(groups) {
+      options = [];
+      for (const group of groups) {
+        for (const conditions of group.items)
+          options.push(conditions);
+        options.push(null);
+      }
+      return options;
+    }
+
+    /**
+     * @param {number} index
+     */
+    function select(index) {
+      selectedIndex = index;
+      button.setText(options[index].title);
+      button.setTitle(options[index].description);
+    }
+  }
+
+  /**
+   * @return {number}
+   */
+  cpuThrottlingRate() {
+    return this._cpuThrottlingRate;
+  }
+
+  /**
+   * @param {!MobileThrottling.CPUThrottlingRates} rate
+   */
+  setCPUThrottlingRate(rate) {
+    this._cpuThrottlingRate = rate;
+    for (const emulationModel of SDK.targetManager.models(SDK.EmulationModel))
+      emulationModel.setCPUThrottlingRate(this._cpuThrottlingRate);
+    let icon = null;
+    if (this._cpuThrottlingRate !== MobileThrottling.CPUThrottlingRates.NoThrottling) {
+      Host.userMetrics.actionTaken(Host.UserMetrics.Action.CpuThrottlingEnabled);
+      icon = UI.Icon.create('smallicon-warning');
+      icon.title = Common.UIString('CPU throttling is enabled');
+    }
+    const index = this._cpuThrottlingRates.indexOf(this._cpuThrottlingRate);
+    for (const control of this._cpuThrottlingControls)
+      control.setSelectedIndex(index);
+    UI.inspectorView.setPanelIcon('timeline', icon);
+    this.dispatchEventToListeners(MobileThrottling.ThrottlingManager.Events.RateChanged, this._cpuThrottlingRate);
+  }
+
+  /**
+   * @override
+   * @param {!SDK.EmulationModel} emulationModel
+   */
+  modelAdded(emulationModel) {
+    if (this._cpuThrottlingRate !== MobileThrottling.CPUThrottlingRates.NoThrottling)
+      emulationModel.setCPUThrottlingRate(this._cpuThrottlingRate);
+  }
+
+  /**
+   * @override
+   * @param {!SDK.EmulationModel} emulationModel
+   */
+  modelRemoved(emulationModel) {
+  }
+
+  /**
+   * @return {!UI.ToolbarComboBox}
+   */
+  createCPUThrottlingSelector() {
+    const control = new UI.ToolbarComboBox(
+        event => this.setCPUThrottlingRate(this._cpuThrottlingRates[event.target.selectedIndex]));
+    this._cpuThrottlingControls.add(control);
+    const currentRate = this._cpuThrottlingRate;
+
+    for (let i = 0; i < this._cpuThrottlingRates.length; ++i) {
+      const rate = this._cpuThrottlingRates[i];
+      const title = rate === 1 ? Common.UIString('No throttling') : Common.UIString('%d\xD7 slowdown', rate);
+      const option = control.createOption(title);
+      control.addOption(option);
+      if (currentRate === rate)
+        control.setSelectedIndex(i);
+    }
+    return control;
+  }
+};
+
+/** @enum {symbol} */
+MobileThrottling.ThrottlingManager.Events = {
+  RateChanged: Symbol('RateChanged')
+};
+
+/**
+ * @implements {UI.ActionDelegate}
+ */
+MobileThrottling.ThrottlingManager.ActionDelegate = class {
+  /**
+   * @override
+   * @param {!UI.Context} context
+   * @param {string} actionId
+   * @return {boolean}
+   */
+  handleAction(context, actionId) {
+    if (actionId === 'network-conditions.network-online') {
+      SDK.multitargetNetworkManager.setNetworkConditions(SDK.NetworkManager.NoThrottlingConditions);
+      return true;
+    }
+    if (actionId === 'network-conditions.network-offline') {
+      SDK.multitargetNetworkManager.setNetworkConditions(SDK.NetworkManager.OfflineConditions);
+      return true;
+    }
+    return false;
+  }
+};
+
+/**
+ * @return {!MobileThrottling.ThrottlingManager}
+ */
+MobileThrottling.throttlingManager = function() {
+  return self.singleton(MobileThrottling.ThrottlingManager);
+};
diff --git a/front_end/mobile_throttling/ThrottlingPresets.js b/front_end/mobile_throttling/ThrottlingPresets.js
new file mode 100644
index 0000000..463ddb0
--- /dev/null
+++ b/front_end/mobile_throttling/ThrottlingPresets.js
@@ -0,0 +1,99 @@
+// 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.
+
+/** @enum {number} */
+MobileThrottling.CPUThrottlingRates = {
+  NoThrottling: 1,
+  MidTierMobile: 4,
+  LowEndMobile: 6,
+};
+
+/**
+ * @typedef {{
+ *   title: string,
+ *   description: string,
+ *   network: !SDK.NetworkManager.Conditions,
+ *   cpuThrottlingRate: !MobileThrottling.CPUThrottlingRates
+ * }}
+ **/
+MobileThrottling.Conditions;
+
+/** @type {!MobileThrottling.Conditions} */
+MobileThrottling.NoThrottlingConditions = {
+  title: SDK.NetworkManager.NoThrottlingConditions.title,
+  description: Common.UIString('No throttling'),
+  network: SDK.NetworkManager.NoThrottlingConditions,
+  cpuThrottlingRate: MobileThrottling.CPUThrottlingRates.NoThrottling,
+};
+
+/** @type {!MobileThrottling.Conditions} */
+MobileThrottling.OfflineConditions = {
+  title: SDK.NetworkManager.OfflineConditions.title,
+  description: Common.UIString('No internet connectivity'),
+  network: SDK.NetworkManager.OfflineConditions,
+  cpuThrottlingRate: MobileThrottling.CPUThrottlingRates.NoThrottling,
+};
+
+/** @type {!MobileThrottling.Conditions} */
+MobileThrottling.LowEndMobileConditions = {
+  title: Common.UIString('Low-end mobile'),
+  description: Common.UIString('Slow 3G & 6x CPU slowdown'),
+  network: SDK.NetworkManager.Slow3GConditions,
+  cpuThrottlingRate: MobileThrottling.CPUThrottlingRates.LowEndMobile,
+};
+
+/** @type {!MobileThrottling.Conditions} */
+MobileThrottling.MidTierMobileConditions = {
+  title: Common.UIString('Mid-tier mobile'),
+  description: Common.UIString('Fast 3G & 4x CPU slowdown'),
+  network: SDK.NetworkManager.Fast3GConditions,
+  cpuThrottlingRate: MobileThrottling.CPUThrottlingRates.MidTierMobile,
+};
+
+/**
+ * @typedef {{
+ *   title: string,
+ *   description: string
+ * }}
+ **/
+MobileThrottling.PlaceholderConditions;
+
+/** @type {!MobileThrottling.PlaceholderConditions} */
+MobileThrottling.CustomConditions = {
+  title: Common.UIString('Custom'),
+  description: Common.UIString('Check Network and Performance panels'),
+};
+
+/** @typedef {!{title: string, items: !Array<!SDK.NetworkManager.Conditions>}} */
+MobileThrottling.NetworkThrottlingConditionsGroup;
+
+/** @typedef {!{title: string, items: !Array<!MobileThrottling.Conditions|!MobileThrottling.PlaceholderConditions>}} */
+MobileThrottling.MobileThrottlingConditionsGroup;
+
+/** @typedef {!Array<?MobileThrottling.Conditions|!MobileThrottling.PlaceholderConditions>} */
+MobileThrottling.ConditionsList;
+
+/** @type {!Array.<!MobileThrottling.Conditions>} */
+MobileThrottling.mobilePresets = [
+  MobileThrottling.MidTierMobileConditions, MobileThrottling.LowEndMobileConditions, MobileThrottling.CustomConditions
+];
+
+/** @type {!Array.<!MobileThrottling.Conditions>} */
+MobileThrottling.advancedMobilePresets = [
+  MobileThrottling.OfflineConditions,
+];
+
+/** @type {!Array<!SDK.NetworkManager.Conditions>} */
+MobileThrottling.networkPresets = [
+  SDK.NetworkManager.Fast3GConditions,
+  SDK.NetworkManager.Slow3GConditions,
+  SDK.NetworkManager.OfflineConditions,
+];
+
+/** @type {!Array<!MobileThrottling.CPUThrottlingRates>} */
+MobileThrottling.cpuThrottlingPresets = [
+  MobileThrottling.CPUThrottlingRates.NoThrottling,
+  MobileThrottling.CPUThrottlingRates.MidTierMobile,
+  MobileThrottling.CPUThrottlingRates.LowEndMobile,
+];
diff --git a/front_end/mobile_throttling/ThrottlingSettingsTab.js b/front_end/mobile_throttling/ThrottlingSettingsTab.js
new file mode 100644
index 0000000..e303163
--- /dev/null
+++ b/front_end/mobile_throttling/ThrottlingSettingsTab.js
@@ -0,0 +1,217 @@
+// 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.
+
+/**
+ * @implements {UI.ListWidget.Delegate}
+ * @unrestricted
+ */
+MobileThrottling.ThrottlingSettingsTab = class extends UI.VBox {
+  constructor() {
+    super(true);
+    this.registerRequiredCSS('mobile_throttling/throttlingSettingsTab.css');
+
+    this.contentElement.createChild('div', 'header').textContent = Common.UIString('Network Throttling Profiles');
+
+    const addButton = UI.createTextButton(
+        Common.UIString('Add custom profile...'), this._addButtonClicked.bind(this), 'add-conditions-button');
+    this.contentElement.appendChild(addButton);
+
+    this._list = new UI.ListWidget(this);
+    this._list.element.classList.add('conditions-list');
+    this._list.registerRequiredCSS('mobile_throttling/throttlingSettingsTab.css');
+    this._list.show(this.contentElement);
+
+    this._customSetting = Common.moduleSetting('customNetworkConditions');
+    this._customSetting.addChangeListener(this._conditionsUpdated, this);
+
+    this.setDefaultFocusedElement(addButton);
+    this.contentElement.tabIndex = 0;
+  }
+
+  /**
+   * @override
+   */
+  wasShown() {
+    super.wasShown();
+    this._conditionsUpdated();
+  }
+
+  _conditionsUpdated() {
+    this._list.clear();
+
+    const conditions = this._customSetting.get();
+    for (let i = 0; i < conditions.length; ++i)
+      this._list.appendItem(conditions[i], true);
+
+    this._list.appendSeparator();
+  }
+
+  _addButtonClicked() {
+    this._list.addNewItem(this._customSetting.get().length, {title: '', download: -1, upload: -1, latency: 0});
+  }
+
+  /**
+   * @override
+   * @param {*} item
+   * @param {boolean} editable
+   * @return {!Element}
+   */
+  renderItem(item, editable) {
+    const conditions = /** @type {!SDK.NetworkManager.Conditions} */ (item);
+    const element = createElementWithClass('div', 'conditions-list-item');
+    const title = element.createChild('div', 'conditions-list-text conditions-list-title');
+    const titleText = title.createChild('div', 'conditions-list-title-text');
+    titleText.textContent = conditions.title;
+    titleText.title = conditions.title;
+    element.createChild('div', 'conditions-list-separator');
+    element.createChild('div', 'conditions-list-text').textContent =
+        MobileThrottling.throughputText(conditions.download);
+    element.createChild('div', 'conditions-list-separator');
+    element.createChild('div', 'conditions-list-text').textContent = MobileThrottling.throughputText(conditions.upload);
+    element.createChild('div', 'conditions-list-separator');
+    element.createChild('div', 'conditions-list-text').textContent = Common.UIString('%dms', conditions.latency);
+    return element;
+  }
+
+  /**
+   * @override
+   * @param {*} item
+   * @param {number} index
+   */
+  removeItemRequested(item, index) {
+    const list = this._customSetting.get();
+    list.splice(index, 1);
+    this._customSetting.set(list);
+  }
+
+  /**
+   * @override
+   * @param {*} item
+   * @param {!UI.ListWidget.Editor} editor
+   * @param {boolean} isNew
+   */
+  commitEdit(item, editor, isNew) {
+    const conditions = /** @type {?SDK.NetworkManager.Conditions} */ (item);
+    conditions.title = editor.control('title').value.trim();
+    const download = editor.control('download').value.trim();
+    conditions.download = download ? parseInt(download, 10) * (1024 / 8) : -1;
+    const upload = editor.control('upload').value.trim();
+    conditions.upload = upload ? parseInt(upload, 10) * (1024 / 8) : -1;
+    const latency = editor.control('latency').value.trim();
+    conditions.latency = latency ? parseInt(latency, 10) : 0;
+
+    const list = this._customSetting.get();
+    if (isNew)
+      list.push(conditions);
+    this._customSetting.set(list);
+  }
+
+  /**
+   * @override
+   * @param {*} item
+   * @return {!UI.ListWidget.Editor}
+   */
+  beginEdit(item) {
+    const conditions = /** @type {?SDK.NetworkManager.Conditions} */ (item);
+    const editor = this._createEditor();
+    editor.control('title').value = conditions.title;
+    editor.control('download').value = conditions.download <= 0 ? '' : String(conditions.download / (1024 / 8));
+    editor.control('upload').value = conditions.upload <= 0 ? '' : String(conditions.upload / (1024 / 8));
+    editor.control('latency').value = conditions.latency ? String(conditions.latency) : '';
+    return editor;
+  }
+
+  /**
+   * @return {!UI.ListWidget.Editor}
+   */
+  _createEditor() {
+    if (this._editor)
+      return this._editor;
+
+    const editor = new UI.ListWidget.Editor();
+    this._editor = editor;
+    const content = editor.contentElement();
+
+    const titles = content.createChild('div', 'conditions-edit-row');
+    titles.createChild('div', 'conditions-list-text conditions-list-title').textContent =
+        Common.UIString('Profile Name');
+    titles.createChild('div', 'conditions-list-separator conditions-list-separator-invisible');
+    titles.createChild('div', 'conditions-list-text').textContent = Common.UIString('Download');
+    titles.createChild('div', 'conditions-list-separator conditions-list-separator-invisible');
+    titles.createChild('div', 'conditions-list-text').textContent = Common.UIString('Upload');
+    titles.createChild('div', 'conditions-list-separator conditions-list-separator-invisible');
+    titles.createChild('div', 'conditions-list-text').textContent = Common.UIString('Latency');
+
+    const fields = content.createChild('div', 'conditions-edit-row');
+    fields.createChild('div', 'conditions-list-text conditions-list-title')
+        .appendChild(editor.createInput('title', 'text', '', titleValidator));
+    fields.createChild('div', 'conditions-list-separator conditions-list-separator-invisible');
+
+    let cell = fields.createChild('div', 'conditions-list-text');
+    cell.appendChild(editor.createInput('download', 'text', Common.UIString('kb/s'), throughputValidator));
+    cell.createChild('div', 'conditions-edit-optional').textContent = Common.UIString('optional');
+    fields.createChild('div', 'conditions-list-separator conditions-list-separator-invisible');
+
+    cell = fields.createChild('div', 'conditions-list-text');
+    cell.appendChild(editor.createInput('upload', 'text', Common.UIString('kb/s'), throughputValidator));
+    cell.createChild('div', 'conditions-edit-optional').textContent = Common.UIString('optional');
+    fields.createChild('div', 'conditions-list-separator conditions-list-separator-invisible');
+
+    cell = fields.createChild('div', 'conditions-list-text');
+    cell.appendChild(editor.createInput('latency', 'text', Common.UIString('ms'), latencyValidator));
+    cell.createChild('div', 'conditions-edit-optional').textContent = Common.UIString('optional');
+
+    return editor;
+
+    /**
+     * @param {*} item
+     * @param {number} index
+     * @param {!HTMLInputElement|!HTMLSelectElement} input
+     * @return {boolean}
+     */
+    function titleValidator(item, index, input) {
+      const value = input.value.trim();
+      return value.length > 0 && value.length < 50;
+    }
+
+    /**
+     * @param {*} item
+     * @param {number} index
+     * @param {!HTMLInputElement|!HTMLSelectElement} input
+     * @return {boolean}
+     */
+    function throughputValidator(item, index, input) {
+      const value = input.value.trim();
+      return !value || (/^[\d]+(\.\d+)?|\.\d+$/.test(value) && value >= 0 && value <= 10000000);
+    }
+
+    /**
+     * @param {*} item
+     * @param {number} index
+     * @param {!HTMLInputElement|!HTMLSelectElement} input
+     * @return {boolean}
+     */
+    function latencyValidator(item, index, input) {
+      const value = input.value.trim();
+      return !value || (/^[\d]+$/.test(value) && value >= 0 && value <= 1000000);
+    }
+  }
+};
+
+/**
+ * @param {number} throughput
+ * @param {boolean=} plainText
+ * @return {string}
+ */
+MobileThrottling.throughputText = function(throughput, plainText) {
+  if (throughput < 0)
+    return '';
+  const throughputInKbps = throughput / (1024 / 8);
+  const delimiter = plainText ? '' : ' ';
+  if (throughputInKbps < 1024)
+    return Common.UIString('%d%skb/s', throughputInKbps, delimiter);
+  if (throughputInKbps < 1024 * 10)
+    return Common.UIString('%.1f%sMb/s', throughputInKbps / 1024, delimiter);
+  return Common.UIString('%d%sMb/s', (throughputInKbps / 1024) | 0, delimiter);
+};
diff --git a/front_end/mobile_throttling/module.json b/front_end/mobile_throttling/module.json
new file mode 100644
index 0000000..217bc85
--- /dev/null
+++ b/front_end/mobile_throttling/module.json
@@ -0,0 +1,54 @@
+{
+    "extensions": [
+        {
+            "type": "setting",
+            "settingName": "customNetworkConditions",
+            "settingType": "array",
+            "defaultValue": []
+        },
+        {
+            "type": "action",
+            "actionId": "network-conditions.network-offline",
+            "category": "Network",
+            "title": "Go offline",
+            "className": "MobileThrottling.ThrottlingManager.ActionDelegate",
+            "tags": "device"
+        },
+        {
+            "type": "action",
+            "actionId": "network-conditions.network-online",
+            "category": "Network",
+            "title": "Go online",
+            "className": "MobileThrottling.ThrottlingManager.ActionDelegate",
+            "tags": "device"
+        },
+        {
+            "type": "view",
+            "location": "settings-view",
+            "id": "throttling-conditions",
+            "title": "Throttling",
+            "order": 35,
+            "className": "MobileThrottling.ThrottlingSettingsTab",
+            "settings": [
+                "customNetworkConditions"
+            ]
+        }
+    ],
+    "dependencies": [
+        "common",
+        "sdk",
+        "ui",
+        "protocol"
+    ],
+    "scripts": [
+        "ThrottlingPresets.js",
+        "MobileThrottlingSelector.js",
+        "NetworkPanelIndicator.js",
+        "NetworkThrottlingSelector.js",
+        "ThrottlingSettingsTab.js",
+        "ThrottlingManager.js"
+    ],
+    "resources": [
+        "throttlingSettingsTab.css"
+    ]
+}
diff --git a/front_end/mobile_throttling/throttlingSettingsTab.css b/front_end/mobile_throttling/throttlingSettingsTab.css
new file mode 100644
index 0000000..d85c7bd
--- /dev/null
+++ b/front_end/mobile_throttling/throttlingSettingsTab.css
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015 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.
+ */
+
+:host {
+    overflow:hidden;
+}
+
+.header {
+    padding: 0 0 6px;
+    border-bottom: 1px solid #EEEEEE;
+    font-size: 18px;
+    font-weight: normal;
+    flex: none;
+}
+
+.add-conditions-button {
+    flex: none;
+    margin: 10px 2px;
+    min-width: 140px;
+    align-self: flex-start;
+}
+
+.conditions-list {
+    max-width: 500px;
+    min-width: 340px;
+    flex: auto;
+}
+
+.conditions-list-item {
+    padding: 3px 5px 3px 5px;
+    height: 30px;
+    display: flex;
+    align-items: center;
+    position: relative;
+    flex: auto 1 1;
+}
+
+.conditions-list-text {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    flex: 0 0 70px;
+    -webkit-user-select: none;
+    color: #222;
+    text-align: end;
+    position: relative;
+}
+
+.conditions-list-title {
+    text-align: start;
+    flex: auto;
+    display: flex;
+    align-items: flex-start;
+}
+
+.conditions-list-title-text {
+    overflow: hidden;
+    flex: auto;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+.conditions-list-separator {
+    flex: 0 0 1px;
+    background-color: rgb(231, 231, 231);
+    height: 30px;
+    margin: 0 4px;
+}
+
+.conditions-list-separator-invisible {
+    visibility: hidden;
+    height: 100% !important;
+}
+
+.conditions-edit-row {
+    flex: none;
+    display: flex;
+    flex-direction: row;
+    margin: 6px 5px;
+}
+
+.conditions-edit-row input {
+    width: 100%;
+    text-align: inherit;
+}
+
+.conditions-edit-optional {
+    position: absolute;
+    bottom: -20px;
+    right: 0;
+    color: rgb(128, 128, 128);
+}