Make calendar pickers testable
[WebKit-https.git] / Source / WebCore / testing / InternalSettings.cpp
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
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "InternalSettings.h"
28
29 #include "CachedResourceLoader.h"
30 #include "Chrome.h"
31 #include "ChromeClient.h"
32 #include "Document.h"
33 #include "ExceptionCode.h"
34 #include "Frame.h"
35 #include "FrameView.h"
36 #include "InspectorController.h"
37 #include "Language.h"
38 #include "LocaleToScriptMapping.h"
39 #include "MockPagePopupDriver.h"
40 #include "Page.h"
41 #include "RuntimeEnabledFeatures.h"
42 #include "Settings.h"
43 #include "TextRun.h"
44
45 #if ENABLE(INPUT_TYPE_COLOR)
46 #include "ColorChooser.h"
47 #endif
48
49 #define InternalSettingsGuardForSettingsReturn(returnValue) \
50     if (!settings()) { \
51         ec = INVALID_ACCESS_ERR; \
52         return returnValue; \
53     }
54
55 #define InternalSettingsGuardForSettings()  \
56     if (!settings()) { \
57         ec = INVALID_ACCESS_ERR; \
58         return; \
59     }
60
61 #define InternalSettingsGuardForPageReturn(returnValue) \
62     if (!page()) { \
63         ec = INVALID_ACCESS_ERR; \
64         return returnValue; \
65     }
66
67 #define InternalSettingsGuardForPage() \
68     if (!page()) { \
69         ec = INVALID_ACCESS_ERR; \
70         return; \
71     }
72
73 namespace WebCore {
74
75 InternalSettings::Backup::Backup(Page* page, Settings* settings)
76     : m_originalPasswordEchoDurationInSeconds(settings->passwordEchoDurationInSeconds())
77     , m_originalPasswordEchoEnabled(settings->passwordEchoEnabled())
78     , m_originalCSSExclusionsEnabled(RuntimeEnabledFeatures::cssExclusionsEnabled())
79 #if ENABLE(SHADOW_DOM)
80     , m_originalShadowDOMEnabled(RuntimeEnabledFeatures::shadowDOMEnabled())
81 #endif
82     , m_originalEditingBehavior(settings->editingBehaviorType())
83     , m_originalFixedPositionCreatesStackingContext(settings->fixedPositionCreatesStackingContext())
84     , m_originalSyncXHRInDocumentsEnabled(settings->syncXHRInDocumentsEnabled())
85 #if ENABLE(INSPECTOR) && ENABLE(JAVASCRIPT_DEBUGGER)
86     , m_originalJavaScriptProfilingEnabled(page->inspectorController() && page->inspectorController()->profilerEnabled())
87 #endif
88     , m_originalWindowFocusRestricted(settings->windowFocusRestricted())
89     , m_originalDeviceSupportsTouch(settings->deviceSupportsTouch())
90     , m_originalDeviceSupportsMouse(settings->deviceSupportsMouse())
91 #if ENABLE(TEXT_AUTOSIZING)
92     , m_originalTextAutosizingEnabled(settings->textAutosizingEnabled())
93     , m_originalTextAutosizingWindowSizeOverride(settings->textAutosizingWindowSizeOverride())
94 #endif
95 #if ENABLE(DIALOG_ELEMENT)
96     , m_originalDialogElementEnabled(RuntimeEnabledFeatures::dialogElementEnabled())
97 #endif
98 {
99 }
100
101
102 void InternalSettings::Backup::restoreTo(Page* page, Settings* settings)
103 {
104     settings->setPasswordEchoDurationInSeconds(m_originalPasswordEchoDurationInSeconds);
105     settings->setPasswordEchoEnabled(m_originalPasswordEchoEnabled);
106     RuntimeEnabledFeatures::setCSSExclusionsEnabled(m_originalCSSExclusionsEnabled);
107 #if ENABLE(SHADOW_DOM)
108     RuntimeEnabledFeatures::setShadowDOMEnabled(m_originalShadowDOMEnabled);
109 #endif
110     settings->setEditingBehaviorType(m_originalEditingBehavior);
111     settings->setFixedPositionCreatesStackingContext(m_originalFixedPositionCreatesStackingContext);
112     settings->setSyncXHRInDocumentsEnabled(m_originalSyncXHRInDocumentsEnabled);
113 #if ENABLE(INSPECTOR) && ENABLE(JAVASCRIPT_DEBUGGER)
114     if (page->inspectorController())
115         page->inspectorController()->setProfilerEnabled(m_originalJavaScriptProfilingEnabled);
116 #endif
117     settings->setWindowFocusRestricted(m_originalWindowFocusRestricted);
118     settings->setDeviceSupportsTouch(m_originalDeviceSupportsTouch);
119     settings->setDeviceSupportsMouse(m_originalDeviceSupportsMouse);
120 #if ENABLE(TEXT_AUTOSIZING)
121     settings->setTextAutosizingEnabled(m_originalTextAutosizingEnabled);
122     settings->setTextAutosizingWindowSizeOverride(m_originalTextAutosizingWindowSizeOverride);
123 #endif
124 #if ENABLE(DIALOG_ELEMENT)
125     RuntimeEnabledFeatures::setDialogElementEnabled(m_originalDialogElementEnabled);
126 #endif
127
128 #if ENABLE(PAGE_POPUP)
129     if (page->chrome())
130         page->chrome()->client()->resetPagePopupDriver();
131 #endif
132 }
133
134 InternalSettings* InternalSettings::from(Page* page)
135 {
136     DEFINE_STATIC_LOCAL(AtomicString, name, ("InternalSettings"));
137     if (!SuperType::from(page, name))
138         SuperType::provideTo(page, name, adoptRef(new InternalSettings(page)));
139     return static_cast<InternalSettings*>(SuperType::from(page, name));
140 }
141
142 InternalSettings::~InternalSettings()
143 {
144 }
145
146 InternalSettings::InternalSettings(Page* page)
147     : m_page(page)
148     , m_backup(page, page->settings())
149 {
150 }
151
152 void InternalSettings::reset()
153 {
154     TextRun::setAllowsRoundingHacks(false);
155     setUserPreferredLanguages(Vector<String>());
156     page()->setPagination(Page::Pagination());
157     page()->setPageScaleFactor(1, IntPoint(0, 0));
158
159     m_backup.restoreTo(page(), settings());
160     m_backup = Backup(page(), settings());
161 }
162
163 Settings* InternalSettings::settings() const
164 {
165     if (!page())
166         return 0;
167     return page()->settings();
168 }
169
170 void InternalSettings::setInspectorResourcesDataSizeLimits(int maximumResourcesContentSize, int maximumSingleResourceContentSize, ExceptionCode& ec)
171 {
172 #if ENABLE(INSPECTOR)
173     if (!page() || !page()->inspectorController()) {
174         ec = INVALID_ACCESS_ERR;
175         return;
176     }
177     page()->inspectorController()->setResourcesDataSizeLimitsFromInternals(maximumResourcesContentSize, maximumSingleResourceContentSize);
178 #else
179     UNUSED_PARAM(maximumResourcesContentSize);
180     UNUSED_PARAM(maximumSingleResourceContentSize);
181     UNUSED_PARAM(ec);
182 #endif
183 }
184
185 void InternalSettings::setForceCompositingMode(bool enabled, ExceptionCode& ec)
186 {
187     InternalSettingsGuardForSettings();
188     settings()->setForceCompositingMode(enabled);
189 }
190
191 void InternalSettings::setAcceleratedFiltersEnabled(bool enabled, ExceptionCode& ec)
192 {
193     InternalSettingsGuardForSettings();
194     settings()->setAcceleratedFiltersEnabled(enabled);
195 }
196
197 void InternalSettings::setEnableCompositingForFixedPosition(bool enabled, ExceptionCode& ec)
198 {
199     InternalSettingsGuardForSettings();
200     settings()->setAcceleratedCompositingForFixedPositionEnabled(enabled);
201 }
202
203 void InternalSettings::setEnableCompositingForScrollableFrames(bool enabled, ExceptionCode& ec)
204 {
205     InternalSettingsGuardForSettings();
206     settings()->setAcceleratedCompositingForScrollableFramesEnabled(enabled);
207 }
208
209 void InternalSettings::setAcceleratedDrawingEnabled(bool enabled, ExceptionCode& ec)
210 {
211     InternalSettingsGuardForSettings();
212     settings()->setAcceleratedDrawingEnabled(enabled);
213 }
214
215 void InternalSettings::setMockScrollbarsEnabled(bool enabled, ExceptionCode& ec)
216 {
217     InternalSettingsGuardForSettings();
218     settings()->setMockScrollbarsEnabled(enabled);
219 }
220
221 void InternalSettings::setPasswordEchoEnabled(bool enabled, ExceptionCode& ec)
222 {
223     InternalSettingsGuardForSettings();
224     settings()->setPasswordEchoEnabled(enabled);
225 }
226
227 void InternalSettings::setPasswordEchoDurationInSeconds(double durationInSeconds, ExceptionCode& ec)
228 {
229     InternalSettingsGuardForSettings();
230     settings()->setPasswordEchoDurationInSeconds(durationInSeconds);
231 }
232
233 void InternalSettings::setFixedElementsLayoutRelativeToFrame(bool enabled, ExceptionCode& ec)
234 {
235     InternalSettingsGuardForSettings();
236     settings()->setFixedElementsLayoutRelativeToFrame(enabled);
237 }
238
239 void InternalSettings::setUnifiedTextCheckingEnabled(bool enabled, ExceptionCode& ec)
240 {
241     InternalSettingsGuardForSettings();
242     settings()->setUnifiedTextCheckerEnabled(enabled);
243 }
244
245 bool InternalSettings::unifiedTextCheckingEnabled(ExceptionCode& ec)
246 {
247     InternalSettingsGuardForSettingsReturn(false);
248     return settings()->unifiedTextCheckerEnabled();
249 }
250
251 void InternalSettings::setPageScaleFactor(float scaleFactor, int x, int y, ExceptionCode& ec)
252 {
253     InternalSettingsGuardForPage();
254     page()->setPageScaleFactor(scaleFactor, IntPoint(x, y));
255 }
256
257 void InternalSettings::setShadowDOMEnabled(bool enabled, ExceptionCode& ec)
258 {
259 #if ENABLE(SHADOW_DOM)
260     UNUSED_PARAM(ec);
261     RuntimeEnabledFeatures::setShadowDOMEnabled(enabled);
262 #else
263     // Even SHADOW_DOM is off, InternalSettings allows setShadowDOMEnabled(false) to
264     // have broader test coverage. But it cannot be setShadowDOMEnabled(true).
265     if (enabled)
266         ec = INVALID_ACCESS_ERR;
267 #endif
268 }
269
270 void InternalSettings::setTouchEventEmulationEnabled(bool enabled, ExceptionCode& ec)
271 {
272 #if ENABLE(TOUCH_EVENTS)
273     InternalSettingsGuardForSettings();
274     settings()->setTouchEventEmulationEnabled(enabled);
275 #else
276     UNUSED_PARAM(enabled);
277     UNUSED_PARAM(ec);
278 #endif
279 }
280
281 void InternalSettings::setDeviceSupportsTouch(bool enabled, ExceptionCode& ec)
282 {
283     InternalSettingsGuardForSettings();
284     settings()->setDeviceSupportsTouch(enabled);
285 }
286
287 void InternalSettings::setDeviceSupportsMouse(bool enabled, ExceptionCode& ec)
288 {
289     InternalSettingsGuardForSettings();
290     settings()->setDeviceSupportsMouse(enabled);
291 }
292
293 typedef void (Settings::*SetFontFamilyFunction)(const AtomicString&, UScriptCode);
294 static void setFontFamily(Settings* settings, const String& family, const String& script, SetFontFamilyFunction setter)
295 {
296     UScriptCode code = scriptNameToCode(script);
297     if (code != USCRIPT_INVALID_CODE)
298         (settings->*setter)(family, code);
299 }
300
301 void InternalSettings::setStandardFontFamily(const String& family, const String& script, ExceptionCode& ec)
302 {
303     InternalSettingsGuardForSettings();
304     setFontFamily(settings(), family, script, &Settings::setStandardFontFamily);
305 }
306
307 void InternalSettings::setSerifFontFamily(const String& family, const String& script, ExceptionCode& ec)
308 {
309     InternalSettingsGuardForSettings();
310     setFontFamily(settings(), family, script, &Settings::setSerifFontFamily);
311 }
312
313 void InternalSettings::setSansSerifFontFamily(const String& family, const String& script, ExceptionCode& ec)
314 {
315     InternalSettingsGuardForSettings();
316     setFontFamily(settings(), family, script, &Settings::setSansSerifFontFamily);
317 }
318
319 void InternalSettings::setFixedFontFamily(const String& family, const String& script, ExceptionCode& ec)
320 {
321     InternalSettingsGuardForSettings();
322     setFontFamily(settings(), family, script, &Settings::setFixedFontFamily);
323 }
324
325 void InternalSettings::setCursiveFontFamily(const String& family, const String& script, ExceptionCode& ec)
326 {
327     InternalSettingsGuardForSettings();
328     setFontFamily(settings(), family, script, &Settings::setCursiveFontFamily);
329 }
330
331 void InternalSettings::setFantasyFontFamily(const String& family, const String& script, ExceptionCode& ec)
332 {
333     InternalSettingsGuardForSettings();
334     setFontFamily(settings(), family, script, &Settings::setFantasyFontFamily);
335 }
336
337 void InternalSettings::setPictographFontFamily(const String& family, const String& script, ExceptionCode& ec)
338 {
339     InternalSettingsGuardForSettings();
340     setFontFamily(settings(), family, script, &Settings::setPictographFontFamily);
341 }
342
343 void InternalSettings::setTextAutosizingEnabled(bool enabled, ExceptionCode& ec)
344 {
345 #if ENABLE(TEXT_AUTOSIZING)
346     InternalSettingsGuardForSettings();
347     settings()->setTextAutosizingEnabled(enabled);
348 #else
349     UNUSED_PARAM(enabled);
350     UNUSED_PARAM(ec);
351 #endif
352 }
353
354 void InternalSettings::setTextAutosizingWindowSizeOverride(int width, int height, ExceptionCode& ec)
355 {
356 #if ENABLE(TEXT_AUTOSIZING)
357     InternalSettingsGuardForSettings();
358     settings()->setTextAutosizingWindowSizeOverride(IntSize(width, height));
359 #else
360     UNUSED_PARAM(width);
361     UNUSED_PARAM(height);
362     UNUSED_PARAM(ec);
363 #endif
364 }
365
366 void InternalSettings::setEnableScrollAnimator(bool enabled, ExceptionCode& ec)
367 {
368 #if ENABLE(SMOOTH_SCROLLING)
369     InternalSettingsGuardForSettings();
370     settings()->setEnableScrollAnimator(enabled);
371 #else
372     UNUSED_PARAM(enabled);
373     UNUSED_PARAM(ec);
374 #endif
375 }
376
377 bool InternalSettings::scrollAnimatorEnabled(ExceptionCode& ec)
378 {
379 #if ENABLE(SMOOTH_SCROLLING)
380     InternalSettingsGuardForSettingsReturn(false);
381     return settings()->scrollAnimatorEnabled();
382 #else
383     UNUSED_PARAM(ec);
384     return false;
385 #endif
386 }
387
388 void InternalSettings::setCSSExclusionsEnabled(bool enabled, ExceptionCode& ec)
389 {
390     UNUSED_PARAM(ec);
391     RuntimeEnabledFeatures::setCSSExclusionsEnabled(enabled);
392 }
393
394 void InternalSettings::setCSSVariablesEnabled(bool enabled, ExceptionCode& ec)
395 {
396     InternalSettingsGuardForSettings();
397     settings()->setCSSVariablesEnabled(enabled);
398 }
399
400 bool InternalSettings::cssVariablesEnabled(ExceptionCode& ec)
401 {
402     InternalSettingsGuardForSettingsReturn(false);
403     return settings()->cssVariablesEnabled();
404 }
405
406 void InternalSettings::setMediaPlaybackRequiresUserGesture(bool enabled, ExceptionCode& ec)
407 {
408     InternalSettingsGuardForSettings();
409     settings()->setMediaPlaybackRequiresUserGesture(enabled);
410 }
411
412 void InternalSettings::setEditingBehavior(const String& editingBehavior, ExceptionCode& ec)
413 {
414     InternalSettingsGuardForSettings();
415     if (equalIgnoringCase(editingBehavior, "win"))
416         settings()->setEditingBehaviorType(EditingWindowsBehavior);
417     else if (equalIgnoringCase(editingBehavior, "mac"))
418         settings()->setEditingBehaviorType(EditingMacBehavior);
419     else if (equalIgnoringCase(editingBehavior, "unix"))
420         settings()->setEditingBehaviorType(EditingUnixBehavior);
421     else
422         ec = SYNTAX_ERR;
423 }
424
425 void InternalSettings::setFixedPositionCreatesStackingContext(bool creates, ExceptionCode& ec)
426 {
427     InternalSettingsGuardForSettings();
428     settings()->setFixedPositionCreatesStackingContext(creates);
429 }
430
431 void InternalSettings::setSyncXHRInDocumentsEnabled(bool enabled, ExceptionCode& ec)
432 {
433     InternalSettingsGuardForSettings();
434     settings()->setSyncXHRInDocumentsEnabled(enabled);
435 }
436
437 void InternalSettings::setJavaScriptProfilingEnabled(bool enabled, ExceptionCode& ec)
438 {
439 #if ENABLE(INSPECTOR)
440     if (!page() || !page()->inspectorController()) {
441         ec = INVALID_ACCESS_ERR;
442         return;
443     }
444
445     page()->inspectorController()->setProfilerEnabled(enabled);
446 #else
447     UNUSED_PARAM(enabled);
448     UNUSED_PARAM(ec);
449     return;
450 #endif
451 }
452
453 void InternalSettings::setWindowFocusRestricted(bool restricted, ExceptionCode& ec)
454 {
455     InternalSettingsGuardForSettings();
456     settings()->setWindowFocusRestricted(restricted);
457 }
458
459 void InternalSettings::setDialogElementEnabled(bool enabled, ExceptionCode& ec)
460 {
461     UNUSED_PARAM(ec);
462 #if ENABLE(DIALOG_ELEMENT)
463     RuntimeEnabledFeatures::setDialogElementEnabled(enabled);
464 #else
465     UNUSED_PARAM(enabled);
466 #endif
467 }
468
469 void InternalSettings::allowRoundingHacks() const
470 {
471     TextRun::setAllowsRoundingHacks(true);
472 }
473
474 Vector<String> InternalSettings::userPreferredLanguages() const
475 {
476     return WebCore::userPreferredLanguages();
477 }
478
479 void InternalSettings::setUserPreferredLanguages(const Vector<String>& languages)
480 {
481     WebCore::overrideUserPreferredLanguages(languages);
482 }
483
484 void InternalSettings::setShouldDisplayTrackKind(const String& kind, bool enabled, ExceptionCode& ec)
485 {
486     InternalSettingsGuardForSettings();
487
488 #if ENABLE(VIDEO_TRACK)
489     if (equalIgnoringCase(kind, "Subtitles"))
490         settings()->setShouldDisplaySubtitles(enabled);
491     else if (equalIgnoringCase(kind, "Captions"))
492         settings()->setShouldDisplayCaptions(enabled);
493     else if (equalIgnoringCase(kind, "TextDescriptions"))
494         settings()->setShouldDisplayTextDescriptions(enabled);
495     else
496         ec = SYNTAX_ERR;
497 #else
498     UNUSED_PARAM(kind);
499     UNUSED_PARAM(enabled);
500 #endif
501 }
502
503 bool InternalSettings::shouldDisplayTrackKind(const String& kind, ExceptionCode& ec)
504 {
505     InternalSettingsGuardForSettingsReturn(false);
506
507 #if ENABLE(VIDEO_TRACK)
508     if (equalIgnoringCase(kind, "Subtitles"))
509         return settings()->shouldDisplaySubtitles();
510     if (equalIgnoringCase(kind, "Captions"))
511         return settings()->shouldDisplayCaptions();
512     if (equalIgnoringCase(kind, "TextDescriptions"))
513         return settings()->shouldDisplayTextDescriptions();
514
515     ec = SYNTAX_ERR;
516     return false;
517 #else
518     UNUSED_PARAM(kind);
519     return false;
520 #endif
521 }
522
523 void InternalSettings::setPagination(const String& mode, int gap, ExceptionCode& ec)
524 {
525     if (!page()) {
526         ec = INVALID_ACCESS_ERR;
527         return;
528     }
529
530     Page::Pagination pagination;
531     if (mode == "Unpaginated")
532         pagination.mode = Page::Pagination::Unpaginated;
533     else if (mode == "LeftToRightPaginated")
534         pagination.mode = Page::Pagination::LeftToRightPaginated;
535     else if (mode == "RightToLeftPaginated")
536         pagination.mode = Page::Pagination::RightToLeftPaginated;
537     else if (mode == "TopToBottomPaginated")
538         pagination.mode = Page::Pagination::TopToBottomPaginated;
539     else if (mode == "BottomToTopPaginated")
540         pagination.mode = Page::Pagination::BottomToTopPaginated;
541     else {
542         ec = SYNTAX_ERR;
543         return;
544     }
545
546     pagination.gap = gap;
547     page()->setPagination(pagination);
548 }
549
550 void InternalSettings::setEnableMockPagePopup(bool enabled, ExceptionCode& ec)
551 {
552 #if ENABLE(PAGE_POPUP)
553     InternalSettingsGuardForPage();
554     if (!page()->chrome())
555         return;
556     if (!enabled) {
557         page()->chrome()->client()->resetPagePopupDriver();
558         return;
559     }
560     if (!m_pagePopupDriver)
561         m_pagePopupDriver = MockPagePopupDriver::create(page()->mainFrame());
562     page()->chrome()->client()->setPagePopupDriver(m_pagePopupDriver.get());
563 #else
564     UNUSED_PARAM(enabled);
565     UNUSED_PARAM(ec);
566 #endif
567 }
568
569 String InternalSettings::configurationForViewport(float devicePixelRatio, int deviceWidth, int deviceHeight, int availableWidth, int availableHeight, ExceptionCode& ec)
570 {
571     if (!page()) {
572         ec = INVALID_ACCESS_ERR;
573         return String();
574     }
575
576     const int defaultLayoutWidthForNonMobilePages = 980;
577
578     ViewportArguments arguments = page()->viewportArguments();
579     ViewportAttributes attributes = computeViewportAttributes(arguments, defaultLayoutWidthForNonMobilePages, deviceWidth, deviceHeight, devicePixelRatio, IntSize(availableWidth, availableHeight));
580     restrictMinimumScaleFactorToViewportSize(attributes, IntSize(availableWidth, availableHeight));
581     restrictScaleFactorToInitialScaleIfNotUserScalable(attributes);
582
583     return "viewport size " + String::number(attributes.layoutSize.width()) + "x" + String::number(attributes.layoutSize.height()) + " scale " + String::number(attributes.initialScale) + " with limits [" + String::number(attributes.minimumScale) + ", " + String::number(attributes.maximumScale) + "] and userScalable " + (attributes.userScalable ? "true" : "false");
584 }
585
586 }