• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2012 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.Object}
34 */
35WebInspector.OverridesSupport = function()
36{
37    WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._deviceMetricsChanged.bind(this), this);
38    this._deviceMetricsOverrideEnabled = false;
39    this._emulateViewportEnabled = false;
40
41    WebInspector.settings.overrideUserAgent.addChangeListener(this._userAgentChanged, this);
42    WebInspector.settings.userAgent.addChangeListener(this._userAgentChanged, this);
43
44    WebInspector.settings.overrideDeviceMetrics.addChangeListener(this._deviceMetricsChanged, this);
45    WebInspector.settings.deviceMetrics.addChangeListener(this._deviceMetricsChanged, this);
46    WebInspector.settings.emulateViewport.addChangeListener(this._deviceMetricsChanged, this);
47    WebInspector.settings.deviceFitWindow.addChangeListener(this._deviceMetricsChanged, this);
48
49    WebInspector.settings.overrideGeolocation.addChangeListener(this._geolocationPositionChanged, this);
50    WebInspector.settings.geolocationOverride.addChangeListener(this._geolocationPositionChanged, this);
51
52    WebInspector.settings.overrideDeviceOrientation.addChangeListener(this._deviceOrientationChanged, this);
53    WebInspector.settings.deviceOrientationOverride.addChangeListener(this._deviceOrientationChanged, this);
54
55    WebInspector.settings.emulateTouchEvents.addChangeListener(this._emulateTouchEventsChanged, this);
56
57    WebInspector.settings.overrideCSSMedia.addChangeListener(this._cssMediaChanged, this);
58    WebInspector.settings.emulatedCSSMedia.addChangeListener(this._cssMediaChanged, this);
59}
60
61WebInspector.OverridesSupport.Events = {
62    OverridesWarningUpdated: "OverridesWarningUpdated",
63}
64
65/**
66 * @constructor
67 * @param {number} width
68 * @param {number} height
69 * @param {number} deviceScaleFactor
70 * @param {boolean} textAutosizing
71 */
72WebInspector.OverridesSupport.DeviceMetrics = function(width, height, deviceScaleFactor, textAutosizing)
73{
74    this.width = width;
75    this.height = height;
76    this.deviceScaleFactor = deviceScaleFactor;
77    this.textAutosizing = textAutosizing;
78}
79
80/**
81 * @return {!WebInspector.OverridesSupport.DeviceMetrics}
82 */
83WebInspector.OverridesSupport.DeviceMetrics.parseSetting = function(value)
84{
85    var width = 0;
86    var height = 0;
87    var deviceScaleFactor = 1;
88    var textAutosizing = true;
89    if (value) {
90        var splitMetrics = value.split("x");
91        if (splitMetrics.length >= 3) {
92            width = parseInt(splitMetrics[0], 10);
93            height = parseInt(splitMetrics[1], 10);
94            deviceScaleFactor = parseFloat(splitMetrics[2]);
95            if (splitMetrics.length == 4)
96                textAutosizing = splitMetrics[3] == 1;
97        }
98    }
99    return new WebInspector.OverridesSupport.DeviceMetrics(width, height, deviceScaleFactor, textAutosizing);
100}
101
102/**
103 * @return {?WebInspector.OverridesSupport.DeviceMetrics}
104 */
105WebInspector.OverridesSupport.DeviceMetrics.parseUserInput = function(widthString, heightString, deviceScaleFactorString, textAutosizing)
106{
107    function isUserInputValid(value, isInteger)
108    {
109        if (!value)
110            return true;
111        return isInteger ? /^[0]*[1-9][\d]*$/.test(value) : /^[0]*([1-9][\d]*(\.\d+)?|\.\d+)$/.test(value);
112    }
113
114    if (!widthString ^ !heightString)
115        return null;
116
117    var isWidthValid = isUserInputValid(widthString, true);
118    var isHeightValid = isUserInputValid(heightString, true);
119    var isDeviceScaleFactorValid = isUserInputValid(deviceScaleFactorString, false);
120
121    if (!isWidthValid && !isHeightValid && !isDeviceScaleFactorValid)
122        return null;
123
124    var width = isWidthValid ? parseInt(widthString || "0", 10) : -1;
125    var height = isHeightValid ? parseInt(heightString || "0", 10) : -1;
126    var deviceScaleFactor = isDeviceScaleFactorValid ? parseFloat(deviceScaleFactorString) : -1;
127
128    return new WebInspector.OverridesSupport.DeviceMetrics(width, height, deviceScaleFactor, textAutosizing);
129}
130
131WebInspector.OverridesSupport.DeviceMetrics.prototype = {
132    /**
133     * @return {boolean}
134     */
135    isValid: function()
136    {
137        return this.isWidthValid() && this.isHeightValid() && this.isDeviceScaleFactorValid();
138    },
139
140    /**
141     * @return {boolean}
142     */
143    isWidthValid: function()
144    {
145        return this.width >= 0;
146    },
147
148    /**
149     * @return {boolean}
150     */
151    isHeightValid: function()
152    {
153        return this.height >= 0;
154    },
155
156    /**
157     * @return {boolean}
158     */
159    isDeviceScaleFactorValid: function()
160    {
161        return this.deviceScaleFactor > 0;
162    },
163
164    /**
165     * @return {string}
166     */
167    toSetting: function()
168    {
169        if (!this.isValid())
170            return "";
171
172        return this.width && this.height ? this.width + "x" + this.height + "x" + this.deviceScaleFactor + "x" + (this.textAutosizing ? "1" : "0") : "";
173    },
174
175    /**
176     * @return {string}
177     */
178    widthToInput: function()
179    {
180        return this.isWidthValid() && this.width ? String(this.width) : "";
181    },
182
183    /**
184     * @return {string}
185     */
186    heightToInput: function()
187    {
188        return this.isHeightValid() && this.height ? String(this.height) : "";
189    },
190
191    /**
192     * @return {string}
193     */
194    deviceScaleFactorToInput: function()
195    {
196        return this.isDeviceScaleFactorValid() && this.deviceScaleFactor ? String(this.deviceScaleFactor) : "";
197    },
198
199    /**
200     * Compute the font scale factor.
201     *
202     * Chromium on Android uses a device scale adjustment for fonts used in text autosizing for
203     * improved legibility. This function computes this adjusted value for text autosizing.
204     *
205     * For a description of the Android device scale adjustment algorithm, see:
206     *     chrome/browser/chrome_content_browser_client.cc, GetFontScaleMultiplier(...)
207     *
208     * @return {number} font scale factor.
209     */
210    fontScaleFactor: function()
211    {
212        if (this.isValid()) {
213            var minWidth = Math.min(this.width, this.height) / this.deviceScaleFactor;
214
215            var kMinFSM = 1.05;
216            var kWidthForMinFSM = 320;
217            var kMaxFSM = 1.3;
218            var kWidthForMaxFSM = 800;
219
220            if (minWidth <= kWidthForMinFSM)
221                return kMinFSM;
222            if (minWidth >= kWidthForMaxFSM)
223                return kMaxFSM;
224
225            // The font scale multiplier varies linearly between kMinFSM and kMaxFSM.
226            var ratio = (minWidth - kWidthForMinFSM) / (kWidthForMaxFSM - kWidthForMinFSM);
227
228            return ratio * (kMaxFSM - kMinFSM) + kMinFSM;
229        }
230
231        return 1;
232    }
233}
234
235/**
236 * @constructor
237 * @param {number} latitude
238 * @param {number} longitude
239 */
240WebInspector.OverridesSupport.GeolocationPosition = function(latitude, longitude, error)
241{
242    this.latitude = latitude;
243    this.longitude = longitude;
244    this.error = error;
245}
246
247WebInspector.OverridesSupport.GeolocationPosition.prototype = {
248    /**
249     * @return {string}
250     */
251    toSetting: function()
252    {
253        return (typeof this.latitude === "number" && typeof this.longitude === "number" && typeof this.error === "string") ? this.latitude + "@" + this.longitude + ":" + this.error : "";
254    }
255}
256
257/**
258 * @return {!WebInspector.OverridesSupport.GeolocationPosition}
259 */
260WebInspector.OverridesSupport.GeolocationPosition.parseSetting = function(value)
261{
262    if (value) {
263        var splitError = value.split(":");
264        if (splitError.length === 2) {
265            var splitPosition = splitError[0].split("@")
266            if (splitPosition.length === 2)
267                return new WebInspector.OverridesSupport.GeolocationPosition(parseFloat(splitPosition[0]), parseFloat(splitPosition[1]), splitError[1]);
268        }
269    }
270    return new WebInspector.OverridesSupport.GeolocationPosition(0, 0, "");
271}
272
273/**
274 * @return {?WebInspector.OverridesSupport.GeolocationPosition}
275 */
276WebInspector.OverridesSupport.GeolocationPosition.parseUserInput = function(latitudeString, longitudeString, errorStatus)
277{
278    function isUserInputValid(value)
279    {
280        if (!value)
281            return true;
282        return /^[-]?[0-9]*[.]?[0-9]*$/.test(value);
283    }
284
285    if (!latitudeString ^ !latitudeString)
286        return null;
287
288    var isLatitudeValid = isUserInputValid(latitudeString);
289    var isLongitudeValid = isUserInputValid(longitudeString);
290
291    if (!isLatitudeValid && !isLongitudeValid)
292        return null;
293
294    var latitude = isLatitudeValid ? parseFloat(latitudeString) : -1;
295    var longitude = isLongitudeValid ? parseFloat(longitudeString) : -1;
296
297    return new WebInspector.OverridesSupport.GeolocationPosition(latitude, longitude, errorStatus ? "PositionUnavailable" : "");
298}
299
300WebInspector.OverridesSupport.GeolocationPosition.clearGeolocationOverride = function()
301{
302    PageAgent.clearGeolocationOverride();
303}
304
305/**
306 * @constructor
307 * @param {number} alpha
308 * @param {number} beta
309 * @param {number} gamma
310 */
311WebInspector.OverridesSupport.DeviceOrientation = function(alpha, beta, gamma)
312{
313    this.alpha = alpha;
314    this.beta = beta;
315    this.gamma = gamma;
316}
317
318WebInspector.OverridesSupport.DeviceOrientation.prototype = {
319    /**
320     * @return {string}
321     */
322    toSetting: function()
323    {
324        return JSON.stringify(this);
325    }
326}
327
328/**
329 * @return {!WebInspector.OverridesSupport.DeviceOrientation}
330 */
331WebInspector.OverridesSupport.DeviceOrientation.parseSetting = function(value)
332{
333    if (value) {
334        var jsonObject = JSON.parse(value);
335        return new WebInspector.OverridesSupport.DeviceOrientation(jsonObject.alpha, jsonObject.beta, jsonObject.gamma);
336    }
337    return new WebInspector.OverridesSupport.DeviceOrientation(0, 0, 0);
338}
339
340/**
341 * @return {?WebInspector.OverridesSupport.DeviceOrientation}
342 */
343WebInspector.OverridesSupport.DeviceOrientation.parseUserInput = function(alphaString, betaString, gammaString)
344{
345    function isUserInputValid(value)
346    {
347        if (!value)
348            return true;
349        return /^[-]?[0-9]*[.]?[0-9]*$/.test(value);
350    }
351
352    if (!alphaString ^ !betaString ^ !gammaString)
353        return null;
354
355    var isAlphaValid = isUserInputValid(alphaString);
356    var isBetaValid = isUserInputValid(betaString);
357    var isGammaValid = isUserInputValid(gammaString);
358
359    if (!isAlphaValid && !isBetaValid && !isGammaValid)
360        return null;
361
362    var alpha = isAlphaValid ? parseFloat(alphaString) : -1;
363    var beta = isBetaValid ? parseFloat(betaString) : -1;
364    var gamma = isGammaValid ? parseFloat(gammaString) : -1;
365
366    return new WebInspector.OverridesSupport.DeviceOrientation(alpha, beta, gamma);
367}
368
369WebInspector.OverridesSupport.DeviceOrientation.clearDeviceOrientationOverride = function()
370{
371    PageAgent.clearDeviceOrientationOverride();
372}
373
374WebInspector.OverridesSupport.prototype = {
375    /**
376     * @param {string} deviceMetrics
377     * @param {string} userAgent
378     */
379    emulateDevice: function(deviceMetrics, userAgent)
380    {
381        this._deviceMetricsChangedListenerMuted = true;
382        WebInspector.settings.deviceMetrics.set(deviceMetrics);
383        WebInspector.settings.userAgent.set(userAgent);
384        WebInspector.settings.overrideDeviceMetrics.set(true);
385        WebInspector.settings.overrideUserAgent.set(true);
386        WebInspector.settings.emulateTouchEvents.set(true);
387        WebInspector.settings.emulateViewport.set(true);
388        delete this._deviceMetricsChangedListenerMuted;
389        this._deviceMetricsChanged();
390    },
391
392    reset: function()
393    {
394        this._deviceMetricsChangedListenerMuted = true;
395        WebInspector.settings.overrideDeviceMetrics.set(false);
396        WebInspector.settings.overrideUserAgent.set(false);
397        WebInspector.settings.emulateTouchEvents.set(false);
398        WebInspector.settings.overrideDeviceOrientation.set(false);
399        WebInspector.settings.overrideGeolocation.set(false);
400        WebInspector.settings.overrideCSSMedia.set(false);
401        WebInspector.settings.emulateViewport.set(false);
402        WebInspector.settings.deviceMetrics.set("");
403        delete this._deviceMetricsChangedListenerMuted;
404        this._deviceMetricsChanged();
405    },
406
407    applyInitialOverrides: function()
408    {
409        this._deviceMetricsChangedListenerMuted = true;
410        this._userAgentChanged();
411        this._deviceMetricsChanged();
412        this._deviceOrientationChanged();
413        this._geolocationPositionChanged();
414        this._emulateTouchEventsChanged();
415        this._cssMediaChanged();
416        delete this._deviceMetricsChangedListenerMuted;
417        this._deviceMetricsChanged();
418        this._revealOverridesTabIfNeeded();
419    },
420
421    _userAgentChanged: function()
422    {
423        if (WebInspector.isInspectingDevice())
424            return;
425        NetworkAgent.setUserAgentOverride(WebInspector.settings.overrideUserAgent.get() ? WebInspector.settings.userAgent.get() : "");
426    },
427
428    _deviceMetricsChanged: function()
429    {
430        if (this._deviceMetricsChangedListenerMuted)
431            return;
432        var metrics = WebInspector.OverridesSupport.DeviceMetrics.parseSetting(WebInspector.settings.overrideDeviceMetrics.get() ? WebInspector.settings.deviceMetrics.get() : "");
433        if (!metrics.isValid())
434            return;
435
436        var dipWidth = Math.round(metrics.width / metrics.deviceScaleFactor);
437        var dipHeight = Math.round(metrics.height / metrics.deviceScaleFactor);
438
439        // Disable override without checks.
440        if (dipWidth && dipHeight && WebInspector.isInspectingDevice()) {
441            this._updateWarningMessage(WebInspector.UIString("Screen emulation on the device is not available."));
442            return;
443        }
444
445        PageAgent.setDeviceMetricsOverride(dipWidth, dipHeight, metrics.deviceScaleFactor, WebInspector.settings.emulateViewport.get(), WebInspector.settings.deviceFitWindow.get(), metrics.textAutosizing, metrics.fontScaleFactor(), apiCallback.bind(this));
446
447        /**
448         * @param {?Protocol.Error} error
449         * @this {WebInspector.OverridesSupport}
450         */
451        function apiCallback(error)
452        {
453            if (error) {
454                this._updateWarningMessage(WebInspector.UIString("Screen emulation is not available on this page."));
455                return;
456            }
457
458            var metricsOverrideEnabled = !!(dipWidth && dipHeight);
459            var viewportEnabled =  WebInspector.settings.emulateViewport.get();
460            this._updateWarningMessage(this._deviceMetricsOverrideEnabled !== metricsOverrideEnabled || (metricsOverrideEnabled && this._emulateViewportEnabled != viewportEnabled) ?
461                WebInspector.UIString("You might need to reload the page for proper user agent spoofing and viewport rendering.") : "");
462            this._deviceMetricsOverrideEnabled = metricsOverrideEnabled;
463            this._emulateViewportEnabled = viewportEnabled;
464        }
465    },
466
467    _geolocationPositionChanged: function()
468    {
469        if (!WebInspector.settings.overrideGeolocation.get()) {
470            PageAgent.clearGeolocationOverride();
471            return;
472        }
473        var geolocation = WebInspector.OverridesSupport.GeolocationPosition.parseSetting(WebInspector.settings.geolocationOverride.get());
474        if (geolocation.error)
475            PageAgent.setGeolocationOverride();
476        else
477            PageAgent.setGeolocationOverride(geolocation.latitude, geolocation.longitude, 150);
478    },
479
480    _deviceOrientationChanged: function()
481    {
482        if (!WebInspector.settings.overrideDeviceOrientation.get()) {
483            PageAgent.clearDeviceOrientationOverride();
484            return;
485        }
486        if (WebInspector.isInspectingDevice())
487            return;
488
489        var deviceOrientation = WebInspector.OverridesSupport.DeviceOrientation.parseSetting(WebInspector.settings.deviceOrientationOverride.get());
490        PageAgent.setDeviceOrientationOverride(deviceOrientation.alpha, deviceOrientation.beta, deviceOrientation.gamma);
491    },
492
493    _emulateTouchEventsChanged: function()
494    {
495        if (WebInspector.isInspectingDevice() && WebInspector.settings.emulateTouchEvents.get())
496            return;
497
498        WebInspector.domAgent.emulateTouchEventObjects(WebInspector.settings.emulateTouchEvents.get());
499    },
500
501    _cssMediaChanged: function()
502    {
503        PageAgent.setEmulatedMedia(WebInspector.settings.overrideCSSMedia.get() ? WebInspector.settings.emulatedCSSMedia.get() : "");
504        WebInspector.cssModel.mediaQueryResultChanged();
505    },
506
507    _anyOverrideIsEnabled: function()
508    {
509        return WebInspector.settings.overrideUserAgent.get() || WebInspector.settings.overrideDeviceMetrics.get() ||
510            WebInspector.settings.overrideGeolocation.get() || WebInspector.settings.overrideDeviceOrientation.get() ||
511            WebInspector.settings.emulateTouchEvents.get() || WebInspector.settings.overrideCSSMedia.get();
512    },
513
514    _revealOverridesTabIfNeeded: function()
515    {
516        if (this._anyOverrideIsEnabled()) {
517            if (!WebInspector.settings.showEmulationViewInDrawer.get())
518                WebInspector.settings.showEmulationViewInDrawer.set(true);
519            WebInspector.inspectorView.showViewInDrawer("emulation");
520        }
521    },
522
523    /**
524     * @param {string} warningMessage
525     */
526    _updateWarningMessage: function(warningMessage)
527    {
528        this._warningMessage = warningMessage;
529        this.dispatchEventToListeners(WebInspector.OverridesSupport.Events.OverridesWarningUpdated);
530    },
531
532    /**
533     * @return {string}
534     */
535    warningMessage: function()
536    {
537        return this._warningMessage || "";
538    },
539
540    __proto__: WebInspector.Object.prototype
541}
542
543
544/**
545 * @type {!WebInspector.OverridesSupport}
546 */
547WebInspector.overridesSupport;
548