Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1177)

Side by Side Diff: chrome/browser/resources/vr_shell/vr_shell_ui.js

Issue 2615613009: Put Scene class into scene namespace. (Closed)
Patch Set: Fix nit. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | chrome/browser/resources/vr_shell/vr_shell_ui_api.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 var vrShellUi = (function() { 5 var vrShellUi = (function() {
6 'use strict'; 6 'use strict';
7 7
8 let scene = new ui.Scene(); 8 let ui = new scene.Scene();
9 let sceneManager; 9 let uiManager;
10 10
11 let uiRootElement = document.querySelector('#ui'); 11 let uiRootElement = document.querySelector('#ui');
12 let uiStyle = window.getComputedStyle(uiRootElement); 12 let uiStyle = window.getComputedStyle(uiRootElement);
13 /** @const */ var ANIM_DURATION = 150; 13 /** @const */ var ANIM_DURATION = 150;
14 14
15 // This value should match the one in VrShellImpl.java 15 // This value should match the one in VrShellImpl.java
16 /** @const */ var UI_DPR = 1.2; 16 /** @const */ var UI_DPR = 1.2;
17 17
18 function getStyleFloat(style, property) { 18 function getStyleFloat(style, property) {
19 let value = parseFloat(style.getPropertyValue(property)); 19 let value = parseFloat(style.getPropertyValue(property));
(...skipping 11 matching lines...) Expand all
31 /** @const */ this.SCREEN_RATIO = 16 / 9; 31 /** @const */ this.SCREEN_RATIO = 16 / 9;
32 /** @const */ this.BROWSING_SCREEN_DISTANCE = 2.0; 32 /** @const */ this.BROWSING_SCREEN_DISTANCE = 2.0;
33 /** @const */ this.FULLSCREEN_DISTANCE = 3.0; 33 /** @const */ this.FULLSCREEN_DISTANCE = 3.0;
34 34
35 let element = new api.UiElement(0, 0, 0, 0); 35 let element = new api.UiElement(0, 0, 0, 0);
36 element.setIsContentQuad(); 36 element.setIsContentQuad();
37 element.setVisible(false); 37 element.setVisible(false);
38 element.setSize( 38 element.setSize(
39 this.SCREEN_HEIGHT * this.SCREEN_RATIO, this.SCREEN_HEIGHT); 39 this.SCREEN_HEIGHT * this.SCREEN_RATIO, this.SCREEN_HEIGHT);
40 element.setTranslation(0, 0, -this.BROWSING_SCREEN_DISTANCE); 40 element.setTranslation(0, 0, -this.BROWSING_SCREEN_DISTANCE);
41 this.elementId = scene.addElement(element); 41 this.elementId = ui.addElement(element);
42 } 42 }
43 43
44 setEnabled(enabled) { 44 setEnabled(enabled) {
45 let update = new api.UiElementUpdate(); 45 let update = new api.UiElementUpdate();
46 update.setVisible(enabled); 46 update.setVisible(enabled);
47 scene.updateElement(this.elementId, update); 47 ui.updateElement(this.elementId, update);
48 } 48 }
49 49
50 setFullscreen(enabled) { 50 setFullscreen(enabled) {
51 let anim = new api.Animation(this.elementId, ANIM_DURATION); 51 let anim = new api.Animation(this.elementId, ANIM_DURATION);
52 if (enabled) { 52 if (enabled) {
53 anim.setTranslation(0, 0, -this.FULLSCREEN_DISTANCE); 53 anim.setTranslation(0, 0, -this.FULLSCREEN_DISTANCE);
54 } else { 54 } else {
55 anim.setTranslation(0, 0, -this.BROWSING_SCREEN_DISTANCE); 55 anim.setTranslation(0, 0, -this.BROWSING_SCREEN_DISTANCE);
56 } 56 }
57 scene.addAnimation(anim); 57 ui.addAnimation(anim);
58 } 58 }
59 59
60 // TODO(crbug/643815): Add a method setting aspect ratio (and possible 60 // TODO(crbug/643815): Add a method setting aspect ratio (and possible
61 // animation of changing it). 61 // animation of changing it).
62 62
63 getElementId() { 63 getElementId() {
64 return this.elementId; 64 return this.elementId;
65 } 65 }
66 }; 66 };
67 67
(...skipping 10 matching lines...) Expand all
78 78
79 let element = new api.UiElement(pixelX, pixelY, pixelWidth, pixelHeight); 79 let element = new api.UiElement(pixelX, pixelY, pixelWidth, pixelHeight);
80 element.setSize(pixelWidth / 1000, pixelHeight / 1000); 80 element.setSize(pixelWidth / 1000, pixelHeight / 1000);
81 81
82 // Pull additional custom properties from CSS. 82 // Pull additional custom properties from CSS.
83 let style = window.getComputedStyle(domElement); 83 let style = window.getComputedStyle(domElement);
84 element.setTranslation( 84 element.setTranslation(
85 getStyleFloat(style, '--tranX'), getStyleFloat(style, '--tranY'), 85 getStyleFloat(style, '--tranX'), getStyleFloat(style, '--tranY'),
86 getStyleFloat(style, '--tranZ')); 86 getStyleFloat(style, '--tranZ'));
87 87
88 this.uiElementId = scene.addElement(element); 88 this.uiElementId = ui.addElement(element);
89 this.uiAnimationId = -1; 89 this.uiAnimationId = -1;
90 this.domElement = domElement; 90 this.domElement = domElement;
91 } 91 }
92 }; 92 };
93 93
94 class RoundButton extends DomUiElement { 94 class RoundButton extends DomUiElement {
95 constructor(domId, callback) { 95 constructor(domId, callback) {
96 super(domId); 96 super(domId);
97 97
98 let button = this.domElement.querySelector('.button'); 98 let button = this.domElement.querySelector('.button');
99 button.addEventListener('mouseenter', this.onMouseEnter.bind(this)); 99 button.addEventListener('mouseenter', this.onMouseEnter.bind(this));
100 button.addEventListener('mouseleave', this.onMouseLeave.bind(this)); 100 button.addEventListener('mouseleave', this.onMouseLeave.bind(this));
101 button.addEventListener('click', callback); 101 button.addEventListener('click', callback);
102 } 102 }
103 103
104 configure(buttonOpacity, captionOpacity, distanceForward) { 104 configure(buttonOpacity, captionOpacity, distanceForward) {
105 let button = this.domElement.querySelector('.button'); 105 let button = this.domElement.querySelector('.button');
106 let caption = this.domElement.querySelector('.caption'); 106 let caption = this.domElement.querySelector('.caption');
107 button.style.opacity = buttonOpacity; 107 button.style.opacity = buttonOpacity;
108 caption.style.opacity = captionOpacity; 108 caption.style.opacity = captionOpacity;
109 let anim = new api.Animation(this.uiElementId, ANIM_DURATION); 109 let anim = new api.Animation(this.uiElementId, ANIM_DURATION);
110 anim.setTranslation(0, 0, distanceForward); 110 anim.setTranslation(0, 0, distanceForward);
111 if (this.uiAnimationId >= 0) { 111 if (this.uiAnimationId >= 0) {
112 scene.removeAnimation(this.uiAnimationId); 112 ui.removeAnimation(this.uiAnimationId);
113 } 113 }
114 this.uiAnimationId = scene.addAnimation(anim); 114 this.uiAnimationId = ui.addAnimation(anim);
115 scene.flush(); 115 ui.flush();
116 } 116 }
117 117
118 onMouseEnter() { 118 onMouseEnter() {
119 this.configure(1, 1, 0.015); 119 this.configure(1, 1, 0.015);
120 } 120 }
121 121
122 onMouseLeave() { 122 onMouseLeave() {
123 this.configure(0.8, 0, 0); 123 this.configure(0.8, 0, 0);
124 } 124 }
125 }; 125 };
(...skipping 26 matching lines...) Expand all
152 ]; 152 ];
153 153
154 /** @const */ var BUTTON_SPACING = 0.136; 154 /** @const */ var BUTTON_SPACING = 0.136;
155 155
156 let startPosition = -BUTTON_SPACING * (descriptors.length / 2.0 - 0.5); 156 let startPosition = -BUTTON_SPACING * (descriptors.length / 2.0 - 0.5);
157 for (let i = 0; i < descriptors.length; i++) { 157 for (let i = 0; i < descriptors.length; i++) {
158 // Use an invisible parent to simplify Z-axis movement on hover. 158 // Use an invisible parent to simplify Z-axis movement on hover.
159 let position = new api.UiElement(0, 0, 0, 0); 159 let position = new api.UiElement(0, 0, 0, 0);
160 position.setVisible(false); 160 position.setVisible(false);
161 position.setTranslation(startPosition + i * BUTTON_SPACING, -0.68, -1); 161 position.setTranslation(startPosition + i * BUTTON_SPACING, -0.68, -1);
162 let id = scene.addElement(position); 162 let id = ui.addElement(position);
163 163
164 let domId = descriptors[i][0]; 164 let domId = descriptors[i][0];
165 let callback = descriptors[i][1]; 165 let callback = descriptors[i][1];
166 let element = new RoundButton(domId, callback); 166 let element = new RoundButton(domId, callback);
167 this.buttons.push(element); 167 this.buttons.push(element);
168 168
169 let update = new api.UiElementUpdate(); 169 let update = new api.UiElementUpdate();
170 update.setParentId(id); 170 update.setParentId(id);
171 update.setVisible(false); 171 update.setVisible(false);
172 scene.updateElement(element.uiElementId, update); 172 ui.updateElement(element.uiElementId, update);
173 } 173 }
174 174
175 this.reloadUiButton = new DomUiElement('#reload-ui-button'); 175 this.reloadUiButton = new DomUiElement('#reload-ui-button');
176 this.reloadUiButton.domElement.addEventListener('click', function() { 176 this.reloadUiButton.domElement.addEventListener('click', function() {
177 scene.purge(); 177 ui.purge();
178 api.doAction(api.Action.RELOAD_UI); 178 api.doAction(api.Action.RELOAD_UI);
179 }); 179 });
180 180
181 let update = new api.UiElementUpdate(); 181 let update = new api.UiElementUpdate();
182 update.setParentId(contentQuadId); 182 update.setParentId(contentQuadId);
183 update.setVisible(false); 183 update.setVisible(false);
184 update.setScale(2.2, 2.2, 1); 184 update.setScale(2.2, 2.2, 1);
185 update.setTranslation(0, -0.6, 0.3); 185 update.setTranslation(0, -0.6, 0.3);
186 update.setAnchoring(api.XAnchoring.XNONE, api.YAnchoring.YBOTTOM); 186 update.setAnchoring(api.XAnchoring.XNONE, api.YAnchoring.YBOTTOM);
187 scene.updateElement(this.reloadUiButton.uiElementId, update); 187 ui.updateElement(this.reloadUiButton.uiElementId, update);
188 } 188 }
189 189
190 setEnabled(enabled) { 190 setEnabled(enabled) {
191 this.enabled = enabled; 191 this.enabled = enabled;
192 this.configure(); 192 this.configure();
193 } 193 }
194 194
195 setReloadUiEnabled(enabled) { 195 setReloadUiEnabled(enabled) {
196 this.reloadUiEnabled = enabled; 196 this.reloadUiEnabled = enabled;
197 this.configure(); 197 this.configure();
198 } 198 }
199 199
200 configure() { 200 configure() {
201 for (let i = 0; i < this.buttons.length; i++) { 201 for (let i = 0; i < this.buttons.length; i++) {
202 let update = new api.UiElementUpdate(); 202 let update = new api.UiElementUpdate();
203 update.setVisible(this.enabled); 203 update.setVisible(this.enabled);
204 scene.updateElement(this.buttons[i].uiElementId, update); 204 ui.updateElement(this.buttons[i].uiElementId, update);
205 } 205 }
206 let update = new api.UiElementUpdate(); 206 let update = new api.UiElementUpdate();
207 update.setVisible(this.enabled && this.reloadUiEnabled); 207 update.setVisible(this.enabled && this.reloadUiEnabled);
208 scene.updateElement(this.reloadUiButton.uiElementId, update); 208 ui.updateElement(this.reloadUiButton.uiElementId, update);
209 } 209 }
210 }; 210 };
211 211
212 class SecureOriginWarnings { 212 class SecureOriginWarnings {
213 constructor() { 213 constructor() {
214 /** @const */ var DISTANCE = 0.7; 214 /** @const */ var DISTANCE = 0.7;
215 /** @const */ var ANGLE_UP = 16.3 * Math.PI / 180.0; 215 /** @const */ var ANGLE_UP = 16.3 * Math.PI / 180.0;
216 216
217 this.enabled = false; 217 this.enabled = false;
218 this.secure = false; 218 this.secure = false;
219 this.secureOriginTimer = null; 219 this.secureOriginTimer = null;
220 220
221 // Permanent WebVR security warning. This warning is shown near the top of 221 // Permanent WebVR security warning. This warning is shown near the top of
222 // the field of view. 222 // the field of view.
223 this.webVrSecureWarning = new DomUiElement('#webvr-not-secure-permanent'); 223 this.webVrSecureWarning = new DomUiElement('#webvr-not-secure-permanent');
224 let update = new api.UiElementUpdate(); 224 let update = new api.UiElementUpdate();
225 update.setScale(DISTANCE, DISTANCE, 1); 225 update.setScale(DISTANCE, DISTANCE, 1);
226 update.setTranslation( 226 update.setTranslation(
227 0, DISTANCE * Math.sin(ANGLE_UP), -DISTANCE * Math.cos(ANGLE_UP)); 227 0, DISTANCE * Math.sin(ANGLE_UP), -DISTANCE * Math.cos(ANGLE_UP));
228 update.setRotation(1.0, 0.0, 0.0, ANGLE_UP); 228 update.setRotation(1.0, 0.0, 0.0, ANGLE_UP);
229 update.setHitTestable(false); 229 update.setHitTestable(false);
230 update.setVisible(false); 230 update.setVisible(false);
231 update.setLockToFieldOfView(true); 231 update.setLockToFieldOfView(true);
232 scene.updateElement(this.webVrSecureWarning.uiElementId, update); 232 ui.updateElement(this.webVrSecureWarning.uiElementId, update);
233 233
234 // Temporary WebVR security warning. This warning is shown in the center 234 // Temporary WebVR security warning. This warning is shown in the center
235 // of the field of view, for a limited period of time. 235 // of the field of view, for a limited period of time.
236 this.transientWarning = new DomUiElement('#webvr-not-secure-transient'); 236 this.transientWarning = new DomUiElement('#webvr-not-secure-transient');
237 update = new api.UiElementUpdate(); 237 update = new api.UiElementUpdate();
238 update.setScale(DISTANCE, DISTANCE, 1); 238 update.setScale(DISTANCE, DISTANCE, 1);
239 update.setTranslation(0, 0, -DISTANCE); 239 update.setTranslation(0, 0, -DISTANCE);
240 update.setHitTestable(false); 240 update.setHitTestable(false);
241 update.setVisible(false); 241 update.setVisible(false);
242 update.setLockToFieldOfView(true); 242 update.setLockToFieldOfView(true);
243 scene.updateElement(this.transientWarning.uiElementId, update); 243 ui.updateElement(this.transientWarning.uiElementId, update);
244 } 244 }
245 245
246 setEnabled(enabled) { 246 setEnabled(enabled) {
247 this.enabled = enabled; 247 this.enabled = enabled;
248 this.updateState(); 248 this.updateState();
249 } 249 }
250 250
251 setSecure(secure) { 251 setSecure(secure) {
252 this.secure = secure; 252 this.secure = secure;
253 this.updateState(); 253 this.updateState();
(...skipping 10 matching lines...) Expand all
264 if (visible) { 264 if (visible) {
265 this.secureOriginTimer = 265 this.secureOriginTimer =
266 setTimeout(this.onTransientTimer.bind(this), TRANSIENT_TIMEOUT_MS); 266 setTimeout(this.onTransientTimer.bind(this), TRANSIENT_TIMEOUT_MS);
267 } 267 }
268 this.showOrHideWarnings(visible); 268 this.showOrHideWarnings(visible);
269 } 269 }
270 270
271 showOrHideWarnings(visible) { 271 showOrHideWarnings(visible) {
272 let update = new api.UiElementUpdate(); 272 let update = new api.UiElementUpdate();
273 update.setVisible(visible); 273 update.setVisible(visible);
274 scene.updateElement(this.webVrSecureWarning.uiElementId, update); 274 ui.updateElement(this.webVrSecureWarning.uiElementId, update);
275 update = new api.UiElementUpdate(); 275 update = new api.UiElementUpdate();
276 update.setVisible(visible); 276 update.setVisible(visible);
277 scene.updateElement(this.transientWarning.uiElementId, update); 277 ui.updateElement(this.transientWarning.uiElementId, update);
278 } 278 }
279 279
280 onTransientTimer() { 280 onTransientTimer() {
281 let update = new api.UiElementUpdate(); 281 let update = new api.UiElementUpdate();
282 update.setVisible(false); 282 update.setVisible(false);
283 scene.updateElement(this.transientWarning.uiElementId, update); 283 ui.updateElement(this.transientWarning.uiElementId, update);
284 this.secureOriginTimer = null; 284 this.secureOriginTimer = null;
285 scene.flush(); 285 ui.flush();
286 } 286 }
287 }; 287 };
288 288
289 class Omnibox { 289 class Omnibox {
290 constructor(contentQuadId) { 290 constructor(contentQuadId) {
291 this.domUiElement = new DomUiElement('#omnibox-container'); 291 this.domUiElement = new DomUiElement('#omnibox-container');
292 this.enabled = false; 292 this.enabled = false;
293 this.loading = false; 293 this.loading = false;
294 this.loadingProgress = 0; 294 this.loadingProgress = 0;
295 this.level = 0; 295 this.level = 0;
296 this.visibilityTimeout = 0; 296 this.visibilityTimeout = 0;
297 this.visibilityTimer = null; 297 this.visibilityTimer = null;
298 this.visibleAfterTransition = false; 298 this.visibleAfterTransition = false;
299 this.nativeState = {}; 299 this.nativeState = {};
300 300
301 // Initially invisible. 301 // Initially invisible.
302 let update = new api.UiElementUpdate(); 302 let update = new api.UiElementUpdate();
303 update.setVisible(false); 303 update.setVisible(false);
304 scene.updateElement(this.domUiElement.uiElementId, update); 304 ui.updateElement(this.domUiElement.uiElementId, update);
305 this.nativeState.visible = false; 305 this.nativeState.visible = false;
306 306
307 // Pull colors from CSS so that Javascript can set the progress indicator 307 // Pull colors from CSS so that Javascript can set the progress indicator
308 // gradient programmatically. 308 // gradient programmatically.
309 let border = 309 let border =
310 this.domUiElement.domElement.querySelector('#omnibox-border'); 310 this.domUiElement.domElement.querySelector('#omnibox-border');
311 let style = window.getComputedStyle(border); 311 let style = window.getComputedStyle(border);
312 this.statusBarColor = getStyleString(style, '--statusBarColor'); 312 this.statusBarColor = getStyleString(style, '--statusBarColor');
313 this.backgroundColor = style.backgroundColor; 313 this.backgroundColor = style.backgroundColor;
314 314
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
433 this.setNativeVisibility(true); 433 this.setNativeVisibility(true);
434 } 434 }
435 435
436 setNativeVisibility(visible) { 436 setNativeVisibility(visible) {
437 if (visible == this.nativeState.visible) { 437 if (visible == this.nativeState.visible) {
438 return; 438 return;
439 } 439 }
440 this.nativeState.visible = visible; 440 this.nativeState.visible = visible;
441 let update = new api.UiElementUpdate(); 441 let update = new api.UiElementUpdate();
442 update.setVisible(visible); 442 update.setVisible(visible);
443 scene.updateElement(this.domUiElement.uiElementId, update); 443 ui.updateElement(this.domUiElement.uiElementId, update);
444 scene.flush(); 444 ui.flush();
445 } 445 }
446 }; 446 };
447 447
448 class SceneManager { 448 class UiManager {
449 constructor() { 449 constructor() {
450 this.mode = api.Mode.UNKNOWN; 450 this.mode = api.Mode.UNKNOWN;
451 this.menuMode = false; 451 this.menuMode = false;
452 this.fullscreen = false; 452 this.fullscreen = false;
453 453
454 this.contentQuad = new ContentQuad(); 454 this.contentQuad = new ContentQuad();
455 let contentId = this.contentQuad.getElementId(); 455 let contentId = this.contentQuad.getElementId();
456 456
457 this.controls = new Controls(contentId); 457 this.controls = new Controls(contentId);
458 this.secureOriginWarnings = new SecureOriginWarnings(); 458 this.secureOriginWarnings = new SecureOriginWarnings();
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
490 setWebVRSecureOrigin(secure) { 490 setWebVRSecureOrigin(secure) {
491 this.secureOriginWarnings.setSecure(secure); 491 this.secureOriginWarnings.setSecure(secure);
492 } 492 }
493 493
494 setReloadUiEnabled(enabled) { 494 setReloadUiEnabled(enabled) {
495 this.controls.setReloadUiEnabled(enabled); 495 this.controls.setReloadUiEnabled(enabled);
496 } 496 }
497 }; 497 };
498 498
499 function initialize() { 499 function initialize() {
500 sceneManager = new SceneManager(); 500 uiManager = new UiManager();
501 scene.flush(); 501 ui.flush();
502 502
503 api.domLoaded(); 503 api.domLoaded();
504 } 504 }
505 505
506 function command(dict) { 506 function command(dict) {
507 if ('mode' in dict) { 507 if ('mode' in dict) {
508 sceneManager.setMode(dict['mode'], dict['menuMode'], dict['fullscreen']); 508 uiManager.setMode(dict['mode'], dict['menuMode'], dict['fullscreen']);
509 } 509 }
510 if ('securityLevel' in dict) { 510 if ('securityLevel' in dict) {
511 sceneManager.setSecurityLevel(dict['securityLevel']); 511 uiManager.setSecurityLevel(dict['securityLevel']);
512 } 512 }
513 if ('webVRSecureOrigin' in dict) { 513 if ('webVRSecureOrigin' in dict) {
514 sceneManager.setWebVRSecureOrigin(dict['webVRSecureOrigin']); 514 uiManager.setWebVRSecureOrigin(dict['webVRSecureOrigin']);
515 } 515 }
516 if ('enableReloadUi' in dict) { 516 if ('enableReloadUi' in dict) {
517 sceneManager.setReloadUiEnabled(dict['enableReloadUi']); 517 uiManager.setReloadUiEnabled(dict['enableReloadUi']);
518 } 518 }
519 if ('url' in dict) { 519 if ('url' in dict) {
520 let url = dict['url']; 520 let url = dict['url'];
521 sceneManager.omnibox.setURL(url['host'], url['path']); 521 uiManager.omnibox.setURL(url['host'], url['path']);
522 } 522 }
523 if ('loading' in dict) { 523 if ('loading' in dict) {
524 sceneManager.omnibox.setLoading(dict['loading']); 524 uiManager.omnibox.setLoading(dict['loading']);
525 } 525 }
526 if ('loadingProgress' in dict) { 526 if ('loadingProgress' in dict) {
527 sceneManager.omnibox.setLoadingProgress(dict['loadingProgress']); 527 uiManager.omnibox.setLoadingProgress(dict['loadingProgress']);
528 } 528 }
529 scene.flush(); 529 ui.flush();
530 } 530 }
531 531
532 return { 532 return {
533 initialize: initialize, 533 initialize: initialize,
534 command: command, 534 command: command,
535 }; 535 };
536 })(); 536 })();
537 537
538 document.addEventListener('DOMContentLoaded', vrShellUi.initialize); 538 document.addEventListener('DOMContentLoaded', vrShellUi.initialize);
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/resources/vr_shell/vr_shell_ui_api.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698