2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
33 * @extends {WebInspector.Object}
35 WebInspector.OverridesSupport = function()
37 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._onMainFrameNavigated.bind(this), this);
38 this._deviceMetricsOverrideEnabled = false;
39 this._emulateViewportEnabled = false;
41 this.maybeHasActiveOverridesChanged();
43 WebInspector.settings.overrideUserAgent.addChangeListener(this._userAgentChanged, this);
44 WebInspector.settings.userAgent.addChangeListener(this._userAgentChanged, this);
46 WebInspector.settings.overrideDeviceMetrics.addChangeListener(this._deviceMetricsChanged, this);
47 WebInspector.settings.deviceMetrics.addChangeListener(this._deviceMetricsChanged, this);
48 WebInspector.settings.emulateViewport.addChangeListener(this._deviceMetricsChanged, this);
49 WebInspector.settings.deviceFitWindow.addChangeListener(this._deviceMetricsChanged, this);
51 WebInspector.settings.overrideGeolocation.addChangeListener(this._geolocationPositionChanged, this);
52 WebInspector.settings.geolocationOverride.addChangeListener(this._geolocationPositionChanged, this);
54 WebInspector.settings.overrideDeviceOrientation.addChangeListener(this._deviceOrientationChanged, this);
55 WebInspector.settings.deviceOrientationOverride.addChangeListener(this._deviceOrientationChanged, this);
57 WebInspector.settings.emulateTouchEvents.addChangeListener(this._emulateTouchEventsChanged, this);
59 WebInspector.settings.overrideCSSMedia.addChangeListener(this._cssMediaChanged, this);
60 WebInspector.settings.emulatedCSSMedia.addChangeListener(this._cssMediaChanged, this);
63 WebInspector.OverridesSupport.Events = {
64 OverridesWarningUpdated: "OverridesWarningUpdated",
65 HasActiveOverridesChanged: "HasActiveOverridesChanged",
70 * @param {number} width
71 * @param {number} height
72 * @param {number} deviceScaleFactor
73 * @param {boolean} textAutosizing
75 WebInspector.OverridesSupport.DeviceMetrics = function(width, height, deviceScaleFactor, textAutosizing)
79 this.deviceScaleFactor = deviceScaleFactor;
80 this.textAutosizing = textAutosizing;
84 * @return {!WebInspector.OverridesSupport.DeviceMetrics}
86 WebInspector.OverridesSupport.DeviceMetrics.parseSetting = function(value)
90 var deviceScaleFactor = 1;
91 var textAutosizing = true;
93 var splitMetrics = value.split("x");
94 if (splitMetrics.length >= 3) {
95 width = parseInt(splitMetrics[0], 10);
96 height = parseInt(splitMetrics[1], 10);
97 deviceScaleFactor = parseFloat(splitMetrics[2]);
98 if (splitMetrics.length == 4)
99 textAutosizing = splitMetrics[3] == 1;
102 return new WebInspector.OverridesSupport.DeviceMetrics(width, height, deviceScaleFactor, textAutosizing);
106 * @return {?WebInspector.OverridesSupport.DeviceMetrics}
108 WebInspector.OverridesSupport.DeviceMetrics.parseUserInput = function(widthString, heightString, deviceScaleFactorString, textAutosizing)
110 function isUserInputValid(value, isInteger)
114 return isInteger ? /^[0]*[1-9][\d]*$/.test(value) : /^[0]*([1-9][\d]*(\.\d+)?|\.\d+)$/.test(value);
117 if (!widthString ^ !heightString)
120 var isWidthValid = isUserInputValid(widthString, true);
121 var isHeightValid = isUserInputValid(heightString, true);
122 var isDeviceScaleFactorValid = isUserInputValid(deviceScaleFactorString, false);
124 if (!isWidthValid && !isHeightValid && !isDeviceScaleFactorValid)
127 var width = isWidthValid ? parseInt(widthString || "0", 10) : -1;
128 var height = isHeightValid ? parseInt(heightString || "0", 10) : -1;
129 var deviceScaleFactor = isDeviceScaleFactorValid ? parseFloat(deviceScaleFactorString) : -1;
131 return new WebInspector.OverridesSupport.DeviceMetrics(width, height, deviceScaleFactor, textAutosizing);
134 WebInspector.OverridesSupport.DeviceMetrics.prototype = {
140 return this.isWidthValid() && this.isHeightValid() && this.isDeviceScaleFactorValid();
146 isWidthValid: function()
148 return this.width >= 0;
154 isHeightValid: function()
156 return this.height >= 0;
162 isDeviceScaleFactorValid: function()
164 return this.deviceScaleFactor > 0;
170 toSetting: function()
175 return this.width && this.height ? this.width + "x" + this.height + "x" + this.deviceScaleFactor + "x" + (this.textAutosizing ? "1" : "0") : "";
181 widthToInput: function()
183 return this.isWidthValid() && this.width ? String(this.width) : "";
189 heightToInput: function()
191 return this.isHeightValid() && this.height ? String(this.height) : "";
197 deviceScaleFactorToInput: function()
199 return this.isDeviceScaleFactorValid() && this.deviceScaleFactor ? String(this.deviceScaleFactor) : "";
203 * Compute the font scale factor.
205 * Chromium on Android uses a device scale adjustment for fonts used in text autosizing for
206 * improved legibility. This function computes this adjusted value for text autosizing.
208 * For a description of the Android device scale adjustment algorithm, see:
209 * chrome/browser/chrome_content_browser_client.cc, GetFontScaleMultiplier(...)
211 * @return {number} font scale factor.
213 fontScaleFactor: function()
215 if (this.isValid()) {
216 var minWidth = Math.min(this.width, this.height) / this.deviceScaleFactor;
219 var kWidthForMinFSM = 320;
221 var kWidthForMaxFSM = 800;
223 if (minWidth <= kWidthForMinFSM)
225 if (minWidth >= kWidthForMaxFSM)
228 // The font scale multiplier varies linearly between kMinFSM and kMaxFSM.
229 var ratio = (minWidth - kWidthForMinFSM) / (kWidthForMaxFSM - kWidthForMinFSM);
231 return ratio * (kMaxFSM - kMinFSM) + kMinFSM;
240 * @param {number} latitude
241 * @param {number} longitude
243 WebInspector.OverridesSupport.GeolocationPosition = function(latitude, longitude, error)
245 this.latitude = latitude;
246 this.longitude = longitude;
250 WebInspector.OverridesSupport.GeolocationPosition.prototype = {
254 toSetting: function()
256 return (typeof this.latitude === "number" && typeof this.longitude === "number" && typeof this.error === "string") ? this.latitude + "@" + this.longitude + ":" + this.error : "";
261 * @return {!WebInspector.OverridesSupport.GeolocationPosition}
263 WebInspector.OverridesSupport.GeolocationPosition.parseSetting = function(value)
266 var splitError = value.split(":");
267 if (splitError.length === 2) {
268 var splitPosition = splitError[0].split("@")
269 if (splitPosition.length === 2)
270 return new WebInspector.OverridesSupport.GeolocationPosition(parseFloat(splitPosition[0]), parseFloat(splitPosition[1]), splitError[1]);
273 return new WebInspector.OverridesSupport.GeolocationPosition(0, 0, "");
277 * @return {?WebInspector.OverridesSupport.GeolocationPosition}
279 WebInspector.OverridesSupport.GeolocationPosition.parseUserInput = function(latitudeString, longitudeString, errorStatus)
281 function isUserInputValid(value)
285 return /^[-]?[0-9]*[.]?[0-9]*$/.test(value);
288 if (!latitudeString ^ !latitudeString)
291 var isLatitudeValid = isUserInputValid(latitudeString);
292 var isLongitudeValid = isUserInputValid(longitudeString);
294 if (!isLatitudeValid && !isLongitudeValid)
297 var latitude = isLatitudeValid ? parseFloat(latitudeString) : -1;
298 var longitude = isLongitudeValid ? parseFloat(longitudeString) : -1;
300 return new WebInspector.OverridesSupport.GeolocationPosition(latitude, longitude, errorStatus ? "PositionUnavailable" : "");
303 WebInspector.OverridesSupport.GeolocationPosition.clearGeolocationOverride = function()
305 GeolocationAgent.clearGeolocationOverride();
310 * @param {number} alpha
311 * @param {number} beta
312 * @param {number} gamma
314 WebInspector.OverridesSupport.DeviceOrientation = function(alpha, beta, gamma)
321 WebInspector.OverridesSupport.DeviceOrientation.prototype = {
325 toSetting: function()
327 return JSON.stringify(this);
332 * @return {!WebInspector.OverridesSupport.DeviceOrientation}
334 WebInspector.OverridesSupport.DeviceOrientation.parseSetting = function(value)
337 var jsonObject = JSON.parse(value);
338 return new WebInspector.OverridesSupport.DeviceOrientation(jsonObject.alpha, jsonObject.beta, jsonObject.gamma);
340 return new WebInspector.OverridesSupport.DeviceOrientation(0, 0, 0);
344 * @return {?WebInspector.OverridesSupport.DeviceOrientation}
346 WebInspector.OverridesSupport.DeviceOrientation.parseUserInput = function(alphaString, betaString, gammaString)
348 function isUserInputValid(value)
352 return /^[-]?[0-9]*[.]?[0-9]*$/.test(value);
355 if (!alphaString ^ !betaString ^ !gammaString)
358 var isAlphaValid = isUserInputValid(alphaString);
359 var isBetaValid = isUserInputValid(betaString);
360 var isGammaValid = isUserInputValid(gammaString);
362 if (!isAlphaValid && !isBetaValid && !isGammaValid)
365 var alpha = isAlphaValid ? parseFloat(alphaString) : -1;
366 var beta = isBetaValid ? parseFloat(betaString) : -1;
367 var gamma = isGammaValid ? parseFloat(gammaString) : -1;
369 return new WebInspector.OverridesSupport.DeviceOrientation(alpha, beta, gamma);
372 WebInspector.OverridesSupport.DeviceOrientation.clearDeviceOrientationOverride = function()
374 PageAgent.clearDeviceOrientationOverride();
377 WebInspector.OverridesSupport.prototype = {
379 * @param {string} deviceMetrics
380 * @param {string} userAgent
382 emulateDevice: function(deviceMetrics, userAgent)
384 this._deviceMetricsChangedListenerMuted = true;
385 this._userAgentChangedListenerMuted = true;
386 WebInspector.settings.deviceMetrics.set(deviceMetrics);
387 WebInspector.settings.userAgent.set(userAgent);
388 WebInspector.settings.overrideDeviceMetrics.set(true);
389 WebInspector.settings.overrideUserAgent.set(true);
390 WebInspector.settings.emulateTouchEvents.set(true);
391 WebInspector.settings.emulateViewport.set(true);
392 delete this._deviceMetricsChangedListenerMuted;
393 delete this._userAgentChangedListenerMuted;
394 this._deviceMetricsChanged();
395 this._userAgentChanged();
400 this._deviceMetricsChangedListenerMuted = true;
401 this._userAgentChangedListenerMuted = true;
402 WebInspector.settings.overrideDeviceMetrics.set(false);
403 WebInspector.settings.overrideUserAgent.set(false);
404 WebInspector.settings.emulateTouchEvents.set(false);
405 WebInspector.settings.overrideDeviceOrientation.set(false);
406 WebInspector.settings.overrideGeolocation.set(false);
407 WebInspector.settings.overrideCSSMedia.set(false);
408 WebInspector.settings.emulateViewport.set(false);
409 WebInspector.settings.deviceMetrics.set("");
410 delete this._deviceMetricsChangedListenerMuted;
411 delete this._userAgentChangedListenerMuted;
412 this._deviceMetricsChanged();
413 this._userAgentChanged();
416 applyInitialOverrides: function()
418 this._deviceMetricsChangedListenerMuted = true;
419 this._userAgentChangedListenerMuted = true;
420 this._userAgentChanged();
421 this._deviceMetricsChanged();
422 this._deviceOrientationChanged();
423 this._geolocationPositionChanged();
424 this._emulateTouchEventsChanged();
425 this._cssMediaChanged();
426 delete this._deviceMetricsChangedListenerMuted;
427 delete this._userAgentChangedListenerMuted;
428 this._deviceMetricsChanged();
429 this._userAgentChanged();
432 _userAgentChanged: function()
434 if (WebInspector.isInspectingDevice() || this._userAgentChangedListenerMuted)
436 var userAgent = WebInspector.settings.overrideUserAgent.get() ? WebInspector.settings.userAgent.get() : "";
437 NetworkAgent.setUserAgentOverride(userAgent);
438 this._updateUserAgentWarningMessage(this._userAgent !== userAgent ? WebInspector.UIString("You might need to reload the page for proper user agent spoofing and viewport rendering.") : "");
439 this._userAgent = userAgent;
440 this.maybeHasActiveOverridesChanged();
443 _deviceMetricsChanged: function()
445 if (this._deviceMetricsChangedListenerMuted)
447 var metrics = WebInspector.OverridesSupport.DeviceMetrics.parseSetting(WebInspector.settings.overrideDeviceMetrics.get() ? WebInspector.settings.deviceMetrics.get() : "");
448 if (!metrics.isValid())
451 var dipWidth = Math.round(metrics.width / metrics.deviceScaleFactor);
452 var dipHeight = Math.round(metrics.height / metrics.deviceScaleFactor);
454 // Disable override without checks.
455 if (dipWidth && dipHeight && WebInspector.isInspectingDevice()) {
456 this._updateDeviceMetricsWarningMessage(WebInspector.UIString("Screen emulation on the device is not available."));
460 PageAgent.setDeviceMetricsOverride(dipWidth, dipHeight, metrics.deviceScaleFactor, WebInspector.settings.emulateViewport.get(), WebInspector.settings.deviceFitWindow.get(), metrics.textAutosizing, metrics.fontScaleFactor(), apiCallback.bind(this));
461 this.maybeHasActiveOverridesChanged();
464 * @param {?Protocol.Error} error
465 * @this {WebInspector.OverridesSupport}
467 function apiCallback(error)
470 this._updateDeviceMetricsWarningMessage(WebInspector.UIString("Screen emulation is not available on this page."));
474 var metricsOverrideEnabled = !!(dipWidth && dipHeight);
475 var viewportEnabled = WebInspector.settings.emulateViewport.get();
476 this._updateDeviceMetricsWarningMessage(this._deviceMetricsOverrideEnabled !== metricsOverrideEnabled || (metricsOverrideEnabled && this._emulateViewportEnabled != viewportEnabled) ?
477 WebInspector.UIString("You might need to reload the page for proper user agent spoofing and viewport rendering.") : "");
478 this._deviceMetricsOverrideEnabled = metricsOverrideEnabled;
479 this._emulateViewportEnabled = viewportEnabled;
480 this._deviceMetricsOverrideAppliedForTest();
484 _deviceMetricsOverrideAppliedForTest: function()
486 // Used for sniffing in tests.
489 _geolocationPositionChanged: function()
491 if (!WebInspector.settings.overrideGeolocation.get()) {
492 GeolocationAgent.clearGeolocationOverride();
495 var geolocation = WebInspector.OverridesSupport.GeolocationPosition.parseSetting(WebInspector.settings.geolocationOverride.get());
496 if (geolocation.error)
497 GeolocationAgent.setGeolocationOverride();
499 GeolocationAgent.setGeolocationOverride(geolocation.latitude, geolocation.longitude, 150);
500 this.maybeHasActiveOverridesChanged();
503 _deviceOrientationChanged: function()
505 if (!WebInspector.settings.overrideDeviceOrientation.get()) {
506 PageAgent.clearDeviceOrientationOverride();
509 if (WebInspector.isInspectingDevice())
512 var deviceOrientation = WebInspector.OverridesSupport.DeviceOrientation.parseSetting(WebInspector.settings.deviceOrientationOverride.get());
513 PageAgent.setDeviceOrientationOverride(deviceOrientation.alpha, deviceOrientation.beta, deviceOrientation.gamma);
514 this.maybeHasActiveOverridesChanged();
517 _emulateTouchEventsChanged: function()
519 if (WebInspector.isInspectingDevice() && WebInspector.settings.emulateTouchEvents.get())
522 WebInspector.domAgent.emulateTouchEventObjects(WebInspector.settings.emulateTouchEvents.get());
523 this.maybeHasActiveOverridesChanged();
526 _cssMediaChanged: function()
528 PageAgent.setEmulatedMedia(WebInspector.settings.overrideCSSMedia.get() ? WebInspector.settings.emulatedCSSMedia.get() : "");
529 WebInspector.cssModel.mediaQueryResultChanged();
530 this.maybeHasActiveOverridesChanged();
536 hasActiveOverrides: function()
538 return this._hasActiveOverrides;
541 maybeHasActiveOverridesChanged: function()
543 var hasActiveOverrides = WebInspector.settings.overrideUserAgent.get() || WebInspector.settings.overrideDeviceMetrics.get() ||
544 WebInspector.settings.overrideGeolocation.get() || WebInspector.settings.overrideDeviceOrientation.get() ||
545 WebInspector.settings.emulateTouchEvents.get() || WebInspector.settings.overrideCSSMedia.get();
546 if (this._hasActiveOverrides !== hasActiveOverrides) {
547 this._hasActiveOverrides = hasActiveOverrides;
548 this.dispatchEventToListeners(WebInspector.OverridesSupport.Events.HasActiveOverridesChanged);
552 _onMainFrameNavigated: function()
554 this._deviceMetricsChanged();
555 this._updateUserAgentWarningMessage("");
559 * @param {string} warningMessage
561 _updateDeviceMetricsWarningMessage: function(warningMessage)
563 this._deviceMetricsWarningMessage = warningMessage;
564 this.dispatchEventToListeners(WebInspector.OverridesSupport.Events.OverridesWarningUpdated);
568 * @param {string} warningMessage
570 _updateUserAgentWarningMessage: function(warningMessage)
572 this._userAgentWarningMessage = warningMessage;
573 this.dispatchEventToListeners(WebInspector.OverridesSupport.Events.OverridesWarningUpdated);
579 warningMessage: function()
581 return this._deviceMetricsWarningMessage || this._userAgentWarningMessage || "";
584 __proto__: WebInspector.Object.prototype
589 * @type {!WebInspector.OverridesSupport}
591 WebInspector.overridesSupport;