1/*
2 * Copyright (C) 2014 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31/**
32 * @constructor
33 * @extends {WebInspector.VBox}
34 */
35WebInspector.OverridesView = function()
36{
37    WebInspector.VBox.call(this);
38    this.registerRequiredCSS("overrides.css");
39    this.element.classList.add("overrides-view");
40
41    this._tabbedPane = new WebInspector.TabbedPane();
42    this._tabbedPane.shrinkableTabs = false;
43    this._tabbedPane.verticalTabLayout = true;
44
45    new WebInspector.OverridesView.DeviceTab().appendAsTab(this._tabbedPane);
46    new WebInspector.OverridesView.MediaTab().appendAsTab(this._tabbedPane);
47    new WebInspector.OverridesView.NetworkTab().appendAsTab(this._tabbedPane);
48    new WebInspector.OverridesView.SensorsTab().appendAsTab(this._tabbedPane);
49
50    this._lastSelectedTabSetting = WebInspector.settings.createSetting("lastSelectedEmulateTab", "device");
51    this._tabbedPane.selectTab(this._lastSelectedTabSetting.get());
52    this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
53    this._tabbedPane.show(this.element);
54
55    var resetButtonElement = this._tabbedPane.headerElement().createChild("button", "text-button");
56    resetButtonElement.id = "overrides-reset-button";
57    resetButtonElement.textContent = WebInspector.UIString("Reset");
58    resetButtonElement.addEventListener("click", WebInspector.overridesSupport.reset.bind(WebInspector.overridesSupport), false);
59
60    if (!WebInspector.overridesSupport.responsiveDesignAvailable()) {
61        var disableButtonElement = this._tabbedPane.headerElement().createChild("button", "text-button overrides-disable-button");
62        disableButtonElement.id = "overrides-disable-button";
63        disableButtonElement.textContent = WebInspector.UIString("Disable");
64        disableButtonElement.addEventListener("click", this._toggleEmulationEnabled.bind(this), false);
65    }
66
67    this._splashScreenElement = this.element.createChild("div", "overrides-splash-screen");
68    this._unavailableSplashScreenElement = this.element.createChild("div", "overrides-splash-screen");
69    this._unavailableSplashScreenElement.createTextChild(WebInspector.UIString("Emulation is not available."));
70
71    if (WebInspector.overridesSupport.responsiveDesignAvailable()) {
72        this._splashScreenElement.createTextChild(WebInspector.UIString("Emulation is currently disabled. Toggle "));
73        var toggleEmulationButton = new WebInspector.StatusBarButton("", "emulation-status-bar-item");
74        toggleEmulationButton.addEventListener("click", this._toggleEmulationEnabled, this);
75        this._splashScreenElement.appendChild(toggleEmulationButton.element);
76        this._splashScreenElement.createTextChild(WebInspector.UIString("in the main toolbar to enable it."));
77    } else {
78        var toggleEmulationButton = this._splashScreenElement.createChild("button", "text-button overrides-enable-button");
79        toggleEmulationButton.textContent = WebInspector.UIString("Enable emulation");
80        toggleEmulationButton.addEventListener("click", this._toggleEmulationEnabled.bind(this), false);
81    }
82
83    this._warningFooter = this.element.createChild("div", "overrides-footer");
84    this._overridesWarningUpdated();
85
86    WebInspector.overridesSupport.addEventListener(WebInspector.OverridesSupport.Events.OverridesWarningUpdated, this._overridesWarningUpdated, this);
87    WebInspector.overridesSupport.addEventListener(WebInspector.OverridesSupport.Events.EmulationStateChanged, this._emulationStateChanged, this);
88    this._emulationStateChanged();
89}
90
91WebInspector.OverridesView.prototype = {
92    /**
93     * @param {!WebInspector.Event} event
94     */
95    _tabSelected: function(event)
96    {
97        this._lastSelectedTabSetting.set(this._tabbedPane.selectedTabId);
98    },
99
100    _overridesWarningUpdated: function()
101    {
102        var message = WebInspector.overridesSupport.warningMessage();
103        this._warningFooter.classList.toggle("hidden", !message);
104        this._warningFooter.textContent = message;
105    },
106
107    _toggleEmulationEnabled: function()
108    {
109        WebInspector.overridesSupport.setEmulationEnabled(!WebInspector.overridesSupport.emulationEnabled());
110    },
111
112    _emulationStateChanged: function()
113    {
114        this._unavailableSplashScreenElement.classList.toggle("hidden", WebInspector.overridesSupport.canEmulate());
115        this._tabbedPane.element.classList.toggle("hidden", !WebInspector.overridesSupport.emulationEnabled());
116        this._splashScreenElement.classList.toggle("hidden", WebInspector.overridesSupport.emulationEnabled() || !WebInspector.overridesSupport.canEmulate());
117    },
118
119    __proto__: WebInspector.VBox.prototype
120}
121
122/**
123 * @constructor
124 * @extends {WebInspector.VBox}
125 * @param {string} id
126 * @param {string} name
127 * @param {!Array.<!WebInspector.Setting>} settings
128 * @param {!Array.<function():boolean>=} predicates
129 */
130WebInspector.OverridesView.Tab = function(id, name, settings, predicates)
131{
132    WebInspector.VBox.call(this);
133    this._id = id;
134    this._name = name;
135    this._settings = settings;
136    this._predicates = predicates || [];
137    for (var i = 0; i < settings.length; ++i)
138        settings[i].addChangeListener(this.updateActiveState, this);
139}
140
141WebInspector.OverridesView.Tab.prototype = {
142    /**
143     * @param {!WebInspector.TabbedPane} tabbedPane
144     */
145    appendAsTab: function(tabbedPane)
146    {
147        this._tabbedPane = tabbedPane;
148        tabbedPane.appendTab(this._id, this._name, this);
149        this.updateActiveState();
150    },
151
152    updateActiveState: function()
153    {
154        if (!this._tabbedPane)
155            return;
156        var active = false;
157        for (var i = 0; !active && i < this._settings.length; ++i)
158            active = this._settings[i].get();
159        for (var i = 0; !active && i < this._predicates.length; ++i)
160            active = this._predicates[i]();
161        this._tabbedPane.toggleTabClass(this._id, "overrides-activate", active);
162    },
163
164    /**
165     * @param {string} name
166     * @param {!WebInspector.Setting} setting
167     * @param {function(boolean)=} callback
168     */
169    _createSettingCheckbox: function(name, setting, callback)
170    {
171        var checkbox = WebInspector.SettingsUI.createSettingCheckbox(name, setting, true);
172
173        function changeListener(value)
174        {
175            callback(setting.get());
176        }
177
178        if (callback)
179            setting.addChangeListener(changeListener);
180
181        return checkbox;
182    },
183
184    __proto__: WebInspector.VBox.prototype
185}
186
187/**
188 * @constructor
189 * @extends {WebInspector.OverridesView.Tab}
190 */
191WebInspector.OverridesView.DeviceTab = function()
192{
193    WebInspector.OverridesView.Tab.call(this, "device", WebInspector.UIString("Device"),  [
194        WebInspector.overridesSupport.settings.emulateResolution,
195        WebInspector.overridesSupport.settings.deviceScaleFactor,
196        WebInspector.overridesSupport.settings.emulateMobile
197    ]);
198    this.element.classList.add("overrides-device");
199
200    this.element.appendChild(this._createDeviceElement());
201
202    var footnote = this.element.createChild("p", "help-footnote");
203    var footnoteLink = footnote.createChild("a");
204    footnoteLink.href = "https://developers.google.com/chrome-developer-tools/docs/mobile-emulation";
205    footnoteLink.target = "_blank";
206    footnoteLink.createTextChild(WebInspector.UIString("More information about screen emulation"));
207}
208
209WebInspector.OverridesView.DeviceTab.prototype = {
210    _createDeviceElement: function()
211    {
212        var fieldsetElement = document.createElement("fieldset");
213        fieldsetElement.id = "metrics-override-section";
214
215        var deviceModelElement = fieldsetElement.createChild("p", "overrides-device-model-section");
216        deviceModelElement.createChild("span").textContent = WebInspector.UIString("Model:");
217
218        var deviceSelectElement = WebInspector.OverridesUI.createDeviceSelect(document, this._showTitleDialog.bind(this));
219        var buttons = deviceSelectElement.querySelectorAll("button");
220        for (var i = 0; i < buttons.length; ++i)
221            buttons[i].classList.add("text-button");
222        deviceModelElement.appendChild(deviceSelectElement);
223
224        var emulateResolutionCheckbox = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("Emulate screen resolution"), WebInspector.overridesSupport.settings.emulateResolution, true);
225        fieldsetElement.appendChild(emulateResolutionCheckbox);
226        var resolutionFieldset = WebInspector.SettingsUI.createSettingFieldset(WebInspector.overridesSupport.settings.emulateResolution);
227        fieldsetElement.appendChild(resolutionFieldset);
228
229        var tableElement = resolutionFieldset.createChild("table", "nowrap");
230        var rowElement = tableElement.createChild("tr");
231        var cellElement = rowElement.createChild("td");
232        cellElement.createTextChild(WebInspector.UIString("Resolution:"));
233        cellElement = rowElement.createChild("td");
234
235        var widthOverrideInput = WebInspector.SettingsUI.createSettingInputField("", WebInspector.overridesSupport.settings.deviceWidth, true, 4, "80px", WebInspector.OverridesSupport.deviceSizeValidator, true, true, WebInspector.UIString("\u2013"));
236        cellElement.appendChild(widthOverrideInput);
237        this._swapDimensionsElement = cellElement.createChild("button", "overrides-swap");
238        this._swapDimensionsElement.createTextChild(" \u21C4 "); // RIGHTWARDS ARROW OVER LEFTWARDS ARROW.
239        this._swapDimensionsElement.title = WebInspector.UIString("Swap dimensions");
240        this._swapDimensionsElement.addEventListener("click", WebInspector.overridesSupport.swapDimensions.bind(WebInspector.overridesSupport), false);
241        this._swapDimensionsElement.tabIndex = -1;
242        var heightOverrideInput = WebInspector.SettingsUI.createSettingInputField("", WebInspector.overridesSupport.settings.deviceHeight, true, 4, "80px", WebInspector.OverridesSupport.deviceSizeValidator, true, true, WebInspector.UIString("\u2013"));
243        cellElement.appendChild(heightOverrideInput);
244
245        rowElement = tableElement.createChild("tr");
246        cellElement = rowElement.createChild("td");
247        cellElement.colSpan = 4;
248
249        rowElement = tableElement.createChild("tr");
250        rowElement.title = WebInspector.UIString("Ratio between a device's physical pixels and device-independent pixels.");
251        rowElement.createChild("td").createTextChild(WebInspector.UIString("Device pixel ratio:"));
252        rowElement.createChild("td").appendChild(WebInspector.SettingsUI.createSettingInputField("", WebInspector.overridesSupport.settings.deviceScaleFactor, true, 4, "80px", WebInspector.OverridesSupport.deviceScaleFactorValidator, true, true, WebInspector.UIString("\u2013")));
253
254        var mobileCheckbox = this._createSettingCheckbox(WebInspector.UIString("Emulate mobile"), WebInspector.overridesSupport.settings.emulateMobile);
255        mobileCheckbox.title = WebInspector.UIString("Enable meta viewport, overlay scrollbars, text autosizing and default 980px body width");
256        fieldsetElement.appendChild(mobileCheckbox);
257
258        fieldsetElement.appendChild(this._createSettingCheckbox(WebInspector.UIString("Shrink to fit"), WebInspector.overridesSupport.settings.deviceFitWindow));
259
260        return fieldsetElement;
261    },
262
263    /**
264     * @param {!function(string)} callback
265     */
266    _showTitleDialog: function(callback)
267    {
268        WebInspector.Dialog.show(this.element, new WebInspector.OverridesView.DeviceTab.CustomDeviceTitleDialog(callback));
269    },
270
271    __proto__: WebInspector.OverridesView.Tab.prototype
272}
273
274/**
275 * @constructor
276 * @extends {WebInspector.DialogDelegate}
277 * @param {!function(string)} callback
278 */
279WebInspector.OverridesView.DeviceTab.CustomDeviceTitleDialog = function(callback)
280{
281    WebInspector.DialogDelegate.call(this);
282
283    this.element = document.createElementWithClass("div", "custom-device-title-dialog");
284    this.element.createChild("label").textContent = WebInspector.UIString("Save as: ");
285
286    this._input = this.element.createChild("input");
287    this._input.setAttribute("type", "text");
288    this._input.placeholder = WebInspector.UIString("device model name");
289    this._input.addEventListener("input", this._onInput.bind(this), false);
290
291    this._saveButton = this.element.createChild("button");
292    this._saveButton.textContent = WebInspector.UIString("Save");
293    this._saveButton.addEventListener("click", this._onSaveClick.bind(this), false);
294
295    this._callback = callback;
296    this._result = "";
297    this._onInput();
298}
299
300WebInspector.OverridesView.DeviceTab.CustomDeviceTitleDialog.prototype = {
301    focus: function()
302    {
303        WebInspector.setCurrentFocusElement(this._input);
304        this._input.select();
305    },
306
307    _onSaveClick: function()
308    {
309        this._result = this._input.value.trim();
310        WebInspector.Dialog.hide();
311    },
312
313    _onInput: function()
314    {
315        this._saveButton.disabled = !this._input.value.trim();
316    },
317
318    /**
319     * @param {!Event} event
320     */
321    onEnter: function(event)
322    {
323        if (this._input.value.trim()) {
324            this._result = this._input.value.trim();
325        } else {
326            event.consume();
327        }
328    },
329
330    willHide: function()
331    {
332        this._callback(this._result);
333    },
334
335    __proto__: WebInspector.DialogDelegate.prototype
336}
337
338/**
339 * @constructor
340 * @extends {WebInspector.OverridesView.Tab}
341 */
342WebInspector.OverridesView.MediaTab = function()
343{
344    var settings = [WebInspector.overridesSupport.settings.overrideCSSMedia];
345    WebInspector.OverridesView.Tab.call(this, "media", WebInspector.UIString("Media"), settings);
346    this.element.classList.add("overrides-media");
347
348    this._createMediaEmulationFragment();
349}
350
351WebInspector.OverridesView.MediaTab.prototype = {
352    _createMediaEmulationFragment: function()
353    {
354        var checkbox = WebInspector.SettingsUI.createSettingCheckbox(WebInspector.UIString("CSS media"), WebInspector.overridesSupport.settings.overrideCSSMedia, true);
355        var fieldsetElement = WebInspector.SettingsUI.createSettingFieldset(WebInspector.overridesSupport.settings.overrideCSSMedia);
356        var mediaSelectElement = fieldsetElement.createChild("select");
357        var mediaTypes = WebInspector.CSSStyleModel.MediaTypes;
358        var defaultMedia = WebInspector.overridesSupport.settings.emulatedCSSMedia.get();
359        for (var i = 0; i < mediaTypes.length; ++i) {
360            var mediaType = mediaTypes[i];
361            if (mediaType === "all") {
362                // "all" is not a device-specific media type.
363                continue;
364            }
365            var option = document.createElement("option");
366            option.text = mediaType;
367            option.value = mediaType;
368            mediaSelectElement.add(option);
369            if (mediaType === defaultMedia)
370                mediaSelectElement.selectedIndex = mediaSelectElement.options.length - 1;
371        }
372
373        mediaSelectElement.addEventListener("change", this._emulateMediaChanged.bind(this, mediaSelectElement), false);
374        var fragment = document.createDocumentFragment();
375        fragment.appendChild(checkbox);
376        fragment.appendChild(fieldsetElement);
377        this.element.appendChild(fragment);
378    },
379
380    _emulateMediaChanged: function(select)
381    {
382        var media = select.options[select.selectedIndex].value;
383        WebInspector.overridesSupport.settings.emulatedCSSMedia.set(media);
384    },
385
386    __proto__: WebInspector.OverridesView.Tab.prototype
387}
388
389
390/**
391 * @constructor
392 * @extends {WebInspector.OverridesView.Tab}
393 */
394WebInspector.OverridesView.NetworkTab = function()
395{
396    WebInspector.OverridesView.Tab.call(this, "network", WebInspector.UIString("Network"), [], [this._userAgentOverrideEnabled.bind(this), this._networkThroughputIsLimited.bind(this)]);
397    this.element.classList.add("overrides-network");
398    this._createNetworkConditionsElement();
399    this._createUserAgentSection();
400}
401
402WebInspector.OverridesView.NetworkTab.prototype = {
403    /**
404     * @return {boolean}
405     */
406    _networkThroughputIsLimited: function()
407    {
408        return WebInspector.overridesSupport.networkThroughputIsLimited();
409    },
410
411    _createNetworkConditionsElement: function()
412    {
413        var fieldsetElement = this.element.createChild("fieldset");
414        fieldsetElement.createChild("span").textContent = WebInspector.UIString("Limit network throughput:");
415        fieldsetElement.createChild("br");
416        fieldsetElement.appendChild(WebInspector.OverridesUI.createNetworkConditionsSelect(document));
417
418        WebInspector.overridesSupport.settings.networkConditions.addChangeListener(this.updateActiveState, this);
419    },
420
421    /**
422     * @return {boolean}
423     */
424    _userAgentOverrideEnabled: function()
425    {
426        return !!WebInspector.overridesSupport.settings.userAgent.get();
427    },
428
429    _createUserAgentSection: function()
430    {
431        var fieldsetElement = this.element.createChild("fieldset");
432        fieldsetElement.createChild("label").textContent = WebInspector.UIString("Spoof user agent:");
433        var selectAndInput = WebInspector.OverridesUI.createUserAgentSelectAndInput(document);
434        fieldsetElement.appendChild(selectAndInput.select);
435        fieldsetElement.appendChild(selectAndInput.input);
436
437        WebInspector.overridesSupport.settings.userAgent.addChangeListener(this.updateActiveState, this);
438    },
439
440    __proto__: WebInspector.OverridesView.Tab.prototype
441}
442
443
444/**
445 * @constructor
446 * @extends {WebInspector.OverridesView.Tab}
447 */
448WebInspector.OverridesView.SensorsTab = function()
449{
450    WebInspector.OverridesView.Tab.call(this, "sensors", WebInspector.UIString("Sensors"), [
451        WebInspector.overridesSupport.settings.overrideGeolocation,
452        WebInspector.overridesSupport.settings.overrideDeviceOrientation,
453        WebInspector.overridesSupport.settings.emulateTouch
454    ]);
455
456    this.element.classList.add("overrides-sensors");
457    this.registerRequiredCSS("accelerometer.css");
458    this.element.appendChild(this._createSettingCheckbox(WebInspector.UIString("Emulate touch screen"), WebInspector.overridesSupport.settings.emulateTouch, undefined));
459    this._appendGeolocationOverrideControl();
460    this._apendDeviceOrientationOverrideControl();
461}
462
463WebInspector.OverridesView.SensorsTab.prototype = {
464    _appendGeolocationOverrideControl: function()
465    {
466        const geolocationSetting = WebInspector.overridesSupport.settings.geolocationOverride.get();
467        var geolocation = WebInspector.OverridesSupport.GeolocationPosition.parseSetting(geolocationSetting);
468        this.element.appendChild(this._createSettingCheckbox(WebInspector.UIString("Emulate geolocation coordinates"), WebInspector.overridesSupport.settings.overrideGeolocation, this._geolocationOverrideCheckboxClicked.bind(this)));
469        this.element.appendChild(this._createGeolocationOverrideElement(geolocation));
470        this._geolocationOverrideCheckboxClicked(WebInspector.overridesSupport.settings.overrideGeolocation.get());
471    },
472
473    /**
474     * @param {boolean} enabled
475     */
476    _geolocationOverrideCheckboxClicked: function(enabled)
477    {
478        if (enabled && !this._latitudeElement.value)
479            this._latitudeElement.focus();
480    },
481
482    _applyGeolocationUserInput: function()
483    {
484        this._setGeolocationPosition(WebInspector.OverridesSupport.GeolocationPosition.parseUserInput(this._latitudeElement.value.trim(), this._longitudeElement.value.trim(), this._geolocationErrorElement.checked), true);
485    },
486
487    /**
488     * @param {?WebInspector.OverridesSupport.GeolocationPosition} geolocation
489     * @param {boolean} userInputModified
490     */
491    _setGeolocationPosition: function(geolocation, userInputModified)
492    {
493        if (!geolocation)
494            return;
495
496        if (!userInputModified) {
497            this._latitudeElement.value = geolocation.latitude;
498            this._longitudeElement.value = geolocation.longitude;
499        }
500
501        var value = geolocation.toSetting();
502        WebInspector.overridesSupport.settings.geolocationOverride.set(value);
503    },
504
505    /**
506     * @param {!WebInspector.OverridesSupport.GeolocationPosition} geolocation
507     * @return {!Element}
508     */
509    _createGeolocationOverrideElement: function(geolocation)
510    {
511        var fieldsetElement = WebInspector.SettingsUI.createSettingFieldset(WebInspector.overridesSupport.settings.overrideGeolocation);
512        fieldsetElement.id = "geolocation-override-section";
513
514        var tableElement = fieldsetElement.createChild("table");
515        var rowElement = tableElement.createChild("tr");
516        var cellElement = rowElement.createChild("td");
517        cellElement = rowElement.createChild("td");
518        cellElement.createTextChild(WebInspector.UIString("Lat = "));
519        this._latitudeElement = WebInspector.SettingsUI.createInput(cellElement, "geolocation-override-latitude", String(geolocation.latitude), this._applyGeolocationUserInput.bind(this), true);
520        cellElement.createTextChild(" , ");
521        cellElement.createTextChild(WebInspector.UIString("Lon = "));
522        this._longitudeElement = WebInspector.SettingsUI.createInput(cellElement, "geolocation-override-longitude", String(geolocation.longitude), this._applyGeolocationUserInput.bind(this), true);
523        rowElement = tableElement.createChild("tr");
524        cellElement = rowElement.createChild("td");
525        cellElement.colSpan = 2;
526        var geolocationErrorLabelElement = document.createElement("label");
527        var geolocationErrorCheckboxElement = geolocationErrorLabelElement.createChild("input");
528        geolocationErrorCheckboxElement.id = "geolocation-error";
529        geolocationErrorCheckboxElement.type = "checkbox";
530        geolocationErrorCheckboxElement.checked = !geolocation || geolocation.error;
531        geolocationErrorCheckboxElement.addEventListener("click", this._applyGeolocationUserInput.bind(this), false);
532        geolocationErrorLabelElement.createTextChild(WebInspector.UIString("Emulate position unavailable"));
533        this._geolocationErrorElement = geolocationErrorCheckboxElement;
534        cellElement.appendChild(geolocationErrorLabelElement);
535
536        return fieldsetElement;
537    },
538
539    _apendDeviceOrientationOverrideControl: function()
540    {
541        const deviceOrientationSetting = WebInspector.overridesSupport.settings.deviceOrientationOverride.get();
542        var deviceOrientation = WebInspector.OverridesSupport.DeviceOrientation.parseSetting(deviceOrientationSetting);
543        this.element.appendChild(this._createSettingCheckbox(WebInspector.UIString("Accelerometer"), WebInspector.overridesSupport.settings.overrideDeviceOrientation, this._deviceOrientationOverrideCheckboxClicked.bind(this)));
544        this.element.appendChild(this._createDeviceOrientationOverrideElement(deviceOrientation));
545        this._deviceOrientationOverrideCheckboxClicked(WebInspector.overridesSupport.settings.overrideDeviceOrientation.get());
546    },
547
548    /**
549     * @param {boolean} enabled
550     */
551    _deviceOrientationOverrideCheckboxClicked: function(enabled)
552    {
553        if (enabled && !this._alphaElement.value)
554            this._alphaElement.focus();
555    },
556
557    _applyDeviceOrientationUserInput: function()
558    {
559        this._setDeviceOrientation(WebInspector.OverridesSupport.DeviceOrientation.parseUserInput(this._alphaElement.value.trim(), this._betaElement.value.trim(), this._gammaElement.value.trim()), WebInspector.OverridesView.SensorsTab.DeviceOrientationModificationSource.UserInput);
560    },
561
562    _resetDeviceOrientation: function()
563    {
564        this._setDeviceOrientation(new WebInspector.OverridesSupport.DeviceOrientation(0, 0, 0), WebInspector.OverridesView.SensorsTab.DeviceOrientationModificationSource.ResetButton);
565    },
566
567    /**
568     * @param {?WebInspector.OverridesSupport.DeviceOrientation} deviceOrientation
569     * @param {!WebInspector.OverridesView.SensorsTab.DeviceOrientationModificationSource} modificationSource
570     */
571    _setDeviceOrientation: function(deviceOrientation, modificationSource)
572    {
573        if (!deviceOrientation)
574            return;
575
576        if (modificationSource != WebInspector.OverridesView.SensorsTab.DeviceOrientationModificationSource.UserInput) {
577            this._alphaElement.value = deviceOrientation.alpha;
578            this._betaElement.value = deviceOrientation.beta;
579            this._gammaElement.value = deviceOrientation.gamma;
580        }
581
582        if (modificationSource != WebInspector.OverridesView.SensorsTab.DeviceOrientationModificationSource.UserDrag)
583            this._setBoxOrientation(deviceOrientation);
584
585        var value = deviceOrientation.toSetting();
586        WebInspector.overridesSupport.settings.deviceOrientationOverride.set(value);
587    },
588
589    /**
590     * @param {!Element} parentElement
591     * @param {string} id
592     * @param {string} label
593     * @param {string} defaultText
594     * @return {!Element}
595     */
596    _createAxisInput: function(parentElement, id, label, defaultText)
597    {
598        var div = parentElement.createChild("div", "accelerometer-axis-input-container");
599        div.createTextChild(label);
600        return WebInspector.SettingsUI.createInput(div, id, defaultText, this._applyDeviceOrientationUserInput.bind(this), true);
601    },
602
603    /**
604     * @param {!WebInspector.OverridesSupport.DeviceOrientation} deviceOrientation
605     */
606    _createDeviceOrientationOverrideElement: function(deviceOrientation)
607    {
608        var fieldsetElement = WebInspector.SettingsUI.createSettingFieldset(WebInspector.overridesSupport.settings.overrideDeviceOrientation);
609        fieldsetElement.id = "device-orientation-override-section";
610        var tableElement = fieldsetElement.createChild("table");
611        var rowElement = tableElement.createChild("tr");
612        var cellElement = rowElement.createChild("td", "accelerometer-inputs-cell");
613
614        this._alphaElement = this._createAxisInput(cellElement, "device-orientation-override-alpha", "\u03B1: ", String(deviceOrientation.alpha));
615        this._betaElement = this._createAxisInput(cellElement, "device-orientation-override-beta", "\u03B2: ", String(deviceOrientation.beta));
616        this._gammaElement = this._createAxisInput(cellElement, "device-orientation-override-gamma", "\u03B3: ", String(deviceOrientation.gamma));
617
618        var resetButton = cellElement.createChild("button", "text-button accelerometer-reset-button");
619        resetButton.textContent = WebInspector.UIString("Reset");
620        resetButton.addEventListener("click", this._resetDeviceOrientation.bind(this), false);
621
622        this._stageElement = rowElement.createChild("td","accelerometer-stage");
623        this._boxElement = this._stageElement.createChild("section", "accelerometer-box");
624
625        this._boxElement.createChild("section", "front");
626        this._boxElement.createChild("section", "top");
627        this._boxElement.createChild("section", "back");
628        this._boxElement.createChild("section", "left");
629        this._boxElement.createChild("section", "right");
630        this._boxElement.createChild("section", "bottom");
631
632        WebInspector.installDragHandle(this._stageElement, this._onBoxDragStart.bind(this), this._onBoxDrag.bind(this), this._onBoxDragEnd.bind(this), "move");
633        this._setBoxOrientation(deviceOrientation);
634        return fieldsetElement;
635    },
636
637    /**
638     * @param {!WebInspector.OverridesSupport.DeviceOrientation} deviceOrientation
639     */
640    _setBoxOrientation: function(deviceOrientation)
641    {
642        var matrix = new WebKitCSSMatrix();
643        this._boxMatrix = matrix.rotate(-deviceOrientation.beta, deviceOrientation.gamma, -deviceOrientation.alpha);
644        this._boxElement.style.webkitTransform = this._boxMatrix.toString();
645    },
646
647    /**
648     * @param {!MouseEvent} event
649     * @return {boolean}
650     */
651    _onBoxDrag: function(event)
652    {
653        var mouseMoveVector = this._calculateRadiusVector(event.x, event.y);
654        if (!mouseMoveVector)
655            return true;
656
657        event.consume(true);
658        var axis = WebInspector.Geometry.crossProduct(this._mouseDownVector, mouseMoveVector);
659        axis.normalize();
660        var angle = WebInspector.Geometry.calculateAngle(this._mouseDownVector, mouseMoveVector);
661        var matrix = new WebKitCSSMatrix();
662        var rotationMatrix = matrix.rotateAxisAngle(axis.x, axis.y, axis.z, angle);
663        this._currentMatrix = rotationMatrix.multiply(this._boxMatrix)
664        this._boxElement.style.webkitTransform = this._currentMatrix;
665        var eulerAngles = WebInspector.Geometry.EulerAngles.fromRotationMatrix(this._currentMatrix);
666        var newOrientation = new WebInspector.OverridesSupport.DeviceOrientation(-eulerAngles.alpha, -eulerAngles.beta, eulerAngles.gamma);
667        this._setDeviceOrientation(newOrientation, WebInspector.OverridesView.SensorsTab.DeviceOrientationModificationSource.UserDrag);
668        return false;
669    },
670
671    /**
672     * @param {!MouseEvent} event
673     * @return {boolean}
674     */
675    _onBoxDragStart: function(event)
676    {
677        if (!WebInspector.overridesSupport.settings.overrideDeviceOrientation.get())
678            return false;
679
680        this._mouseDownVector = this._calculateRadiusVector(event.x, event.y);
681
682        if (!this._mouseDownVector)
683            return false;
684
685        event.consume(true);
686        return true;
687    },
688
689    _onBoxDragEnd: function()
690    {
691        this._boxMatrix = this._currentMatrix;
692    },
693
694    /**
695     * @param {number} x
696     * @param {number} y
697     * @return {?WebInspector.Geometry.Vector}
698     */
699    _calculateRadiusVector: function(x, y)
700    {
701        var rect = this._stageElement.getBoundingClientRect();
702        var radius = Math.max(rect.width, rect.height) / 2;
703        var sphereX = (x - rect.left - rect.width / 2) / radius;
704        var sphereY = (y - rect.top - rect.height / 2) / radius;
705        var sqrSum = sphereX * sphereX + sphereY * sphereY;
706        if (sqrSum > 0.5)
707            return new WebInspector.Geometry.Vector(sphereX, sphereY, 0.5 / Math.sqrt(sqrSum));
708
709        return new WebInspector.Geometry.Vector(sphereX, sphereY, Math.sqrt(1 - sqrSum));
710    },
711
712    __proto__ : WebInspector.OverridesView.Tab.prototype
713}
714
715/** @enum {string} */
716WebInspector.OverridesView.SensorsTab.DeviceOrientationModificationSource = {
717    UserInput: "userInput",
718    UserDrag: "userDrag",
719    ResetButton: "resetButton"
720}
721
722/**
723 * @constructor
724 * @implements {WebInspector.Revealer}
725 */
726WebInspector.OverridesView.Revealer = function()
727{
728}
729
730WebInspector.OverridesView.Revealer.prototype = {
731    /**
732     * @param {!Object} overridesSupport
733     */
734    reveal: function(overridesSupport)
735    {
736        WebInspector.inspectorView.showViewInDrawer("emulation");
737    }
738}
739